solana_account_decoder/
parse_token_extension.rs

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