carbon_pumpfun_decoder/accounts/
mod.rs

1use carbon_core::account::AccountDecoder;
2use carbon_core::deserialize::CarbonDeserialize;
3
4use crate::PROGRAM_ID;
5
6use super::PumpfunDecoder;
7pub mod bonding_curve;
8pub mod global;
9pub mod last_withdraw;
10
11#[allow(clippy::large_enum_variant)]
12pub enum PumpAccount {
13    BondingCurve(bonding_curve::BondingCurve),
14    Global(global::Global),
15    LastWithdraw(last_withdraw::LastWithdraw),
16}
17
18impl AccountDecoder<'_> for PumpfunDecoder {
19    type AccountType = PumpAccount;
20    fn decode_account(
21        &self,
22        account: &solana_account::Account,
23    ) -> Option<carbon_core::account::DecodedAccount<Self::AccountType>> {
24        if !account.owner.eq(&PROGRAM_ID) {
25            return None;
26        }
27
28        if let Some(decoded_account) =
29            bonding_curve::BondingCurve::deserialize(account.data.as_slice())
30        {
31            return Some(carbon_core::account::DecodedAccount {
32                lamports: account.lamports,
33                data: PumpAccount::BondingCurve(decoded_account),
34                owner: account.owner,
35                executable: account.executable,
36                rent_epoch: account.rent_epoch,
37            });
38        }
39
40        if let Some(decoded_account) = global::Global::deserialize(account.data.as_slice()) {
41            return Some(carbon_core::account::DecodedAccount {
42                lamports: account.lamports,
43                data: PumpAccount::Global(decoded_account),
44                owner: account.owner,
45                executable: account.executable,
46                rent_epoch: account.rent_epoch,
47            });
48        }
49
50        if let Some(decoded_account) =
51            last_withdraw::LastWithdraw::deserialize(account.data.as_slice())
52        {
53            return Some(carbon_core::account::DecodedAccount {
54                lamports: account.lamports,
55                data: PumpAccount::LastWithdraw(decoded_account),
56                owner: account.owner,
57                executable: account.executable,
58                rent_epoch: account.rent_epoch,
59            });
60        }
61
62        None
63    }
64}
65
66#[cfg(test)]
67mod tests {
68    use solana_pubkey::Pubkey;
69
70    use super::*;
71
72    #[test]
73    fn test_decode_bonding_curve_account() {
74        // Arrange
75        let expected_bonding_curve = bonding_curve::BondingCurve {
76            virtual_token_reserves: 1072906494066221,
77            virtual_sol_reserves: 30002615555,
78            real_token_reserves: 793006494066221,
79            real_sol_reserves: 2615555,
80            token_total_supply: 1000000000000000,
81            complete: false,
82        };
83
84        // Act
85        let decoder = PumpfunDecoder;
86        let account = carbon_test_utils::read_account("tests/fixtures/bonding_curve_account.json")
87            .expect("read fixture");
88        let decoded_account = decoder.decode_account(&account).expect("decode fixture");
89
90        // Assert
91        match decoded_account.data {
92            PumpAccount::BondingCurve(bonding_curve) => {
93                assert_eq!(
94                    expected_bonding_curve.virtual_token_reserves,
95                    bonding_curve.virtual_token_reserves
96                );
97                assert_eq!(
98                    expected_bonding_curve.virtual_sol_reserves,
99                    bonding_curve.virtual_sol_reserves
100                );
101                assert_eq!(
102                    expected_bonding_curve.real_token_reserves,
103                    bonding_curve.real_token_reserves
104                );
105                assert_eq!(
106                    expected_bonding_curve.real_sol_reserves,
107                    bonding_curve.real_sol_reserves
108                );
109                assert_eq!(
110                    expected_bonding_curve.token_total_supply,
111                    bonding_curve.token_total_supply
112                );
113                assert_eq!(expected_bonding_curve.complete, bonding_curve.complete);
114            }
115            _ => panic!("Expected BondingCurve"),
116        }
117    }
118
119    #[test]
120    fn test_decode_global_account() {
121        // Arrange
122        let expected_global_account = global::Global {
123            initialized: true,
124            authority: Pubkey::from_str_const("FFWtrEQ4B4PKQoVuHYzZq8FabGkVatYzDpEVHsK5rrhF"),
125            withdraw_authority: Pubkey::from_str_const(
126                "39azUYFWPz3VHgKCf3VChUwbpURdCHRxjWVowf5jUJjg",
127            ),
128            fee_recipient: Pubkey::from_str_const("62qc2CNXwrYqQScmEdiZFFAnJR262PxWEuNQtxfafNgV"),
129            initial_virtual_token_reserves: 1073000000000000,
130            initial_virtual_sol_reserves: 30000000000,
131            initial_real_token_reserves: 793100000000000,
132            token_total_supply: 1000000000000000,
133            fee_basis_points: 100,
134            pool_migration_fee: 15000001,
135            enable_migrate: true,
136            fee_recipients: [
137                Pubkey::from_str_const("7VtfL8fvgNfhz17qKRMjzQEXgbdpnHHHQRh54R9jP2RJ"),
138                Pubkey::from_str_const("7hTckgnGnLQR6sdH7YkqFTAA7VwTfYFaZ6EhEsU3saCX"),
139                Pubkey::from_str_const("9rPYyANsfQZw3DnDmKE3YCQF5E8oD89UXoHn9JFEhJUz"),
140                Pubkey::from_str_const("AVmoTthdrX6tKt4nDjco2D775W2YK3sDhxPcMmzUAmTY"),
141                Pubkey::from_str_const("CebN5WGQ4jvEPvsVU4EoHEpgzq1VV7AbicfhtW4xC9iM"),
142                Pubkey::from_str_const("FWsW1xNtWscwNmKv6wVsU1iTzRN6wmmk3MjxRP5tT7hz"),
143                Pubkey::from_str_const("G5UZAVbAf46s7cKWoyKu8kYTip9DGTpbLZ2qa9Aq69dP"),
144            ],
145            ..Default::default()
146        };
147
148        // Act
149        let decoder = PumpfunDecoder;
150        let account = carbon_test_utils::read_account("tests/fixtures/global_account.json")
151            .expect("read fixture");
152        let decoded_account = decoder.decode_account(&account).expect("decode fixture");
153
154        // Assert
155        match decoded_account.data {
156            PumpAccount::Global(global_account) => {
157                assert_eq!(
158                    expected_global_account.initialized,
159                    global_account.initialized
160                );
161                assert_eq!(expected_global_account.authority, global_account.authority);
162                assert_eq!(
163                    expected_global_account.fee_recipient,
164                    global_account.fee_recipient
165                );
166                assert_eq!(
167                    expected_global_account.initial_virtual_token_reserves,
168                    global_account.initial_virtual_token_reserves
169                );
170                assert_eq!(
171                    expected_global_account.initial_virtual_sol_reserves,
172                    global_account.initial_virtual_sol_reserves
173                );
174                assert_eq!(
175                    expected_global_account.initial_real_token_reserves,
176                    global_account.initial_real_token_reserves
177                );
178                assert_eq!(
179                    expected_global_account.token_total_supply,
180                    global_account.token_total_supply
181                );
182                assert_eq!(
183                    expected_global_account.fee_basis_points,
184                    global_account.fee_basis_points
185                );
186            }
187            _ => panic!("Expected Global"),
188        }
189    }
190
191    #[test]
192    fn test_decode_last_withdraw_account() {
193        // Arrange
194        let expected_last_withdraw_account = last_withdraw::LastWithdraw {
195            last_withdraw_timestamp: 1741550682,
196        };
197
198        // Act
199        let decoder = PumpfunDecoder;
200        let account = carbon_test_utils::read_account("tests/fixtures/last_withdraw_account.json")
201            .expect("read fixture");
202        let decoded_account = decoder.decode_account(&account).expect("decode fixture");
203
204        // Assert
205        match decoded_account.data {
206            PumpAccount::LastWithdraw(last_withdraw) => {
207                assert_eq!(
208                    expected_last_withdraw_account.last_withdraw_timestamp,
209                    last_withdraw.last_withdraw_timestamp
210                );
211            }
212            _ => panic!("Expected LastWithdraw"),
213        }
214    }
215}