Skip to main content

spl_token_2022_interface/extension/transfer_hook/
mod.rs

1use {
2    crate::{
3        extension::{
4            BaseState, BaseStateWithExtensions, BaseStateWithExtensionsMut, Extension,
5            ExtensionType, PodStateWithExtensionsMut,
6        },
7        pod::PodAccount,
8    },
9    bytemuck::{Pod, Zeroable},
10    solana_account_info::AccountInfo,
11    solana_address::Address,
12    solana_nullable::MaybeNull,
13    solana_program_error::ProgramError,
14    solana_zero_copy::unaligned::Bool,
15};
16#[cfg(feature = "serde")]
17use {
18    serde::{Deserialize, Serialize},
19    serde_with::{As, DisplayFromStr},
20};
21
22/// Instructions for the `TransferHook` extension
23pub mod instruction;
24
25/// Transfer hook extension data for mints.
26#[repr(C)]
27#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
28#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
29#[derive(Clone, Copy, Debug, Default, PartialEq, Pod, Zeroable)]
30pub struct TransferHook {
31    /// Authority that can set the transfer hook program id
32    #[cfg_attr(feature = "serde", serde(with = "As::<Option<DisplayFromStr>>"))]
33    pub authority: MaybeNull<Address>,
34    /// Program that authorizes the transfer
35    #[cfg_attr(feature = "serde", serde(with = "As::<Option<DisplayFromStr>>"))]
36    pub program_id: MaybeNull<Address>,
37}
38
39/// Indicates that the tokens from this account belong to a mint with a transfer
40/// hook
41#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
42#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
43#[derive(Clone, Copy, Debug, Default, PartialEq, Pod, Zeroable)]
44#[repr(transparent)]
45pub struct TransferHookAccount {
46    /// Flag to indicate that the account is in the middle of a transfer
47    pub transferring: Bool,
48}
49
50impl Extension for TransferHook {
51    const TYPE: ExtensionType = ExtensionType::TransferHook;
52}
53
54impl Extension for TransferHookAccount {
55    const TYPE: ExtensionType = ExtensionType::TransferHookAccount;
56}
57
58/// Attempts to get the transfer hook program id from the TLV data, returning
59/// None if the extension is not found
60pub fn get_program_id<S: BaseState, BSE: BaseStateWithExtensions<S>>(
61    state: &BSE,
62) -> Option<Address> {
63    state
64        .get_extension::<TransferHook>()
65        .ok()
66        .and_then(|e| Option::<Address>::from(e.program_id))
67}
68
69/// Helper function to set the transferring flag before calling into transfer
70/// hook
71pub fn set_transferring<BSE: BaseStateWithExtensionsMut<S>, S: BaseState>(
72    account: &mut BSE,
73) -> Result<(), ProgramError> {
74    let account_extension = account.get_extension_mut::<TransferHookAccount>()?;
75    account_extension.transferring = true.into();
76    Ok(())
77}
78
79/// Helper function to unset the transferring flag after a transfer
80pub fn unset_transferring(account_info: &AccountInfo) -> Result<(), ProgramError> {
81    let mut account_data = account_info.data.borrow_mut();
82    let mut account = PodStateWithExtensionsMut::<PodAccount>::unpack(&mut account_data)?;
83    let account_extension = account.get_extension_mut::<TransferHookAccount>()?;
84    account_extension.transferring = false.into();
85    Ok(())
86}