1use crate::SwapInstructionBuilder;
2use anyhow::{anyhow, Result};
3use async_trait::async_trait;
4use scematica_core::{dex::program_ids, types::DexKind};
5use solana_client::nonblocking::rpc_client::RpcClient;
6use solana_sdk::{
7 instruction::{AccountMeta, Instruction},
8 pubkey::Pubkey,
9};
10use std::sync::Arc;
11
12pub struct MeteoraBuilder {
13 pub rpc: Arc<RpcClient>,
14}
15
16impl MeteoraBuilder {
17 pub fn new(rpc: Arc<RpcClient>) -> Self {
18 Self { rpc }
19 }
20}
21
22const METEORA_SWAP_DISCRIMINATOR: [u8; 8] = [0xf8, 0xc6, 0x9e, 0x91, 0xe1, 0x75, 0x27, 0x43];
25
26const DLMM_MIN_DATA_LEN: usize = 834;
29
30mod offsets {
33 pub const ACTIVE_ID: usize = 76; pub const BIN_STEP: usize = 80; pub const TOKEN_X_MINT: usize = 88; pub const TOKEN_Y_MINT: usize = 120; pub const RESERVE_X: usize = 152; pub const RESERVE_Y: usize = 184; pub const ORACLE: usize = 488; pub const BIN_ARRAY_BITMAP_EXTENSION: usize = 802; }
42
43fn derive_bin_array_pda(pool: &Pubkey, index: i32, program_id: &Pubkey) -> Result<Pubkey> {
45 let index_bytes = index.to_le_bytes();
46 let (pda, _) = Pubkey::find_program_address(
47 &[b"bin_array", pool.as_ref(), &index_bytes],
48 program_id,
49 );
50 Ok(pda)
51}
52fn read_pubkey(data: &[u8], offset: usize) -> Result<Pubkey> {
53 Pubkey::try_from(&data[offset..offset + 32])
54 .map_err(|_| anyhow!("failed to read Pubkey at offset {}", offset))
55}
56
57fn read_i32(data: &[u8], offset: usize) -> Result<i32> {
59 let bytes: [u8; 4] = data[offset..offset + 4]
60 .try_into()
61 .map_err(|_| anyhow!("failed to read i32 at offset {}", offset))?;
62 Ok(i32::from_le_bytes(bytes))
63}
64
65fn read_u16(data: &[u8], offset: usize) -> Result<u16> {
67 let bytes: [u8; 2] = data[offset..offset + 2]
68 .try_into()
69 .map_err(|_| anyhow!("failed to read u16 at offset {}", offset))?;
70 Ok(u16::from_le_bytes(bytes))
71}
72
73#[async_trait]
74impl SwapInstructionBuilder for MeteoraBuilder {
75 fn dex(&self) -> DexKind {
76 DexKind::Meteora
77 }
78
79 async fn build_swap(
80 &self,
81 pool: &Pubkey,
82 owner: &Pubkey,
83 token_in: &Pubkey,
84 _token_out: &Pubkey,
85 ata_in: &Pubkey,
86 ata_out: &Pubkey,
87 amount_in: u64,
88 min_amount_out: u64,
89 ) -> Result<Vec<Instruction>> {
90 let data = self
92 .rpc
93 .get_account_data(pool)
94 .await
95 .map_err(|e| anyhow!("RPC error fetching DLMM pool {}: {}", pool, e))?;
96
97 if data.len() < DLMM_MIN_DATA_LEN {
99 return Err(anyhow!(
100 "DLMM pool data too short: {} < {} bytes",
101 data.len(),
102 DLMM_MIN_DATA_LEN
103 ));
104 }
105
106 let active_id = read_i32(&data, offsets::ACTIVE_ID)?;
109 let _bin_step = read_u16(&data, offsets::BIN_STEP)?;
110 let token_x_mint = read_pubkey(&data, offsets::TOKEN_X_MINT)?;
111 let token_y_mint = read_pubkey(&data, offsets::TOKEN_Y_MINT)?;
112 let reserve_x = read_pubkey(&data, offsets::RESERVE_X)?;
113 let reserve_y = read_pubkey(&data, offsets::RESERVE_Y)?;
114 let oracle = read_pubkey(&data, offsets::ORACLE)?;
115 let bin_array_bitmap_extension = read_pubkey(&data, offsets::BIN_ARRAY_BITMAP_EXTENSION)?;
116
117 let swap_for_y = if token_in == &token_x_mint {
119 true
120 } else if token_in == &token_y_mint {
121 false
122 } else {
123 return Err(anyhow!(
124 "token_in {} matches neither token_x_mint {} nor token_y_mint {} for DLMM pool {}",
125 token_in,
126 token_x_mint,
127 token_y_mint,
128 pool
129 ));
130 };
131
132 const BIN_ARRAY_SIZE: i32 = 70;
136 let lower_idx = active_id.div_euclid(BIN_ARRAY_SIZE);
137 let upper_idx = if swap_for_y { lower_idx - 1 } else { lower_idx + 1 };
138
139 let bin_array_lower = derive_bin_array_pda(pool, lower_idx, &program_ids::METEORA_DLMM)?;
140 let bin_array_upper = derive_bin_array_pda(pool, upper_idx, &program_ids::METEORA_DLMM)?;
141
142 let mut ix_data = METEORA_SWAP_DISCRIMINATOR.to_vec();
148 ix_data.extend_from_slice(&amount_in.to_le_bytes());
149 ix_data.extend_from_slice(&min_amount_out.to_le_bytes());
150 ix_data.push(swap_for_y as u8);
151
152 let accounts = vec![
168 AccountMeta::new(*pool, false),
169 AccountMeta::new(bin_array_bitmap_extension, false),
170 AccountMeta::new(reserve_x, false),
171 AccountMeta::new(reserve_y, false),
172 AccountMeta::new(*ata_in, false),
173 AccountMeta::new(*ata_out, false),
174 AccountMeta::new_readonly(token_x_mint, false),
175 AccountMeta::new_readonly(token_y_mint, false),
176 AccountMeta::new_readonly(oracle, false),
177 AccountMeta::new(bin_array_lower, false),
178 AccountMeta::new(bin_array_upper, false),
179 AccountMeta::new_readonly(*owner, true),
180 AccountMeta::new_readonly(spl_token::id(), false),
181 AccountMeta::new_readonly(spl_token::id(), false),
182 ];
183
184 Ok(vec![Instruction {
185 program_id: program_ids::METEORA_DLMM,
186 accounts,
187 data: ix_data,
188 }])
189 }
190}