1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
#[cfg(liquidity_provider)]
use gmsol_programs::gmsol_liquidity_provider::accounts::{LpTokenController, Position};
use crate::utils::{Amount, GmAmount, Value};
use super::StringPubkey;
/// Additional computed data for LP position.
#[cfg(liquidity_provider)]
#[derive(Debug, Clone)]
pub struct LpPositionComputedData {
/// Claimable GT rewards (calculated using precise on-chain logic) - Amount type for proper decimal handling.
pub claimable_gt: Amount,
/// Current display APY (current week's APY rate) as fixed-point Value (1e20 scale, same as on-chain).
/// Note: This is used for UI display and represents the APY rate for the current staking week.
/// GT reward calculations internally use time-weighted APY for accuracy.
pub current_apy: Value,
/// Time-weighted average APY over the entire staking period as fixed-point Value (1e20 scale).
/// This represents the average APY rate considering all weeks of staking.
pub average_apy: Value,
/// LP token symbol (e.g., "GM-SOL/USDC", "GLV-BTC").
/// Should have fallback to abbreviated mint address if mapping fails.
pub lp_token_symbol: String,
}
/// Serializable version of LP staking position [`Position`].
#[cfg(liquidity_provider)]
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct SerdeLpStakingPosition {
/// Owner of this LP position.
pub owner: StringPubkey,
/// LP token controller that manages this position.
pub controller: StringPubkey,
/// Controller index (allows multiple controllers per token).
pub controller_index: u64,
/// LP token mint for this position.
pub lp_token_mint: StringPubkey,
/// LP token symbol (e.g., "GM-SOL/USDC", "GLV-BTC").
pub lp_token_symbol: String,
/// Position id to allow multiple positions per owner.
pub position_id: u64,
/// Staked LP amount (raw format, display layer will format).
pub staked_amount: GmAmount,
/// Staked value in USD (raw format, display layer will format).
pub staked_value_usd: Value,
/// Stake start unix timestamp (seconds) - display layer handles formatting.
pub stake_start_time: i64,
/// Current display APY (current week's APY rate) as fixed-point Value (display layer converts to percentage).
/// Note: This is the APY for the current staking week, used for UI display.
/// For GT reward calculations, time-weighted APY is used internally.
pub current_apy: Value,
/// Time-weighted average APY over the entire staking period as fixed-point Value (display layer converts to percentage).
/// This represents the average APY rate considering all weeks of staking.
pub average_apy: Value,
/// Claimable GT rewards (calculated using precise on-chain logic) - raw format.
pub claimable_gt: Amount,
/// Position vault address (PDA that holds staked tokens).
pub vault: StringPubkey,
/// Whether the controller is still enabled.
pub controller_enabled: bool,
}
#[cfg(liquidity_provider)]
impl SerdeLpStakingPosition {
/// Create from LP [`Position`] with additional computed data.
pub fn from_position(
position: &Position,
controller: &LpTokenController,
computed_data: LpPositionComputedData,
) -> crate::Result<Self> {
// Symbol fallback: use provided symbol or generate from mint address
let lp_token_symbol = if computed_data.lp_token_symbol.is_empty() {
fallback_lp_token_symbol(&position.lp_mint.into())
} else {
computed_data.lp_token_symbol
};
Ok(Self {
owner: position.owner.into(),
controller: position.controller.into(),
controller_index: controller.controller_index,
lp_token_mint: position.lp_mint.into(),
lp_token_symbol,
position_id: position.position_id,
staked_amount: GmAmount::from_u64(position.staked_amount),
staked_value_usd: Value::from_u128(position.staked_value_usd),
stake_start_time: position.stake_start_time, // Raw timestamp, display layer formats
current_apy: computed_data.current_apy, // Raw Value, display layer converts to %
average_apy: computed_data.average_apy, // Raw Value, display layer converts to %
claimable_gt: computed_data.claimable_gt, // Already an Amount type
vault: position.vault.into(),
controller_enabled: controller.is_enabled,
})
}
}
/// Helper to create a fallback symbol from mint address when token mapping fails.
#[cfg(liquidity_provider)]
pub fn fallback_lp_token_symbol(mint: &StringPubkey) -> String {
let mint_str = mint.to_string();
// Take first 4 and last 4 characters for abbreviated display
if mint_str.len() > 8 {
format!("{}...{}", &mint_str[..4], &mint_str[mint_str.len() - 4..])
} else {
mint_str
}
}