1use std::ops::Deref;
2
3use gmsol_solana_utils::{
4 client_traits::FromRpcClientWith,
5 make_bundle_builder::{MakeBundleBuilder, SetExecutionFee},
6 transaction_builder::TransactionBuilder,
7 IntoAtomicGroup,
8};
9use gmsol_utils::oracle::PriceProviderKind;
10use solana_sdk::{pubkey::Pubkey, signer::Signer};
11
12use crate::{
13 builders::{
14 liquidity_provider::{
15 AcceptAuthority, ClaimGtReward, CreateLpTokenController, DisableLpTokenController,
16 GtRewardCalculationParams, InitializeLp, LpPositionQueryParams, SetClaimEnabled,
17 SetPricingStaleness, StakeLpToken, StakeLpTokenHint, StakeLpTokenParams,
18 TransferAuthority, UnstakeLpToken, UnstakeLpTokenParams, UpdateApyGradientRange,
19 UpdateApyGradientSparse, UpdateMinStakeValue,
20 },
21 StoreProgram,
22 },
23 client::pull_oracle::{FeedIds, PullOraclePriceConsumer},
24 utils::token_map::FeedAddressMap,
25};
26
27const STAKE_LP_TOKEN_COMPUTE_BUDGET: u32 = 800_000;
33
34pub trait LiquidityProviderOps<C> {
36 fn initialize_lp(
38 &self,
39 min_stake_value: u128,
40 initial_apy: u128,
41 ) -> crate::Result<TransactionBuilder<'_, C>>;
42
43 fn create_lp_token_controller(
45 &self,
46 lp_token_mint: &Pubkey,
47 controller_index: u64,
48 ) -> crate::Result<TransactionBuilder<'_, C>>;
49
50 fn disable_lp_token_controller(
52 &self,
53 store: &Pubkey,
54 lp_token_mint: &Pubkey,
55 controller_index: u64,
56 controller_address: Option<Pubkey>,
57 ) -> crate::Result<TransactionBuilder<'_, C>>;
58
59 fn unstake_lp_token(
61 &self,
62 params: UnstakeLpTokenParams<'_>,
63 ) -> crate::Result<TransactionBuilder<'_, C>>;
64
65 fn stake_lp_token(&self, params: StakeLpTokenParams<'_>) -> StakeLpTokenBuilder<'_, C>;
67
68 fn calculate_gt_reward(
70 &self,
71 params: GtRewardCalculationParams<'_>,
72 ) -> impl std::future::Future<Output = crate::Result<u128>>;
73
74 fn claim_gt_reward(
76 &self,
77 store: &Pubkey,
78 lp_token_mint: &Pubkey,
79 position_id: u64,
80 controller_index: u64,
81 controller_address: Option<Pubkey>,
82 ) -> crate::Result<TransactionBuilder<'_, C>>;
83
84 fn transfer_lp_authority(
86 &self,
87 new_authority: &Pubkey,
88 ) -> crate::Result<TransactionBuilder<'_, C>>;
89
90 fn accept_lp_authority(&self) -> crate::Result<TransactionBuilder<'_, C>>;
92
93 fn set_claim_enabled(&self, enabled: bool) -> crate::Result<TransactionBuilder<'_, C>>;
95
96 fn set_pricing_staleness(
98 &self,
99 staleness_seconds: u32,
100 ) -> crate::Result<TransactionBuilder<'_, C>>;
101
102 fn update_apy_gradient_sparse(
104 &self,
105 bucket_indices: Vec<u8>,
106 apy_values: Vec<u128>,
107 ) -> crate::Result<TransactionBuilder<'_, C>>;
108
109 fn update_apy_gradient_range(
111 &self,
112 start_bucket: u8,
113 end_bucket: u8,
114 apy_values: Vec<u128>,
115 ) -> crate::Result<TransactionBuilder<'_, C>>;
116
117 fn update_min_stake_value(
119 &self,
120 new_min_stake_value: u128,
121 ) -> crate::Result<TransactionBuilder<'_, C>>;
122
123 fn get_lp_positions(
125 &self,
126 store: &Pubkey,
127 owner: &Pubkey,
128 ) -> impl std::future::Future<
129 Output = crate::Result<Vec<crate::serde::serde_lp_position::SerdeLpStakingPosition>>,
130 >;
131
132 fn get_lp_position(
134 &self,
135 params: LpPositionQueryParams<'_>,
136 ) -> impl std::future::Future<
137 Output = crate::Result<Option<crate::serde::serde_lp_position::SerdeLpStakingPosition>>,
138 >;
139
140 fn get_my_lp_positions(
142 &self,
143 store: &Pubkey,
144 ) -> impl std::future::Future<
145 Output = crate::Result<Vec<crate::serde::serde_lp_position::SerdeLpStakingPosition>>,
146 >;
147
148 fn get_lp_controllers(
150 &self,
151 lp_token_mint: &Pubkey,
152 ) -> impl std::future::Future<
153 Output = crate::Result<Vec<crate::serde::serde_lp_controller::SerdeLpController>>,
154 >;
155
156 fn get_all_lp_controllers(
158 &self,
159 ) -> impl std::future::Future<
160 Output = crate::Result<Vec<crate::serde::serde_lp_controller::SerdeLpController>>,
161 >;
162
163 fn get_lp_global_state(
165 &self,
166 ) -> impl std::future::Future<
167 Output = crate::Result<crate::serde::serde_lp_global_state::SerdeLpGlobalState>,
168 >;
169}
170
171impl<C: Deref<Target = impl Signer> + Clone> LiquidityProviderOps<C> for crate::Client<C> {
172 fn initialize_lp(
173 &self,
174 min_stake_value: u128,
175 initial_apy: u128,
176 ) -> crate::Result<TransactionBuilder<'_, C>> {
177 let builder = InitializeLp::builder()
178 .payer(self.payer())
179 .lp_program(self.lp_program_for_builders().clone())
180 .min_stake_value(min_stake_value)
181 .initial_apy(initial_apy)
182 .build();
183
184 let ag = builder.into_atomic_group(&())?;
185 Ok(self.store_transaction().pre_atomic_group(ag, true))
186 }
187
188 fn create_lp_token_controller(
189 &self,
190 lp_token_mint: &Pubkey,
191 controller_index: u64,
192 ) -> crate::Result<TransactionBuilder<'_, C>> {
193 let builder = CreateLpTokenController::builder()
194 .authority(self.payer())
195 .lp_program(self.lp_program_for_builders().clone())
196 .lp_token_mint(*lp_token_mint)
197 .controller_index(controller_index)
198 .build();
199
200 let ag = builder.into_atomic_group(&())?;
201 Ok(self.store_transaction().pre_atomic_group(ag, true))
202 }
203
204 fn disable_lp_token_controller(
205 &self,
206 store: &Pubkey,
207 lp_token_mint: &Pubkey,
208 controller_index: u64,
209 controller_address: Option<Pubkey>,
210 ) -> crate::Result<TransactionBuilder<'_, C>> {
211 let builder = DisableLpTokenController::builder()
212 .authority(self.payer())
213 .store_program(self.store_program_for_builders(store))
214 .lp_program(self.lp_program_for_builders().clone())
215 .lp_token_mint(*lp_token_mint)
216 .controller_index(controller_index)
217 .controller_address(controller_address.map(|addr| addr.into()))
218 .build();
219
220 let ag = builder.into_atomic_group(&())?;
221 Ok(self.store_transaction().pre_atomic_group(ag, true))
222 }
223
224 fn unstake_lp_token(
225 &self,
226 params: UnstakeLpTokenParams<'_>,
227 ) -> crate::Result<TransactionBuilder<'_, C>> {
228 let builder = UnstakeLpToken::builder()
229 .payer(self.payer())
230 .store_program(self.store_program_for_builders(params.store))
231 .lp_program(self.lp_program_for_builders().clone())
232 .lp_token_kind(params.lp_token_kind)
233 .lp_token_mint(*params.lp_token_mint)
234 .position_id(params.position_id)
235 .unstake_amount(params.unstake_amount)
236 .controller_index(params.controller_index)
237 .controller_address(params.controller_address.map(|addr| addr.into()))
238 .build();
239
240 let ag = builder.into_atomic_group(&())?;
241 Ok(self.store_transaction().pre_atomic_group(ag, true))
242 }
243
244 fn stake_lp_token(&self, params: StakeLpTokenParams<'_>) -> StakeLpTokenBuilder<'_, C> {
245 StakeLpTokenBuilder {
246 client: self,
247 builder: StakeLpToken::builder()
248 .payer(self.payer())
249 .amount(params.amount)
250 .oracle(*params.oracle)
251 .lp_token_kind(params.lp_token_kind)
252 .lp_token_mint(*params.lp_token_mint)
253 .lp_program(self.lp_program_for_builders().clone())
254 .store_program(
255 StoreProgram::builder()
256 .id(*self.store_program_id())
257 .store(*params.store)
258 .build(),
259 )
260 .controller_index(params.controller_index)
261 .controller_address(params.controller_address.map(|addr| addr.into()))
262 .build(),
263 hint: None,
264 }
265 }
266
267 async fn calculate_gt_reward(
268 &self,
269 params: GtRewardCalculationParams<'_>,
270 ) -> crate::Result<u128> {
271 let lp_program = self.lp_program_for_builders();
272
273 lp_program.calculate_gt_reward(self.rpc(), ¶ms).await
274 }
275
276 fn claim_gt_reward(
277 &self,
278 store: &Pubkey,
279 lp_token_mint: &Pubkey,
280 position_id: u64,
281 controller_index: u64,
282 controller_address: Option<Pubkey>,
283 ) -> crate::Result<TransactionBuilder<'_, C>> {
284 let builder = ClaimGtReward::builder()
285 .owner(self.payer())
286 .store_program(self.store_program_for_builders(store))
287 .lp_program(self.lp_program_for_builders().clone())
288 .lp_token_mint(*lp_token_mint)
289 .position_id(position_id)
290 .controller_index(controller_index)
291 .controller_address(controller_address.map(|addr| addr.into()))
292 .build();
293
294 let ag = builder.into_atomic_group(&())?;
295 Ok(self.store_transaction().pre_atomic_group(ag, true))
296 }
297
298 fn transfer_lp_authority(
299 &self,
300 new_authority: &Pubkey,
301 ) -> crate::Result<TransactionBuilder<'_, C>> {
302 let builder = TransferAuthority::builder()
303 .authority(self.payer())
304 .lp_program(self.lp_program_for_builders().clone())
305 .new_authority(*new_authority)
306 .build();
307
308 let ag = builder.into_atomic_group(&())?;
309 Ok(self.store_transaction().pre_atomic_group(ag, true))
310 }
311
312 fn accept_lp_authority(&self) -> crate::Result<TransactionBuilder<'_, C>> {
313 let builder = AcceptAuthority::builder()
314 .pending_authority(self.payer())
315 .lp_program(self.lp_program_for_builders().clone())
316 .build();
317
318 let ag = builder.into_atomic_group(&())?;
319 Ok(self.store_transaction().pre_atomic_group(ag, true))
320 }
321
322 fn set_claim_enabled(&self, enabled: bool) -> crate::Result<TransactionBuilder<'_, C>> {
323 let builder = SetClaimEnabled::builder()
324 .authority(self.payer())
325 .lp_program(self.lp_program_for_builders().clone())
326 .enabled(enabled)
327 .build();
328
329 let ag = builder.into_atomic_group(&())?;
330 Ok(self.store_transaction().pre_atomic_group(ag, true))
331 }
332
333 fn set_pricing_staleness(
334 &self,
335 staleness_seconds: u32,
336 ) -> crate::Result<TransactionBuilder<'_, C>> {
337 let builder = SetPricingStaleness::builder()
338 .authority(self.payer())
339 .lp_program(self.lp_program_for_builders().clone())
340 .staleness_seconds(staleness_seconds)
341 .build();
342
343 let ag = builder.into_atomic_group(&())?;
344 Ok(self.store_transaction().pre_atomic_group(ag, true))
345 }
346
347 fn update_apy_gradient_sparse(
348 &self,
349 bucket_indices: Vec<u8>,
350 apy_values: Vec<u128>,
351 ) -> crate::Result<TransactionBuilder<'_, C>> {
352 let builder = UpdateApyGradientSparse::builder()
353 .authority(self.payer())
354 .lp_program(self.lp_program_for_builders().clone())
355 .bucket_indices(bucket_indices)
356 .apy_values(apy_values)
357 .build();
358
359 let ag = builder.into_atomic_group(&())?;
360 Ok(self.store_transaction().pre_atomic_group(ag, true))
361 }
362
363 fn update_apy_gradient_range(
364 &self,
365 start_bucket: u8,
366 end_bucket: u8,
367 apy_values: Vec<u128>,
368 ) -> crate::Result<TransactionBuilder<'_, C>> {
369 let builder = UpdateApyGradientRange::builder()
370 .authority(self.payer())
371 .lp_program(self.lp_program_for_builders().clone())
372 .start_bucket(start_bucket)
373 .end_bucket(end_bucket)
374 .apy_values(apy_values)
375 .build();
376
377 let ag = builder.into_atomic_group(&())?;
378 Ok(self.store_transaction().pre_atomic_group(ag, true))
379 }
380
381 fn update_min_stake_value(
382 &self,
383 new_min_stake_value: u128,
384 ) -> crate::Result<TransactionBuilder<'_, C>> {
385 let builder = UpdateMinStakeValue::builder()
386 .authority(self.payer())
387 .lp_program(self.lp_program_for_builders().clone())
388 .new_min_stake_value(new_min_stake_value)
389 .build();
390
391 let ag = builder.into_atomic_group(&())?;
392 Ok(self.store_transaction().pre_atomic_group(ag, true))
393 }
394
395 async fn get_lp_positions(
396 &self,
397 store: &Pubkey,
398 owner: &Pubkey,
399 ) -> crate::Result<Vec<crate::serde::serde_lp_position::SerdeLpStakingPosition>> {
400 let lp_program = self.lp_program_for_builders();
401 lp_program
402 .query_lp_positions(self.rpc(), store, owner)
403 .await
404 }
405
406 async fn get_lp_position(
407 &self,
408 params: LpPositionQueryParams<'_>,
409 ) -> crate::Result<Option<crate::serde::serde_lp_position::SerdeLpStakingPosition>> {
410 let lp_program = self.lp_program_for_builders();
411
412 lp_program.query_lp_position(self.rpc(), ¶ms).await
413 }
414
415 async fn get_my_lp_positions(
416 &self,
417 store: &Pubkey,
418 ) -> crate::Result<Vec<crate::serde::serde_lp_position::SerdeLpStakingPosition>> {
419 self.get_lp_positions(store, &self.payer()).await
420 }
421
422 async fn get_lp_controllers(
423 &self,
424 lp_token_mint: &Pubkey,
425 ) -> crate::Result<Vec<crate::serde::serde_lp_controller::SerdeLpController>> {
426 let lp_program = self.lp_program_for_builders();
427 lp_program
428 .query_lp_controllers(self.rpc(), lp_token_mint)
429 .await
430 }
431
432 async fn get_all_lp_controllers(
433 &self,
434 ) -> crate::Result<Vec<crate::serde::serde_lp_controller::SerdeLpController>> {
435 let lp_program = self.lp_program_for_builders();
436 lp_program.query_all_lp_controllers(self.rpc()).await
437 }
438
439 async fn get_lp_global_state(
440 &self,
441 ) -> crate::Result<crate::serde::serde_lp_global_state::SerdeLpGlobalState> {
442 let lp_program = self.lp_program_for_builders();
443 lp_program.query_lp_global_state(self.rpc()).await
444 }
445}
446
447pub struct StakeLpTokenBuilder<'a, C> {
449 client: &'a crate::Client<C>,
450 builder: StakeLpToken,
451 hint: Option<StakeLpTokenHint>,
452}
453
454impl<'a, C: Deref<Target = impl Signer> + Clone> StakeLpTokenBuilder<'a, C> {
455 pub fn with_position_id(mut self, position_id: u64) -> Self {
457 self.builder = self.builder.with_position_id(position_id);
458 self
459 }
460
461 pub async fn prepare_hint(&mut self) -> crate::Result<StakeLpTokenHint> {
463 if let Some(hint) = self.hint.as_ref() {
464 return Ok(hint.clone());
465 }
466 let hint = StakeLpTokenHint::from_rpc_client_with(&self.builder, self.client.rpc()).await?;
467 self.hint = Some(hint.clone());
468 Ok(hint)
469 }
470
471 async fn build_txn(&mut self) -> crate::Result<TransactionBuilder<'a, C>> {
472 let hint = self.prepare_hint().await?;
473 let ag = self.builder.clone().into_atomic_group(&hint)?;
474 let mut txn = self.client.store_transaction().pre_atomic_group(ag, true);
475 txn.compute_budget_mut()
476 .set_limit(STAKE_LP_TOKEN_COMPUTE_BUDGET);
477 Ok(txn)
478 }
479}
480
481impl<'a, C: Deref<Target = impl Signer> + Clone> MakeBundleBuilder<'a, C>
482 for StakeLpTokenBuilder<'a, C>
483{
484 async fn build_with_options(
485 &mut self,
486 options: gmsol_solana_utils::bundle_builder::BundleOptions,
487 ) -> gmsol_solana_utils::Result<gmsol_solana_utils::bundle_builder::BundleBuilder<'a, C>> {
488 let mut tx = self.client.bundle_with_options(options);
489
490 tx.try_push(
491 self.build_txn()
492 .await
493 .map_err(gmsol_solana_utils::Error::custom)?,
494 )?;
495
496 Ok(tx)
497 }
498}
499
500impl<C: Deref<Target = impl Signer> + Clone> PullOraclePriceConsumer
501 for StakeLpTokenBuilder<'_, C>
502{
503 async fn feed_ids(&mut self) -> crate::Result<FeedIds> {
504 let hint = self.prepare_hint().await?;
505 Ok(FeedIds::new(
506 self.builder.store_program.store.0,
507 hint.to_tokens_with_feeds()?,
508 ))
509 }
510
511 fn process_feeds(
512 &mut self,
513 provider: PriceProviderKind,
514 map: FeedAddressMap,
515 ) -> crate::Result<()> {
516 self.builder.insert_feed_parser(provider, map)?;
517 Ok(())
518 }
519}
520
521impl<C> SetExecutionFee for StakeLpTokenBuilder<'_, C> {
522 fn is_execution_fee_estimation_required(&self) -> bool {
523 false
524 }
525
526 fn set_execution_fee(&mut self, _lamports: u64) -> &mut Self {
527 self
529 }
530}