solana_account_decoder/
parse_token_extension.rs

1pub use solana_account_decoder_client_types::token::{
2    UiConfidentialTransferAccount, UiConfidentialTransferFeeAmount,
3    UiConfidentialTransferFeeConfig, UiConfidentialTransferMint, UiCpiGuard, UiDefaultAccountState,
4    UiExtension, UiGroupMemberPointer, UiGroupPointer, UiInterestBearingConfig, UiMemoTransfer,
5    UiMetadataPointer, UiMintCloseAuthority, UiPermanentDelegate, UiTokenGroup, UiTokenGroupMember,
6    UiTokenMetadata, UiTransferFee, UiTransferFeeAmount, UiTransferFeeConfig, UiTransferHook,
7    UiTransferHookAccount,
8};
9use {
10    crate::parse_token::convert_account_state,
11    solana_sdk::{clock::UnixTimestamp, program_pack::Pack},
12    spl_token_2022::{
13        extension::{self, BaseState, BaseStateWithExtensions, ExtensionType, StateWithExtensions},
14        solana_program::pubkey::Pubkey,
15        solana_zk_token_sdk::zk_token_elgamal::pod::ElGamalPubkey,
16    },
17    spl_token_group_interface::state::{TokenGroup, TokenGroupMember},
18    spl_token_metadata_interface::state::TokenMetadata,
19};
20
21pub fn parse_extension<S: BaseState + Pack>(
22    extension_type: &ExtensionType,
23    account: &StateWithExtensions<S>,
24) -> UiExtension {
25    match extension_type {
26        ExtensionType::Uninitialized => UiExtension::Uninitialized,
27        ExtensionType::TransferFeeConfig => account
28            .get_extension::<extension::transfer_fee::TransferFeeConfig>()
29            .map(|&extension| {
30                UiExtension::TransferFeeConfig(convert_transfer_fee_config(extension))
31            })
32            .unwrap_or(UiExtension::UnparseableExtension),
33        ExtensionType::TransferFeeAmount => account
34            .get_extension::<extension::transfer_fee::TransferFeeAmount>()
35            .map(|&extension| {
36                UiExtension::TransferFeeAmount(convert_transfer_fee_amount(extension))
37            })
38            .unwrap_or(UiExtension::UnparseableExtension),
39        ExtensionType::MintCloseAuthority => account
40            .get_extension::<extension::mint_close_authority::MintCloseAuthority>()
41            .map(|&extension| {
42                UiExtension::MintCloseAuthority(convert_mint_close_authority(extension))
43            })
44            .unwrap_or(UiExtension::UnparseableExtension),
45        ExtensionType::ConfidentialTransferMint => account
46            .get_extension::<extension::confidential_transfer::ConfidentialTransferMint>()
47            .map(|&extension| {
48                UiExtension::ConfidentialTransferMint(convert_confidential_transfer_mint(extension))
49            })
50            .unwrap_or(UiExtension::UnparseableExtension),
51        ExtensionType::ConfidentialTransferFeeConfig => account
52            .get_extension::<extension::confidential_transfer_fee::ConfidentialTransferFeeConfig>()
53            .map(|&extension| {
54                UiExtension::ConfidentialTransferFeeConfig(
55                    convert_confidential_transfer_fee_config(extension),
56                )
57            })
58            .unwrap_or(UiExtension::UnparseableExtension),
59        ExtensionType::ConfidentialTransferAccount => account
60            .get_extension::<extension::confidential_transfer::ConfidentialTransferAccount>()
61            .map(|&extension| {
62                UiExtension::ConfidentialTransferAccount(convert_confidential_transfer_account(
63                    extension,
64                ))
65            })
66            .unwrap_or(UiExtension::UnparseableExtension),
67        ExtensionType::ConfidentialTransferFeeAmount => account
68            .get_extension::<extension::confidential_transfer_fee::ConfidentialTransferFeeAmount>()
69            .map(|&extension| {
70                UiExtension::ConfidentialTransferFeeAmount(
71                    convert_confidential_transfer_fee_amount(extension),
72                )
73            })
74            .unwrap_or(UiExtension::UnparseableExtension),
75        ExtensionType::DefaultAccountState => account
76            .get_extension::<extension::default_account_state::DefaultAccountState>()
77            .map(|&extension| {
78                UiExtension::DefaultAccountState(convert_default_account_state(extension))
79            })
80            .unwrap_or(UiExtension::UnparseableExtension),
81        ExtensionType::ImmutableOwner => UiExtension::ImmutableOwner,
82        ExtensionType::MemoTransfer => account
83            .get_extension::<extension::memo_transfer::MemoTransfer>()
84            .map(|&extension| UiExtension::MemoTransfer(convert_memo_transfer(extension)))
85            .unwrap_or(UiExtension::UnparseableExtension),
86        ExtensionType::NonTransferable => UiExtension::NonTransferable,
87        ExtensionType::InterestBearingConfig => account
88            .get_extension::<extension::interest_bearing_mint::InterestBearingConfig>()
89            .map(|&extension| {
90                UiExtension::InterestBearingConfig(convert_interest_bearing_config(extension))
91            })
92            .unwrap_or(UiExtension::UnparseableExtension),
93        ExtensionType::CpiGuard => account
94            .get_extension::<extension::cpi_guard::CpiGuard>()
95            .map(|&extension| UiExtension::CpiGuard(convert_cpi_guard(extension)))
96            .unwrap_or(UiExtension::UnparseableExtension),
97        ExtensionType::PermanentDelegate => account
98            .get_extension::<extension::permanent_delegate::PermanentDelegate>()
99            .map(|&extension| UiExtension::PermanentDelegate(convert_permanent_delegate(extension)))
100            .unwrap_or(UiExtension::UnparseableExtension),
101        ExtensionType::NonTransferableAccount => UiExtension::NonTransferableAccount,
102        ExtensionType::MetadataPointer => account
103            .get_extension::<extension::metadata_pointer::MetadataPointer>()
104            .map(|&extension| UiExtension::MetadataPointer(convert_metadata_pointer(extension)))
105            .unwrap_or(UiExtension::UnparseableExtension),
106        ExtensionType::TokenMetadata => account
107            .get_variable_len_extension::<TokenMetadata>()
108            .map(|extension| UiExtension::TokenMetadata(convert_token_metadata(extension)))
109            .unwrap_or(UiExtension::UnparseableExtension),
110        ExtensionType::TransferHook => account
111            .get_extension::<extension::transfer_hook::TransferHook>()
112            .map(|&extension| UiExtension::TransferHook(convert_transfer_hook(extension)))
113            .unwrap_or(UiExtension::UnparseableExtension),
114        ExtensionType::TransferHookAccount => account
115            .get_extension::<extension::transfer_hook::TransferHookAccount>()
116            .map(|&extension| {
117                UiExtension::TransferHookAccount(convert_transfer_hook_account(extension))
118            })
119            .unwrap_or(UiExtension::UnparseableExtension),
120        ExtensionType::GroupPointer => account
121            .get_extension::<extension::group_pointer::GroupPointer>()
122            .map(|&extension| UiExtension::GroupPointer(convert_group_pointer(extension)))
123            .unwrap_or(UiExtension::UnparseableExtension),
124        ExtensionType::GroupMemberPointer => account
125            .get_extension::<extension::group_member_pointer::GroupMemberPointer>()
126            .map(|&extension| {
127                UiExtension::GroupMemberPointer(convert_group_member_pointer(extension))
128            })
129            .unwrap_or(UiExtension::UnparseableExtension),
130        ExtensionType::TokenGroup => account
131            .get_extension::<TokenGroup>()
132            .map(|&extension| UiExtension::TokenGroup(convert_token_group(extension)))
133            .unwrap_or(UiExtension::UnparseableExtension),
134        ExtensionType::TokenGroupMember => account
135            .get_extension::<TokenGroupMember>()
136            .map(|&extension| UiExtension::TokenGroupMember(convert_token_group_member(extension)))
137            .unwrap_or(UiExtension::UnparseableExtension),
138    }
139}
140
141fn convert_transfer_fee(transfer_fee: extension::transfer_fee::TransferFee) -> UiTransferFee {
142    UiTransferFee {
143        epoch: u64::from(transfer_fee.epoch),
144        maximum_fee: u64::from(transfer_fee.maximum_fee),
145        transfer_fee_basis_points: u16::from(transfer_fee.transfer_fee_basis_points),
146    }
147}
148
149fn convert_transfer_fee_config(
150    transfer_fee_config: extension::transfer_fee::TransferFeeConfig,
151) -> UiTransferFeeConfig {
152    let transfer_fee_config_authority: Option<Pubkey> =
153        transfer_fee_config.transfer_fee_config_authority.into();
154    let withdraw_withheld_authority: Option<Pubkey> =
155        transfer_fee_config.withdraw_withheld_authority.into();
156
157    UiTransferFeeConfig {
158        transfer_fee_config_authority: transfer_fee_config_authority
159            .map(|pubkey| pubkey.to_string()),
160        withdraw_withheld_authority: withdraw_withheld_authority.map(|pubkey| pubkey.to_string()),
161        withheld_amount: u64::from(transfer_fee_config.withheld_amount),
162        older_transfer_fee: convert_transfer_fee(transfer_fee_config.older_transfer_fee),
163        newer_transfer_fee: convert_transfer_fee(transfer_fee_config.newer_transfer_fee),
164    }
165}
166
167fn convert_transfer_fee_amount(
168    transfer_fee_amount: extension::transfer_fee::TransferFeeAmount,
169) -> UiTransferFeeAmount {
170    UiTransferFeeAmount {
171        withheld_amount: u64::from(transfer_fee_amount.withheld_amount),
172    }
173}
174
175fn convert_mint_close_authority(
176    mint_close_authority: extension::mint_close_authority::MintCloseAuthority,
177) -> UiMintCloseAuthority {
178    let authority: Option<Pubkey> = mint_close_authority.close_authority.into();
179    UiMintCloseAuthority {
180        close_authority: authority.map(|pubkey| pubkey.to_string()),
181    }
182}
183
184fn convert_default_account_state(
185    default_account_state: extension::default_account_state::DefaultAccountState,
186) -> UiDefaultAccountState {
187    let account_state = spl_token_2022::state::AccountState::try_from(default_account_state.state)
188        .unwrap_or_default();
189    UiDefaultAccountState {
190        account_state: convert_account_state(account_state),
191    }
192}
193
194fn convert_memo_transfer(memo_transfer: extension::memo_transfer::MemoTransfer) -> UiMemoTransfer {
195    UiMemoTransfer {
196        require_incoming_transfer_memos: memo_transfer.require_incoming_transfer_memos.into(),
197    }
198}
199
200fn convert_interest_bearing_config(
201    interest_bearing_config: extension::interest_bearing_mint::InterestBearingConfig,
202) -> UiInterestBearingConfig {
203    let rate_authority: Option<Pubkey> = interest_bearing_config.rate_authority.into();
204
205    UiInterestBearingConfig {
206        rate_authority: rate_authority.map(|pubkey| pubkey.to_string()),
207        initialization_timestamp: UnixTimestamp::from(
208            interest_bearing_config.initialization_timestamp,
209        ),
210        pre_update_average_rate: i16::from(interest_bearing_config.pre_update_average_rate),
211        last_update_timestamp: UnixTimestamp::from(interest_bearing_config.last_update_timestamp),
212        current_rate: i16::from(interest_bearing_config.current_rate),
213    }
214}
215
216fn convert_cpi_guard(cpi_guard: extension::cpi_guard::CpiGuard) -> UiCpiGuard {
217    UiCpiGuard {
218        lock_cpi: cpi_guard.lock_cpi.into(),
219    }
220}
221
222fn convert_permanent_delegate(
223    permanent_delegate: extension::permanent_delegate::PermanentDelegate,
224) -> UiPermanentDelegate {
225    let delegate: Option<Pubkey> = permanent_delegate.delegate.into();
226    UiPermanentDelegate {
227        delegate: delegate.map(|pubkey| pubkey.to_string()),
228    }
229}
230
231pub fn convert_confidential_transfer_mint(
232    confidential_transfer_mint: extension::confidential_transfer::ConfidentialTransferMint,
233) -> UiConfidentialTransferMint {
234    let authority: Option<Pubkey> = confidential_transfer_mint.authority.into();
235    let auditor_elgamal_pubkey: Option<ElGamalPubkey> =
236        confidential_transfer_mint.auditor_elgamal_pubkey.into();
237    UiConfidentialTransferMint {
238        authority: authority.map(|pubkey| pubkey.to_string()),
239        auto_approve_new_accounts: confidential_transfer_mint.auto_approve_new_accounts.into(),
240        auditor_elgamal_pubkey: auditor_elgamal_pubkey.map(|pubkey| pubkey.to_string()),
241    }
242}
243
244pub fn convert_confidential_transfer_fee_config(
245    confidential_transfer_fee_config: extension::confidential_transfer_fee::ConfidentialTransferFeeConfig,
246) -> UiConfidentialTransferFeeConfig {
247    let authority: Option<Pubkey> = confidential_transfer_fee_config.authority.into();
248    let withdraw_withheld_authority_elgamal_pubkey: Option<ElGamalPubkey> =
249        confidential_transfer_fee_config
250            .withdraw_withheld_authority_elgamal_pubkey
251            .into();
252    UiConfidentialTransferFeeConfig {
253        authority: authority.map(|pubkey| pubkey.to_string()),
254        withdraw_withheld_authority_elgamal_pubkey: withdraw_withheld_authority_elgamal_pubkey
255            .map(|pubkey| pubkey.to_string()),
256        harvest_to_mint_enabled: confidential_transfer_fee_config
257            .harvest_to_mint_enabled
258            .into(),
259        withheld_amount: format!("{}", confidential_transfer_fee_config.withheld_amount),
260    }
261}
262
263fn convert_confidential_transfer_account(
264    confidential_transfer_account: extension::confidential_transfer::ConfidentialTransferAccount,
265) -> UiConfidentialTransferAccount {
266    UiConfidentialTransferAccount {
267        approved: confidential_transfer_account.approved.into(),
268        elgamal_pubkey: format!("{}", confidential_transfer_account.elgamal_pubkey),
269        pending_balance_lo: format!("{}", confidential_transfer_account.pending_balance_lo),
270        pending_balance_hi: format!("{}", confidential_transfer_account.pending_balance_hi),
271        available_balance: format!("{}", confidential_transfer_account.available_balance),
272        decryptable_available_balance: format!(
273            "{}",
274            confidential_transfer_account.decryptable_available_balance
275        ),
276        allow_confidential_credits: confidential_transfer_account
277            .allow_confidential_credits
278            .into(),
279        allow_non_confidential_credits: confidential_transfer_account
280            .allow_non_confidential_credits
281            .into(),
282        pending_balance_credit_counter: confidential_transfer_account
283            .pending_balance_credit_counter
284            .into(),
285        maximum_pending_balance_credit_counter: confidential_transfer_account
286            .maximum_pending_balance_credit_counter
287            .into(),
288        expected_pending_balance_credit_counter: confidential_transfer_account
289            .expected_pending_balance_credit_counter
290            .into(),
291        actual_pending_balance_credit_counter: confidential_transfer_account
292            .actual_pending_balance_credit_counter
293            .into(),
294    }
295}
296
297fn convert_confidential_transfer_fee_amount(
298    confidential_transfer_fee_amount: extension::confidential_transfer_fee::ConfidentialTransferFeeAmount,
299) -> UiConfidentialTransferFeeAmount {
300    UiConfidentialTransferFeeAmount {
301        withheld_amount: format!("{}", confidential_transfer_fee_amount.withheld_amount),
302    }
303}
304
305fn convert_metadata_pointer(
306    metadata_pointer: extension::metadata_pointer::MetadataPointer,
307) -> UiMetadataPointer {
308    let authority: Option<Pubkey> = metadata_pointer.authority.into();
309    let metadata_address: Option<Pubkey> = metadata_pointer.metadata_address.into();
310    UiMetadataPointer {
311        authority: authority.map(|pubkey| pubkey.to_string()),
312        metadata_address: metadata_address.map(|pubkey| pubkey.to_string()),
313    }
314}
315
316fn convert_token_metadata(token_metadata: TokenMetadata) -> UiTokenMetadata {
317    let update_authority: Option<Pubkey> = token_metadata.update_authority.into();
318    UiTokenMetadata {
319        update_authority: update_authority.map(|pubkey| pubkey.to_string()),
320        mint: token_metadata.mint.to_string(),
321        name: token_metadata.name,
322        symbol: token_metadata.symbol,
323        uri: token_metadata.uri,
324        additional_metadata: token_metadata.additional_metadata,
325    }
326}
327
328fn convert_transfer_hook(transfer_hook: extension::transfer_hook::TransferHook) -> UiTransferHook {
329    let authority: Option<Pubkey> = transfer_hook.authority.into();
330    let program_id: Option<Pubkey> = transfer_hook.program_id.into();
331    UiTransferHook {
332        authority: authority.map(|pubkey| pubkey.to_string()),
333        program_id: program_id.map(|pubkey| pubkey.to_string()),
334    }
335}
336
337fn convert_transfer_hook_account(
338    transfer_hook: extension::transfer_hook::TransferHookAccount,
339) -> UiTransferHookAccount {
340    UiTransferHookAccount {
341        transferring: transfer_hook.transferring.into(),
342    }
343}
344
345fn convert_group_pointer(group_pointer: extension::group_pointer::GroupPointer) -> UiGroupPointer {
346    let authority: Option<Pubkey> = group_pointer.authority.into();
347    let group_address: Option<Pubkey> = group_pointer.group_address.into();
348    UiGroupPointer {
349        authority: authority.map(|pubkey| pubkey.to_string()),
350        group_address: group_address.map(|pubkey| pubkey.to_string()),
351    }
352}
353
354fn convert_group_member_pointer(
355    member_pointer: extension::group_member_pointer::GroupMemberPointer,
356) -> UiGroupMemberPointer {
357    let authority: Option<Pubkey> = member_pointer.authority.into();
358    let member_address: Option<Pubkey> = member_pointer.member_address.into();
359    UiGroupMemberPointer {
360        authority: authority.map(|pubkey| pubkey.to_string()),
361        member_address: member_address.map(|pubkey| pubkey.to_string()),
362    }
363}
364
365fn convert_token_group(token_group: TokenGroup) -> UiTokenGroup {
366    let update_authority: Option<Pubkey> = token_group.update_authority.into();
367    UiTokenGroup {
368        update_authority: update_authority.map(|pubkey| pubkey.to_string()),
369        mint: token_group.mint.to_string(),
370        size: token_group.size.into(),
371        max_size: token_group.max_size.into(),
372    }
373}
374
375fn convert_token_group_member(member: TokenGroupMember) -> UiTokenGroupMember {
376    UiTokenGroupMember {
377        mint: member.mint.to_string(),
378        group: member.group.to_string(),
379        member_number: member.member_number.into(),
380    }
381}