Skip to main content

On-chain Program

Overview

The on-chain program is the core component that analyzes token mints and liquidity pools to calculate and execute optimal arbitrage trades. It provides maximum efficiency by performing all calculations and trade executions directly on-chain.

For maximum flexibility, you can write your own bot and call the on-chain program directly. This approach allows for complete customization while leveraging the program's optimized arbitrage calculations and execution capabilities.

Demo Bot: Solana On-chain Arbitrage Bot

Supported DEXs

The program currently supports the following decentralized exchanges:

Configuration Recommendations

Address Lookup Table

It's recommended to include this address lookup table as it contains all the fixed keys needed:

4hzxAKCWrtpudhcQGL67UqGoR8b1hpUfUGKpy3QKCwkg

Compute Unit Limits

  • AMM pools/Solfi only: Minimum 300,000 compute units
  • Other pool types: Minimum 450,000 compute units
  • Optimal performance: 600,000 - 700,000 compute units for larger arbitrage opportunities
  • Multiple mints: Requires additional compute units beyond single mint operations

Usage Example

Here's a simplified Rust example demonstrating how to interact with the program. You can include multiple token mints with their corresponding pools.

For detailed implementation of pool key extraction for each pool type, refer to the demo bot repository.

Example Rust function to generate arbitrage instruction
pub fn generate_onchain_swap_multiple_mints_instruction(
wallet: &Pubkey,
base_mint: &Pubkey, // Support SOL/USDC
wallet_base_account: &Pubkey,
token_program: &Pubkey,
system_program: &Pubkey,
associated_token_program: &Pubkey,
token_pools: &[(
Pubkey, // X mint
Pubkey, // Wallet X account
Vec<(
Pubkey, // Raydium program ID
Pubkey, // Raydium authority
// Raydium pools
Pubkey, // Raydium AMM account
Pubkey, // Raydium token X vault
Pubkey, // Raydium SOL vault
)>,
Vec<(
Pubkey, // Raydium CP program ID
Pubkey, // Raydium CP authority
// Raydium CP pools
Pubkey, // Raydium CP pool
Pubkey, // Raydium CP amm config
Pubkey, // Raydium CP token X vault
Pubkey, // Raydium CP SOL vault
Pubkey, // Raydium CP observation
)>,
Vec<(
Pubkey, // Pump program ID
Pubkey, // Pump global config
Pubkey, // Pump authority
Pubkey, // Pump fee wallet
// Pump pools
Pubkey, // Pump pool
Pubkey, // Pump token X account
Pubkey, // Pump SOL account
Pubkey, // Pump fee token wallet
Pubkey, // Pump coin creator fee ata
Pubkey, // Pump coin creator vault authority
)>,
Vec<(
Pubkey, // DLMM program ID
Pubkey, // DLMM event authority
// DLMM pairs
Pubkey, // DLMM pair account
Pubkey, // DLMM token X vault
Pubkey, // DLMM SOL vault
Pubkey, // DLMM oracle
Vec<AccountMeta>, // Bin array accounts
)>,
Vec<(
Pubkey, // Whirlpool program ID
// Whirlpool pools
Pubkey, // Whirlpool pool
Pubkey, // Whirlpool oracle
Pubkey, // Whirlpool token X vault
Pubkey, // Whirlpool token SOL vault
Vec<AccountMeta>, // Whirlpool tick array accounts
)>,
Vec<(
Pubkey, // Raydium CLMM program ID
// Raydium CLMM pools
Pubkey, // Raydium CLMM pool
Pubkey, // Raydium CLMM amm config
Pubkey, // Raydium CLMM observation state
Pubkey, // Raydium CLMM bitmap extension
Pubkey, // Raydium CLMM token X vault
Pubkey, // Raydium CLMM token SOL vault
Vec<AccountMeta>, // Raydium CLMM tick array accounts
)>,
Vec<(
Pubkey, // Meteora program ID
Pubkey, // Meteora vault program ID
// Meteora pools
Pubkey, // Meteora pool
Pubkey, // Meteora pool token X vault
Pubkey, // Meteora pool token SOL vault
Pubkey, // Meteora pool token X token vault
Pubkey, // Meteora pool token SOL token vault
Pubkey, // Meteora pool token X lp mint
Pubkey, // Meteora pool token SOL lp mint
Pubkey, // Meteora pool token X pool lp
Pubkey, // Meteora pool token SOL pool lp
Pubkey, // Meteora pool admin token fee X
Pubkey, // Meteora pool admin token fee SOL
)>,
Vec<(
Pubkey, // Solfi program ID
Pubkey, // Sysvar instructions
// Solfi pools
Pubkey, // Solfi pool
Pubkey, // Solfi token X vault
Pubkey, // Solfi token SOL vault
)>,
Vec<(
Pubkey, // DAMM V2 program ID
Pubkey, // DAMM V2 event authority
Pubkey, // DAMM V2 pool authority
// DAMM V2 pools
Pubkey, // DAMM V2 pool
Pubkey, // DAMM V2 token X vault
Pubkey, // DAMM V2 token SOL vault
)>,
)],
program_id: &Pubkey,
minimum_profit: u64,
compute_unit_limit: u64,
no_failure_mode: bool,
use_flashloan: bool,
) -> Instruction {
// Create the list of accounts required for the instruction
let fee_collector = get_associated_token_address(
&Pubkey::from_str("FeeYFTEyHEZUw5dF8n2ByqitHSnqF7HJUF4x1ZyHCieA").unwrap(),
base_mint,
);
let mut accounts = vec![
// Fixed accounts (0-6)
AccountMeta::new_readonly(*wallet, true), // 0. Wallet (signer)
AccountMeta::new_readonly(*base_mint, false), // 1. SOL mint
AccountMeta::new(fee_collector, false), // 2. Fee collector
AccountMeta::new(*wallet_base_account, false), // 3. Wallet SOL account
AccountMeta::new_readonly(*token_program, false), // 4. Token program
AccountMeta::new_readonly(*system_program, false), // 5. System program
AccountMeta::new_readonly(*associated_token_program, false), // 6. Associated Token program
];

if use_flashloan {
accounts.push(AccountMeta::new_readonly(
Pubkey::from_str("DvaCNgCN8sbNtAN5sgUSEjWkY6Mt7JLjaYFV7MzPVPnM").unwrap(),
false,
));
let token_pda = derive_vault_token_account(
&Pubkey::from_str("KoyGzrKNQhzorDEsYwcRSy8AWVkeu9Zvu8quu4WK24M").unwrap(),
base_mint,
);
accounts.push(AccountMeta::new(token_pda.0, false));
}

// Add accounts for each token X and its pools
for (
x_mint,
wallet_x_account,
raydium_pools,
raydium_cp_pools,
pump_pools,
dlmm_pairs,
whirlpool_pools,
raydium_clmm_pools,
meteora_pools,
solfi_pools,
dammv2_pools,
) in token_pools
{
// Add token X mint and wallet account
accounts.push(AccountMeta::new_readonly(*x_mint, false));
accounts.push(AccountMeta::new(*wallet_x_account, false));

// Add Raydium pools
for (program_id, authority, amm, x_vault, sol_vault) in raydium_pools.iter() {
accounts.push(AccountMeta::new_readonly(*program_id, false));
accounts.push(AccountMeta::new_readonly(*authority, false));
accounts.push(AccountMeta::new(*amm, false));
accounts.push(AccountMeta::new(*x_vault, false));
accounts.push(AccountMeta::new(*sol_vault, false));
}

// Add Raydium CP pools
for (program_id, authority, pool, amm_config, x_vault, sol_vault, observation) in
raydium_cp_pools.iter()
{
accounts.push(AccountMeta::new_readonly(*program_id, false));
accounts.push(AccountMeta::new_readonly(*authority, false));
accounts.push(AccountMeta::new(*pool, false));
accounts.push(AccountMeta::new_readonly(*amm_config, false));
accounts.push(AccountMeta::new(*x_vault, false));
accounts.push(AccountMeta::new(*sol_vault, false));
accounts.push(AccountMeta::new(*observation, false));
}

// Add Pump pools
for (
program_id,
global_config,
authority,
fee_wallet,
pool,
x_account,
sol_account,
fee_token_wallet,
coin_creator_fee_ata,
coin_creator_vault_authority,
) in pump_pools.iter()
{
accounts.push(AccountMeta::new_readonly(*program_id, false));
accounts.push(AccountMeta::new_readonly(*global_config, false));
accounts.push(AccountMeta::new_readonly(*authority, false));
accounts.push(AccountMeta::new_readonly(*fee_wallet, false));
accounts.push(AccountMeta::new_readonly(*pool, false));
accounts.push(AccountMeta::new(*x_account, false));
accounts.push(AccountMeta::new(*sol_account, false));
accounts.push(AccountMeta::new(*fee_token_wallet, false));
accounts.push(AccountMeta::new(*coin_creator_fee_ata, false));
accounts.push(AccountMeta::new(*coin_creator_vault_authority, false));
}

// Add DLMM pairs
for (program_id, event_authority, pair, x_vault, sol_vault, oracle, bin_arrays) in
dlmm_pairs.iter()
{
accounts.push(AccountMeta::new_readonly(*program_id, false));
accounts.push(AccountMeta::new_readonly(*event_authority, false));
accounts.push(AccountMeta::new(*pair, false));
accounts.push(AccountMeta::new(*x_vault, false));
accounts.push(AccountMeta::new(*sol_vault, false));
accounts.push(AccountMeta::new(*oracle, false));
accounts.extend(bin_arrays.clone());
}

// Add Whirlpool pools
for (program_id, pool, oracle, x_vault, y_vault, tick_arrays) in whirlpool_pools.iter() {
accounts.push(AccountMeta::new_readonly(*program_id, false));
accounts.push(AccountMeta::new(*pool, false));
accounts.push(AccountMeta::new(*oracle, false));
accounts.push(AccountMeta::new(*x_vault, false));
accounts.push(AccountMeta::new(*y_vault, false));
accounts.extend(tick_arrays.clone());
}

// Add Raydium CLMM pools
for (
program_id,
pool,
amm_config,
observation_state,
bitmap_extension,
x_vault,
y_vault,
tick_arrays,
) in raydium_clmm_pools.iter()
{
accounts.push(AccountMeta::new_readonly(*program_id, false));
accounts.push(AccountMeta::new(*pool, false));
accounts.push(AccountMeta::new_readonly(*amm_config, false));
accounts.push(AccountMeta::new(*observation_state, false));
accounts.push(AccountMeta::new(*bitmap_extension, false));
accounts.push(AccountMeta::new(*x_vault, false));
accounts.push(AccountMeta::new(*y_vault, false));
accounts.extend(tick_arrays.clone());
}

// Add Meteora pools
for (
program_id,
vault_program_id,
pool,
x_vault,
sol_vault,
x_token_vault,
sol_token_vault,
x_lp_mint,
sol_lp_mint,
x_pool_lp,
sol_pool_lp,
x_admin_fee,
sol_admin_fee,
) in meteora_pools.iter()
{
accounts.push(AccountMeta::new_readonly(*program_id, false));
accounts.push(AccountMeta::new_readonly(*vault_program_id, false));
accounts.push(AccountMeta::new(*pool, false));
accounts.push(AccountMeta::new(*x_vault, false));
accounts.push(AccountMeta::new(*sol_vault, false));
accounts.push(AccountMeta::new(*x_token_vault, false));
accounts.push(AccountMeta::new(*sol_token_vault, false));
accounts.push(AccountMeta::new(*x_lp_mint, false));
accounts.push(AccountMeta::new(*sol_lp_mint, false));
accounts.push(AccountMeta::new(*x_pool_lp, false));
accounts.push(AccountMeta::new(*sol_pool_lp, false));
accounts.push(AccountMeta::new(*x_admin_fee, false));
accounts.push(AccountMeta::new(*sol_admin_fee, false));
}

// Add Solfi pools
for (program_id, sysvar_instructions, pool, x_vault, sol_vault) in solfi_pools.iter() {
accounts.push(AccountMeta::new_readonly(*program_id, false));
accounts.push(AccountMeta::new_readonly(*sysvar_instructions, false));
accounts.push(AccountMeta::new(*pool, false));
accounts.push(AccountMeta::new(*x_vault, false));
accounts.push(AccountMeta::new(*sol_vault, false));
}

// Add DAMM V2 pools
for (program_id, event_authority, pool_authority, pool, x_vault, sol_vault) in
dammv2_pools.iter()
{
accounts.push(AccountMeta::new_readonly(*program_id, false));
accounts.push(AccountMeta::new_readonly(*event_authority, false));
accounts.push(AccountMeta::new_readonly(*pool_authority, false));
accounts.push(AccountMeta::new(*pool, false));
accounts.push(AccountMeta::new(*x_vault, false));
accounts.push(AccountMeta::new(*sol_vault, false));
}
}

// Create instruction data
let mut data = vec![16u8];
data.extend_from_slice(&minimum_profit.to_le_bytes());
data.extend_from_slice(&compute_unit_limit.to_le_bytes());
data.extend_from_slice(if no_failure_mode { &[1] } else { &[0] });
data.extend_from_slice(&0u16.to_le_bytes()); // Keep this 0.
data.extend_from_slice(if use_flashloan { &[1] } else { &[0] });

Instruction {
program_id: *program_id,
accounts,
data,
}
}

