1use anyhow::{anyhow, Result};
5use serde::{Deserialize, Serialize};
6use std::collections::HashMap;
7
8pub const ORCA_CONFIGS_API: &str = "https://api.orca.so/configs";
9
10#[derive(Clone, Debug, Serialize, Deserialize)]
12#[serde(rename_all = "camelCase")]
13pub struct OrcaConfigsApiResponse {
14 pub aquafarms: HashMap<String, AquaFarm>,
15 pub collectibles: HashMap<String, Collectible>,
16 pub double_dips: HashMap<String, DoubleDip>,
17 pub pools: HashMap<String, Pool>,
18 pub program_ids: ProgramIds,
19 pub tokens: HashMap<String, Token>,
20 pub coingecko_ids: HashMap<String, String>,
21 pub ftx_ids: HashMap<String, String>,
22}
23
24#[derive(Clone, Debug, Serialize, Deserialize)]
25#[serde(rename_all = "camelCase")]
26pub struct AquaFarm {
27 pub account: String,
28 pub nonce: u8,
29 pub token_program_id: String,
30 pub emissions_authority: String,
31 pub remove_rewards_authority: String,
32 pub base_token_mint: String,
33 pub base_token_vault: String,
34 pub reward_token_mint: String,
35 pub reward_token_vault: String,
36 pub farm_token_mint: String,
37}
38
39#[derive(Clone, Debug, Serialize, Deserialize)]
40#[serde(rename_all = "camelCase")]
41pub struct DoubleDip {
42 pub account: String,
43 pub nonce: u8,
44 pub token_program_id: String,
45 pub emissions_authority: String,
46 pub remove_rewards_authority: String,
47 pub base_token_mint: String,
48 pub base_token_vault: String,
49 pub reward_token_mint: String,
50 pub reward_token_vault: String,
51 pub farm_token_mint: String,
52 pub date_start: String,
53 pub date_end: String,
54 pub total_emissions: String,
55 pub custom_gradient_start_color: String,
56}
57
58#[derive(Clone, Debug, Serialize, Deserialize)]
59#[serde(rename_all = "camelCase")]
60pub struct Collectible {
61 pub mint: String,
62 pub decimals: u8,
63}
64
65#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
66#[serde(rename_all = "camelCase")]
67pub struct Pool {
68 pub account: String,
69 pub authority: String,
70 pub nonce: u8,
71 pub pool_token_mint: String,
72 pub token_account_a: String,
73 pub token_account_b: String,
74 pub fee_account: String,
75 pub fee_numerator: u64,
76 pub fee_denominator: u64,
77 pub owner_trade_fee_numerator: u64,
78 pub owner_trade_fee_denominator: u64,
79 pub owner_withdraw_fee_numerator: u64,
80 pub host_fee_numerator: u64,
81 pub token_a_name: String,
82 pub token_b_name: String,
83 pub curve_type: String,
84 pub deprecated: Option<bool>,
85 pub program_version: Option<u64>,
86}
87
88#[derive(Clone, Debug, Serialize, Deserialize)]
89#[serde(rename_all = "camelCase")]
90pub struct ProgramIds {
91 pub serum_token_swap: String,
92 pub token_swap_v2: String,
93 pub token_swap: String,
94 pub token: String,
95 pub aquafarm: String,
96}
97
98#[derive(Clone, Debug, Serialize, Deserialize)]
99#[serde(rename_all = "camelCase")]
100pub struct Token {
101 pub mint: String,
102 pub name: String,
103 pub decimals: u8,
104 pub fetch_price: Option<bool>,
105}
106
107impl OrcaConfigsApiResponse {
108 pub async fn fetch_orca_config() -> Result<Self> {
109 let client = reqwest::Client::builder().build()?;
110 let res = client.get(ORCA_CONFIGS_API).send().await?;
111 let data = res.json::<Self>().await?;
112 Ok(data)
113 }
114 pub fn find_pool(&self, name: &str, stable: bool, aquafarm: bool) -> Result<Pool> {
119 let name = format_orca_amm_name(name, stable, aquafarm);
120 for pool in self.pools.iter() {
121 if pool.0.eq(&name) {
122 return Ok(pool.1.clone());
123 }
124 }
125 Err(anyhow!("failed to find pool for {}", name))
126 }
127 pub fn find_aquafarm(&self, name: &str, stable: bool) -> Result<(Pool, AquaFarm)> {
130 let pool = self.find_pool(name, stable, true)?;
131 for farm in self.aquafarms.iter() {
132 if farm.0.eq(&pool.account) {
133 return Ok((pool, farm.1.clone()));
134 }
135 }
136 Err(anyhow!("failed to find aquafarm for {}", name))
137 }
138 pub fn find_double_dip(&self, name: &str, stable: bool) -> Result<(Pool, DoubleDip, AquaFarm)> {
140 let pool = self.find_pool(name, stable, true)?;
141 for doubledip in self.double_dips.iter() {
142 if doubledip.0.eq(&pool.account) {
143 for aquafarm in self.aquafarms.iter() {
144 if aquafarm.0.eq(&pool.account) {
145 return Ok((pool, doubledip.1.clone(), aquafarm.1.clone()));
146 }
147 }
148 }
149 }
150 Err(anyhow!("failed to find doubledip for {}", name))
151 }
152}
153
154pub fn format_orca_amm_name(name: &str, stable: bool, aquafarm: bool) -> String {
155 let name = if name.split('-').count() == 3 {
158 let lp_name_str = name.to_string();
159 let parts: Vec<_> = lp_name_str.split('-').collect();
160 let mut lp_name_parsed = String::with_capacity(name.len() - 5); for (idx, part) in parts.iter().enumerate() {
162 if idx == parts.len() - 1 {
163 break;
164 }
165 lp_name_parsed.push_str(*part);
166 if idx != parts.len() - 2 {
167 lp_name_parsed.push('/');
168 }
169 }
170 lp_name_parsed
171 } else {
172 name.replace("-", "/")
173 };
174 let name = name.replace("SOCN", "scnSOL");
177 let name = if stable && !name.contains("[stable]") {
178 format!("{}[stable]", name)
179 } else {
180 name
181 };
182 let name = if aquafarm && !name.contains("[aquafarm]") {
183 format!("{}[aquafarm]", name)
184 } else {
185 name
186 };
187 name
188}
189
190#[cfg(test)]
191mod test {
192 use super::*;
193 #[test]
194 fn test_format_orca_amm_name() {
195 let name_one = "SAMO-USDC[stable][aquafarm]".to_string();
196 let name_two = "SAMO-USDC".to_string();
197 assert_eq!(
198 format_orca_amm_name(&name_one, true, true),
199 "SAMO/USDC[stable][aquafarm]"
200 );
201 assert_eq!(
202 format_orca_amm_name(&name_two, false, true),
203 "SAMO/USDC[aquafarm]"
204 );
205 assert_eq!(
206 format_orca_amm_name(&name_two, true, false),
207 "SAMO/USDC[stable]"
208 );
209 }
210 #[tokio::test]
211 async fn test_orca_config() {
212 let orca_config = OrcaConfigsApiResponse::fetch_orca_config().await.unwrap();
213
214 let pool_config = orca_config
215 .find_pool(&"SOL/USDC".to_string(), false, false)
216 .unwrap();
217 assert_eq!(
218 pool_config.account,
219 "6fTRDD7sYxCN7oyoSQaN1AWC3P2m8A6gVZzGrpej9DvL".to_string()
220 );
221
222 let pool_config = orca_config
223 .find_pool(&"SOL/USDC".to_string(), false, true)
224 .unwrap();
225 assert_eq!(
226 pool_config.account,
227 "EGZ7tiLeH62TPV1gL8WwbXGzEPa9zmcpVnnkPKKnrE2U".to_string()
228 );
229
230 let aquafarm_config = orca_config
231 .find_aquafarm(&"SOL/USDC".to_string(), false)
232 .unwrap();
233 assert_eq!(aquafarm_config.0, pool_config);
234 println!(
235 "sol/usdc aquafarm information\npool {:#?}\nfarm {:#?}",
236 aquafarm_config.0, aquafarm_config.1
237 );
238
239 let pool_config = orca_config
240 .find_pool(&"LIQ/USDC".to_string(), false, true)
241 .unwrap();
242 let doubledip_config = orca_config
243 .find_double_dip(&"LIQ/USDC".to_string(), false)
244 .unwrap();
245 assert_eq!(doubledip_config.0, pool_config);
246 println!(
247 "liq/usdc doubledip information\npool {:#?}\nfarm {:#?}\ndouble dip {:#?}",
248 doubledip_config.0, doubledip_config.1, doubledip_config.0,
249 );
250 }
251}