pub fn derive_vault_token_account(program_id: &Pubkey, mint: &Pubkey) -> (Pubkey, u8) {
Pubkey::find_program_address(&[b"vault_token_account", mint.as_ref()], program_id)
}

Additional Fee Configuration

The on-chain program supports charging additional fees on top of the standard bot fee. This feature allows you to build customized bots and charge your users directly.

Implementation Requirements

To implement additional fees, you need to modify two components when constructing the instruction:

1. Add Additional Fee Account

Include your additional fee account in the accounts list. Important: This account must use the same token mint as the base mint (e.g., if base mint is WSOL, your additional fee account must also be a WSOL account).

2. Set Additional Fee Basis Points

Configure the additional_fee_bp parameter using basis points:

  • 500 basis points = 5% fee
  • Example: With 500 bp, you receive 5% of profit, the on-chain program takes 15%, and the user gets 80%

Code Example

let additional_fee_collector = Pubkey::from_str("YOUR_FEE_ACCOUNT_ADDRESS").unwrap();
let mut accounts = vec![
// Fixed accounts (0-6)
AccountMeta::new_readonly(*wallet, true), // 0. Wallet (signer)
AccountMeta::new_readonly(*base_mint, false), // 1. SOL mint
AccountMeta::new(fee_collector, false), // 2. Fee collector
AccountMeta::new(additional_fee_collector, false), // 2.5 Additional fee account
AccountMeta::new(*wallet_base_account, false), // 3. Wallet SOL account
AccountMeta::new_readonly(*token_program, false), // 4. Token program
AccountMeta::new_readonly(*system_program, false), // 5. System program
AccountMeta::new_readonly(*associated_token_program, false), // 6. Associated Token program
];

// ... rest of account setup ...

// Create instruction data with additional fee
let mut data = vec![15u8];
data.extend_from_slice(&minimum_profit.to_le_bytes());
data.extend_from_slice(&max_bin_to_process.to_le_bytes());
data.extend_from_slice(if no_failure_mode { &[1] } else { &[0] });

// Example: 5% additional fee (500 basis points)
let additional_fee_bp = 500u16;
data.extend_from_slice(&additional_fee_bp.to_le_bytes());

This configuration enables you to monetize your bot while providing arbitrage services to your users.