瀏覽代碼

spl: Bump token-2022 and friends (#33453)

* token: Update to 4.0.0

* token-2022: Bump and support new account and instruction types

* Update token-2022 in fetch_spl / program-test

* Fixup downstream uses

* Mint and destination were flipped in 0.9.0

* Don't use `convert_pubkey`

* Bump spl dependencies to versions which avoid recompilations
Jon Cinque 2 年之前
父節點
當前提交
de38b05ad1

+ 196 - 26
Cargo.lock

@@ -709,7 +709,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "4114279215a005bc675e386011e594e1d9b800918cea18fcadadcce864a2046b"
 dependencies = [
  "borsh-derive 0.10.3",
- "hashbrown 0.13.2",
+ "hashbrown 0.12.3",
 ]
 
 [[package]]
@@ -3299,6 +3299,17 @@ dependencies = [
  "syn 1.0.109",
 ]
 
+[[package]]
+name = "num-derive"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9e6a0fd4f737c707bd9086cc16c925f294943eb62eb71499e9fd4cf71f8b9f4e"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.37",
+]
+
 [[package]]
 name = "num-integer"
 version = "0.1.44"
@@ -3370,6 +3381,15 @@ dependencies = [
  "num_enum_derive 0.6.1",
 ]
 
+[[package]]
+name = "num_enum"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "70bf6736f74634d299d00086f02986875b3c2d924781a6a2cb6c201e73da0ceb"
+dependencies = [
+ "num_enum_derive 0.7.0",
+]
+
 [[package]]
 name = "num_enum_derive"
 version = "0.5.11"
@@ -3394,6 +3414,18 @@ dependencies = [
  "syn 2.0.37",
 ]
 
+[[package]]
+name = "num_enum_derive"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "56ea360eafe1022f7cc56cd7b869ed57330fb2453d0c7831d99b74c65d2f5597"
+dependencies = [
+ "proc-macro-crate 1.1.0",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.37",
+]
+
 [[package]]
 name = "num_threads"
 version = "0.1.3"
@@ -5077,8 +5109,10 @@ dependencies = [
  "serde_json",
  "solana-config-program",
  "solana-sdk",
+ "spl-pod",
  "spl-token",
  "spl-token-2022",
+ "spl-token-metadata-interface",
  "thiserror",
  "zstd",
 ]
@@ -5155,7 +5189,7 @@ dependencies = [
  "memmap2",
  "memoffset 0.9.0",
  "modular-bitfield",
- "num-derive",
+ "num-derive 0.3.3",
  "num-traits",
  "num_cpus",
  "num_enum 0.6.1",
@@ -5198,7 +5232,7 @@ dependencies = [
  "bincode",
  "bytemuck",
  "log",
- "num-derive",
+ "num-derive 0.3.3",
  "num-traits",
  "rustc_version 0.4.0",
  "serde",
@@ -6160,6 +6194,7 @@ dependencies = [
  "solana-transaction-status",
  "solana-vote",
  "solana-vote-program",
+ "spl-pod",
  "spl-token",
  "spl-token-2022",
  "static_assertions",
@@ -6483,7 +6518,7 @@ dependencies = [
  "log",
  "memoffset 0.9.0",
  "num-bigint 0.4.4",
- "num-derive",
+ "num-derive 0.3.3",
  "num-traits",
  "parking_lot 0.12.1",
  "rand 0.8.5",
@@ -6519,7 +6554,7 @@ dependencies = [
  "libc",
  "libsecp256k1",
  "log",
- "num-derive",
+ "num-derive 0.3.3",
  "num-traits",
  "percentage",
  "rand 0.8.5",
@@ -6632,7 +6667,7 @@ dependencies = [
  "dialoguer",
  "hidapi",
  "log",
- "num-derive",
+ "num-derive 0.3.3",
  "num-traits",
  "parking_lot 0.12.1",
  "qstring",
@@ -6691,6 +6726,7 @@ dependencies = [
  "solana-version",
  "solana-vote",
  "solana-vote-program",
+ "spl-pod",
  "spl-token",
  "spl-token-2022",
  "stream-cancel",
@@ -6824,7 +6860,7 @@ dependencies = [
  "memmap2",
  "memoffset 0.9.0",
  "modular-bitfield",
- "num-derive",
+ "num-derive 0.3.3",
  "num-traits",
  "num_cpus",
  "num_enum 0.6.1",
@@ -6904,7 +6940,7 @@ dependencies = [
  "libsecp256k1",
  "log",
  "memmap2",
- "num-derive",
+ "num-derive 0.3.3",
  "num-traits",
  "num_enum 0.6.1",
  "pbkdf2 0.11.0",
@@ -7232,7 +7268,7 @@ dependencies = [
  "Inflector",
  "base64 0.21.4",
  "bincode",
- "borsh 0.9.3",
+ "borsh 0.10.3",
  "bs58",
  "lazy_static",
  "log",
@@ -7410,7 +7446,7 @@ dependencies = [
  "assert_matches",
  "bincode",
  "log",
- "num-derive",
+ "num-derive 0.3.3",
  "num-traits",
  "rustc_version 0.4.0",
  "serde",
@@ -7471,7 +7507,7 @@ dependencies = [
  "bytemuck",
  "criterion",
  "curve25519-dalek",
- "num-derive",
+ "num-derive 0.3.3",
  "num-traits",
  "solana-program-runtime",
  "solana-sdk",
@@ -7504,7 +7540,7 @@ dependencies = [
  "itertools",
  "lazy_static",
  "merlin",
- "num-derive",
+ "num-derive 0.3.3",
  "num-traits",
  "rand 0.7.3",
  "serde",
@@ -7562,13 +7598,13 @@ dependencies = [
 
 [[package]]
 name = "spl-associated-token-account"
-version = "1.1.3"
+version = "2.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "978dba3bcbe88d0c2c58366c254d9ea41c5f73357e72fc0bdee4d6b5fc99c8f4"
+checksum = "385e31c29981488f2820b2022d8e731aae3b02e6e18e2fd854e4c9a94dc44fc3"
 dependencies = [
  "assert_matches",
- "borsh 0.9.3",
- "num-derive",
+ "borsh 0.10.3",
+ "num-derive 0.4.0",
  "num-traits",
  "solana-program",
  "spl-token",
@@ -7576,6 +7612,41 @@ dependencies = [
  "thiserror",
 ]
 
+[[package]]
+name = "spl-discriminator"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cce5d563b58ef1bb2cdbbfe0dfb9ffdc24903b10ae6a4df2d8f425ece375033f"
+dependencies = [
+ "bytemuck",
+ "solana-program",
+ "spl-discriminator-derive",
+]
+
+[[package]]
+name = "spl-discriminator-derive"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fadbefec4f3c678215ca72bd71862697bb06b41fd77c0088902dd3203354387b"
+dependencies = [
+ "quote",
+ "spl-discriminator-syn",
+ "syn 2.0.37",
+]
+
+[[package]]
+name = "spl-discriminator-syn"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0e5f2044ca42c8938d54d1255ce599c79a1ffd86b677dfab695caa20f9ffc3f2"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "sha2 0.10.7",
+ "syn 2.0.37",
+ "thiserror",
+]
+
 [[package]]
 name = "spl-instruction-padding"
 version = "0.1.0"
@@ -7588,46 +7659,145 @@ dependencies = [
 
 [[package]]
 name = "spl-memo"
-version = "3.0.1"
+version = "4.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f0f180b03318c3dbab3ef4e1e4d46d5211ae3c780940dd0a28695aba4b59a75a"
+dependencies = [
+ "solana-program",
+]
+
+[[package]]
+name = "spl-pod"
+version = "0.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bd0dc6f70db6bacea7ff25870b016a65ba1d1b6013536f08e4fd79a8f9005325"
+checksum = "2881dddfca792737c0706fa0175345ab282b1b0879c7d877bad129645737c079"
 dependencies = [
+ "borsh 0.10.3",
+ "bytemuck",
  "solana-program",
+ "solana-zk-token-sdk",
+ "spl-program-error",
+]
+
+[[package]]
+name = "spl-program-error"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "249e0318493b6bcf27ae9902600566c689b7dfba9f1bdff5893e92253374e78c"
+dependencies = [
+ "num-derive 0.4.0",
+ "num-traits",
+ "solana-program",
+ "spl-program-error-derive",
+ "thiserror",
+]
+
+[[package]]
+name = "spl-program-error-derive"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ab5269c8e868da17b6552ef35a51355a017bd8e0eae269c201fef830d35fa52c"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "sha2 0.10.7",
+ "syn 2.0.37",
+]
+
+[[package]]
+name = "spl-tlv-account-resolution"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "062e148d3eab7b165582757453632ffeef490c02c86a48bfdb4988f63eefb3b9"
+dependencies = [
+ "bytemuck",
+ "solana-program",
+ "spl-discriminator",
+ "spl-pod",
+ "spl-program-error",
+ "spl-type-length-value",
 ]
 
 [[package]]
 name = "spl-token"
-version = "3.5.0"
+version = "4.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8e85e168a785e82564160dcb87b2a8e04cee9bfd1f4d488c729d53d6a4bd300d"
+checksum = "08459ba1b8f7c1020b4582c4edf0f5c7511a5e099a7a97570c9698d4f2337060"
 dependencies = [
  "arrayref",
  "bytemuck",
- "num-derive",
+ "num-derive 0.3.3",
  "num-traits",
- "num_enum 0.5.11",
+ "num_enum 0.6.1",
  "solana-program",
  "thiserror",
 ]
 
 [[package]]
 name = "spl-token-2022"
-version = "0.6.1"
+version = "0.9.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0043b590232c400bad5ee9eb983ced003d15163c4c5d56b090ac6d9a57457b47"
+checksum = "e4abf34a65ba420584a0c35f3903f8d727d1f13ababbdc3f714c6b065a686e86"
 dependencies = [
  "arrayref",
  "bytemuck",
- "num-derive",
+ "num-derive 0.4.0",
  "num-traits",
- "num_enum 0.5.11",
+ "num_enum 0.7.0",
  "solana-program",
  "solana-zk-token-sdk",
  "spl-memo",
+ "spl-pod",
  "spl-token",
+ "spl-token-metadata-interface",
+ "spl-transfer-hook-interface",
+ "spl-type-length-value",
  "thiserror",
 ]
 
+[[package]]
+name = "spl-token-metadata-interface"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4c16ce3ba6979645fb7627aa1e435576172dd63088dc7848cb09aa331fa1fe4f"
+dependencies = [
+ "borsh 0.10.3",
+ "solana-program",
+ "spl-discriminator",
+ "spl-pod",
+ "spl-program-error",
+ "spl-type-length-value",
+]
+
+[[package]]
+name = "spl-transfer-hook-interface"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "051d31803f873cabe71aec3c1b849f35248beae5d19a347d93a5c9cccc5d5a9b"
+dependencies = [
+ "arrayref",
+ "bytemuck",
+ "solana-program",
+ "spl-discriminator",
+ "spl-pod",
+ "spl-program-error",
+ "spl-tlv-account-resolution",
+ "spl-type-length-value",
+]
+
+[[package]]
+name = "spl-type-length-value"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a468e6f6371f9c69aae760186ea9f1a01c2908351b06a5e0026d21cfc4d7ecac"
+dependencies = [
+ "bytemuck",
+ "solana-program",
+ "spl-discriminator",
+ "spl-pod",
+ "spl-program-error",
+]
+
 [[package]]
 name = "static_assertions"
 version = "1.1.0"

+ 8 - 4
Cargo.toml

@@ -373,11 +373,13 @@ solana-vote-program = { path = "programs/vote", version = "=1.17.0" }
 solana-zk-keygen = { path = "zk-keygen", version = "=1.17.0" }
 solana-zk-token-proof-program = { path = "programs/zk-token-proof", version = "=1.17.0" }
 solana-zk-token-sdk = { path = "zk-token-sdk", version = "=1.17.0" }
-spl-associated-token-account = "=1.1.3"
+spl-associated-token-account = "=2.2.0"
 spl-instruction-padding = "0.1"
-spl-memo = "=3.0.1"
-spl-token = "=3.5.0"
-spl-token-2022 = "=0.6.1"
+spl-memo = "=4.0.0"
+spl-pod = "=0.1.0"
+spl-token = "=4.0.0"
+spl-token-2022 = "=0.9.0"
+spl-token-metadata-interface = "=0.2.0"
 static_assertions = "1.1.0"
 stream-cancel = "0.8.1"
 strum = "0.24"
@@ -423,8 +425,10 @@ crossbeam-epoch = { git = "https://github.com/solana-labs/crossbeam", rev = "fd2
 #  * spl-associated-token-account
 #  * spl-instruction-padding
 #  * spl-memo
+#  * spl-pod
 #  * spl-token
 #  * spl-token-2022
+#  * spl-token-metadata-interface
 #
 # They, in turn, depend on a number of crates that we also include directly using `path`
 # specifications.  For example, `spl-token` depends on `solana-program`.  And we explicitly specify

+ 2 - 0
account-decoder/Cargo.toml

@@ -23,11 +23,13 @@ solana-config-program = { workspace = true }
 solana-sdk = { workspace = true }
 spl-token = { workspace = true, features = ["no-entrypoint"] }
 spl-token-2022 = { workspace = true, features = ["no-entrypoint"] }
+spl-token-metadata-interface = { workspace = true }
 thiserror = { workspace = true }
 zstd = { workspace = true }
 
 [dev-dependencies]
 assert_matches = { workspace = true }
+spl-pod = { workspace = true }
 
 [package.metadata.docs.rs]
 targets = ["x86_64-unknown-linux-gnu"]

+ 9 - 9
account-decoder/src/parse_token.rs

@@ -290,12 +290,10 @@ mod test {
     use {
         super::*,
         crate::parse_token_extension::{UiMemoTransfer, UiMintCloseAuthority},
-        spl_token_2022::{
-            extension::{
-                immutable_owner::ImmutableOwner, memo_transfer::MemoTransfer,
-                mint_close_authority::MintCloseAuthority, ExtensionType, StateWithExtensionsMut,
-            },
-            pod::OptionalNonZeroPubkey,
+        spl_pod::optional_keys::OptionalNonZeroPubkey,
+        spl_token_2022::extension::{
+            immutable_owner::ImmutableOwner, memo_transfer::MemoTransfer,
+            mint_close_authority::MintCloseAuthority, ExtensionType, StateWithExtensionsMut,
         },
     };
 
@@ -506,10 +504,11 @@ mod test {
             delegate: COption::None,
             delegated_amount: 0,
         };
-        let account_size = ExtensionType::get_account_len::<Account>(&[
+        let account_size = ExtensionType::try_calculate_account_len::<Account>(&[
             ExtensionType::ImmutableOwner,
             ExtensionType::MemoTransfer,
-        ]);
+        ])
+        .unwrap();
         let mut account_data = vec![0; account_size];
         let mut account_state =
             StateWithExtensionsMut::<Account>::unpack_uninitialized(&mut account_data).unwrap();
@@ -586,7 +585,8 @@ mod test {
     fn test_parse_token_mint_with_extensions() {
         let owner_pubkey = SplTokenPubkey::new_from_array([3; 32]);
         let mint_size =
-            ExtensionType::get_account_len::<Mint>(&[ExtensionType::MintCloseAuthority]);
+            ExtensionType::try_calculate_account_len::<Mint>(&[ExtensionType::MintCloseAuthority])
+                .unwrap();
         let mint_base = Mint {
             mint_authority: COption::Some(owner_pubkey),
             supply: 42,

+ 164 - 19
account-decoder/src/parse_token_extension.rs

@@ -6,6 +6,7 @@ use {
         solana_program::pubkey::Pubkey,
         solana_zk_token_sdk::zk_token_elgamal::pod::ElGamalPubkey,
     },
+    spl_token_metadata_interface::state::TokenMetadata,
 };
 
 #[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
@@ -24,15 +25,21 @@ pub enum UiExtension {
     InterestBearingConfig(UiInterestBearingConfig),
     CpiGuard(UiCpiGuard),
     PermanentDelegate(UiPermanentDelegate),
-    UnparseableExtension,
     NonTransferableAccount,
+    ConfidentialTransferFeeConfig(UiConfidentialTransferFeeConfig),
+    ConfidentialTransferFeeAmount(UiConfidentialTransferFeeAmount),
+    TransferHook(UiTransferHook),
+    TransferHookAccount(UiTransferHookAccount),
+    MetadataPointer(UiMetadataPointer),
+    TokenMetadata(UiTokenMetadata),
+    UnparseableExtension,
 }
 
 pub fn parse_extension<S: BaseState>(
     extension_type: &ExtensionType,
     account: &StateWithExtensions<S>,
 ) -> UiExtension {
-    match &extension_type {
+    match extension_type {
         ExtensionType::Uninitialized => UiExtension::Uninitialized,
         ExtensionType::TransferFeeConfig => account
             .get_extension::<extension::transfer_fee::TransferFeeConfig>()
@@ -50,10 +57,18 @@ pub fn parse_extension<S: BaseState>(
             .get_extension::<extension::confidential_transfer::ConfidentialTransferMint>()
             .map(|&extension| UiExtension::ConfidentialTransferMint(extension.into()))
             .unwrap_or(UiExtension::UnparseableExtension),
+        ExtensionType::ConfidentialTransferFeeConfig => account
+            .get_extension::<extension::confidential_transfer_fee::ConfidentialTransferFeeConfig>()
+            .map(|&extension| UiExtension::ConfidentialTransferFeeConfig(extension.into()))
+            .unwrap_or(UiExtension::UnparseableExtension),
         ExtensionType::ConfidentialTransferAccount => account
             .get_extension::<extension::confidential_transfer::ConfidentialTransferAccount>()
             .map(|&extension| UiExtension::ConfidentialTransferAccount(extension.into()))
             .unwrap_or(UiExtension::UnparseableExtension),
+        ExtensionType::ConfidentialTransferFeeAmount => account
+            .get_extension::<extension::confidential_transfer_fee::ConfidentialTransferFeeAmount>()
+            .map(|&extension| UiExtension::ConfidentialTransferFeeAmount(extension.into()))
+            .unwrap_or(UiExtension::UnparseableExtension),
         ExtensionType::DefaultAccountState => account
             .get_extension::<extension::default_account_state::DefaultAccountState>()
             .map(|&extension| UiExtension::DefaultAccountState(extension.into()))
@@ -77,6 +92,22 @@ pub fn parse_extension<S: BaseState>(
             .map(|&extension| UiExtension::PermanentDelegate(extension.into()))
             .unwrap_or(UiExtension::UnparseableExtension),
         ExtensionType::NonTransferableAccount => UiExtension::NonTransferableAccount,
+        ExtensionType::MetadataPointer => account
+            .get_extension::<extension::metadata_pointer::MetadataPointer>()
+            .map(|&extension| UiExtension::MetadataPointer(extension.into()))
+            .unwrap_or(UiExtension::UnparseableExtension),
+        ExtensionType::TokenMetadata => account
+            .get_variable_len_extension::<TokenMetadata>()
+            .map(|extension| UiExtension::TokenMetadata(extension.into()))
+            .unwrap_or(UiExtension::UnparseableExtension),
+        ExtensionType::TransferHook => account
+            .get_extension::<extension::transfer_hook::TransferHook>()
+            .map(|&extension| UiExtension::TransferHook(extension.into()))
+            .unwrap_or(UiExtension::UnparseableExtension),
+        ExtensionType::TransferHookAccount => account
+            .get_extension::<extension::transfer_hook::TransferHookAccount>()
+            .map(|&extension| UiExtension::TransferHookAccount(extension.into()))
+            .unwrap_or(UiExtension::UnparseableExtension),
     }
 }
 
@@ -251,9 +282,7 @@ impl From<extension::permanent_delegate::PermanentDelegate> for UiPermanentDeleg
 pub struct UiConfidentialTransferMint {
     pub authority: Option<String>,
     pub auto_approve_new_accounts: bool,
-    pub auditor_encryption_pubkey: Option<String>,
-    pub withdraw_withheld_authority_encryption_pubkey: Option<String>,
-    pub withheld_amount: String,
+    pub auditor_elgamal_pubkey: Option<String>,
 }
 
 impl From<extension::confidential_transfer::ConfidentialTransferMint>
@@ -263,19 +292,44 @@ impl From<extension::confidential_transfer::ConfidentialTransferMint>
         confidential_transfer_mint: extension::confidential_transfer::ConfidentialTransferMint,
     ) -> Self {
         let authority: Option<Pubkey> = confidential_transfer_mint.authority.into();
-        let auditor_encryption_pubkey: Option<ElGamalPubkey> =
-            confidential_transfer_mint.auditor_encryption_pubkey.into();
-        let withdraw_withheld_authority_encryption_pubkey: Option<ElGamalPubkey> =
-            confidential_transfer_mint
-                .withdraw_withheld_authority_encryption_pubkey
-                .into();
+        let auditor_elgamal_pubkey: Option<ElGamalPubkey> =
+            confidential_transfer_mint.auditor_elgamal_pubkey.into();
         Self {
             authority: authority.map(|pubkey| pubkey.to_string()),
             auto_approve_new_accounts: confidential_transfer_mint.auto_approve_new_accounts.into(),
-            auditor_encryption_pubkey: auditor_encryption_pubkey.map(|pubkey| pubkey.to_string()),
-            withdraw_withheld_authority_encryption_pubkey:
-                withdraw_withheld_authority_encryption_pubkey.map(|pubkey| pubkey.to_string()),
-            withheld_amount: format!("{}", confidential_transfer_mint.withheld_amount),
+            auditor_elgamal_pubkey: auditor_elgamal_pubkey.map(|pubkey| pubkey.to_string()),
+        }
+    }
+}
+
+#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
+#[serde(rename_all = "camelCase")]
+pub struct UiConfidentialTransferFeeConfig {
+    pub authority: Option<String>,
+    pub withdraw_withheld_authority_elgamal_pubkey: Option<String>,
+    pub harvest_to_mint_enabled: bool,
+    pub withheld_amount: String,
+}
+
+impl From<extension::confidential_transfer_fee::ConfidentialTransferFeeConfig>
+    for UiConfidentialTransferFeeConfig
+{
+    fn from(
+        confidential_transfer_fee_config: extension::confidential_transfer_fee::ConfidentialTransferFeeConfig,
+    ) -> Self {
+        let authority: Option<Pubkey> = confidential_transfer_fee_config.authority.into();
+        let withdraw_withheld_authority_elgamal_pubkey: Option<ElGamalPubkey> =
+            confidential_transfer_fee_config
+                .withdraw_withheld_authority_elgamal_pubkey
+                .into();
+        Self {
+            authority: authority.map(|pubkey| pubkey.to_string()),
+            withdraw_withheld_authority_elgamal_pubkey: withdraw_withheld_authority_elgamal_pubkey
+                .map(|pubkey| pubkey.to_string()),
+            harvest_to_mint_enabled: confidential_transfer_fee_config
+                .harvest_to_mint_enabled
+                .into(),
+            withheld_amount: format!("{}", confidential_transfer_fee_config.withheld_amount),
         }
     }
 }
@@ -284,7 +338,7 @@ impl From<extension::confidential_transfer::ConfidentialTransferMint>
 #[serde(rename_all = "camelCase")]
 pub struct UiConfidentialTransferAccount {
     pub approved: bool,
-    pub encryption_pubkey: String,
+    pub elgamal_pubkey: String,
     pub pending_balance_lo: String,
     pub pending_balance_hi: String,
     pub available_balance: String,
@@ -295,7 +349,6 @@ pub struct UiConfidentialTransferAccount {
     pub maximum_pending_balance_credit_counter: u64,
     pub expected_pending_balance_credit_counter: u64,
     pub actual_pending_balance_credit_counter: u64,
-    pub withheld_amount: String,
 }
 
 impl From<extension::confidential_transfer::ConfidentialTransferAccount>
@@ -306,7 +359,7 @@ impl From<extension::confidential_transfer::ConfidentialTransferAccount>
     ) -> Self {
         Self {
             approved: confidential_transfer_account.approved.into(),
-            encryption_pubkey: format!("{}", confidential_transfer_account.encryption_pubkey),
+            elgamal_pubkey: format!("{}", confidential_transfer_account.elgamal_pubkey),
             pending_balance_lo: format!("{}", confidential_transfer_account.pending_balance_lo),
             pending_balance_hi: format!("{}", confidential_transfer_account.pending_balance_hi),
             available_balance: format!("{}", confidential_transfer_account.available_balance),
@@ -332,7 +385,99 @@ impl From<extension::confidential_transfer::ConfidentialTransferAccount>
             actual_pending_balance_credit_counter: confidential_transfer_account
                 .actual_pending_balance_credit_counter
                 .into(),
-            withheld_amount: format!("{}", confidential_transfer_account.withheld_amount),
+        }
+    }
+}
+
+#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
+#[serde(rename_all = "camelCase")]
+pub struct UiConfidentialTransferFeeAmount {
+    pub withheld_amount: String,
+}
+
+impl From<extension::confidential_transfer_fee::ConfidentialTransferFeeAmount>
+    for UiConfidentialTransferFeeAmount
+{
+    fn from(
+        confidential_transfer_fee_amount: extension::confidential_transfer_fee::ConfidentialTransferFeeAmount,
+    ) -> Self {
+        Self {
+            withheld_amount: format!("{}", confidential_transfer_fee_amount.withheld_amount),
+        }
+    }
+}
+
+#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
+#[serde(rename_all = "camelCase")]
+pub struct UiMetadataPointer {
+    pub authority: Option<String>,
+    pub metadata_address: Option<String>,
+}
+
+impl From<extension::metadata_pointer::MetadataPointer> for UiMetadataPointer {
+    fn from(metadata_pointer: extension::metadata_pointer::MetadataPointer) -> Self {
+        let authority: Option<Pubkey> = metadata_pointer.authority.into();
+        let metadata_address: Option<Pubkey> = metadata_pointer.metadata_address.into();
+        Self {
+            authority: authority.map(|pubkey| pubkey.to_string()),
+            metadata_address: metadata_address.map(|pubkey| pubkey.to_string()),
+        }
+    }
+}
+
+#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
+#[serde(rename_all = "camelCase")]
+pub struct UiTokenMetadata {
+    pub update_authority: Option<String>,
+    pub mint: String,
+    pub name: String,
+    pub symbol: String,
+    pub uri: String,
+    pub additional_metadata: Vec<(String, String)>,
+}
+
+impl From<TokenMetadata> for UiTokenMetadata {
+    fn from(token_metadata: TokenMetadata) -> Self {
+        let update_authority: Option<Pubkey> = token_metadata.update_authority.into();
+        Self {
+            update_authority: update_authority.map(|pubkey| pubkey.to_string()),
+            mint: token_metadata.mint.to_string(),
+            name: token_metadata.name,
+            symbol: token_metadata.symbol,
+            uri: token_metadata.uri,
+            additional_metadata: token_metadata.additional_metadata,
+        }
+    }
+}
+
+#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
+#[serde(rename_all = "camelCase")]
+pub struct UiTransferHook {
+    pub authority: Option<String>,
+    pub program_id: Option<String>,
+}
+
+impl From<extension::transfer_hook::TransferHook> for UiTransferHook {
+    fn from(transfer_hook: extension::transfer_hook::TransferHook) -> Self {
+        let authority: Option<Pubkey> = transfer_hook.authority.into();
+        let program_id: Option<Pubkey> = transfer_hook.program_id.into();
+        Self {
+            authority: authority.map(|pubkey| pubkey.to_string()),
+            program_id: program_id.map(|pubkey| pubkey.to_string()),
+        }
+    }
+}
+
+#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
+#[serde(rename_all = "camelCase")]
+pub struct UiTransferHookAccount {
+    pub transferring: bool,
+}
+
+impl From<extension::transfer_hook::TransferHookAccount> for UiTransferHookAccount {
+    fn from(transfer_hook: extension::transfer_hook::TransferHookAccount) -> Self {
+        Self {
+            transferring: transfer_hook.transferring.into(),
         }
     }
 }

+ 1 - 1
fetch-spl.sh

@@ -45,7 +45,7 @@ fetch_program() {
 }
 
 fetch_program token 3.5.0 TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA BPFLoader2111111111111111111111111111111111
-fetch_program token-2022 0.6.0 TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb BPFLoaderUpgradeab1e11111111111111111111111
+fetch_program token-2022 0.9.0 TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb BPFLoaderUpgradeab1e11111111111111111111111
 fetch_program memo  1.0.0 Memo1UhkJRfHyvLMcVucJwxXeuD728EqVDDwQDxFMNo BPFLoader1111111111111111111111111111111111
 fetch_program memo  3.0.0 MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr BPFLoader2111111111111111111111111111111111
 fetch_program associated-token-account 1.1.2 ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL BPFLoader2111111111111111111111111111111111

+ 1 - 0
ledger/Cargo.toml

@@ -78,6 +78,7 @@ features = ["lz4"]
 bs58 = { workspace = true }
 solana-account-decoder = { workspace = true }
 solana-logger = { workspace = true }
+spl-pod = { workspace = true }
 test-case = { workspace = true }
 
 [build-dependencies]

+ 9 - 6
ledger/src/token_balances.rs

@@ -121,12 +121,12 @@ mod test {
     use {
         super::*,
         solana_sdk::{account::Account, genesis_config::create_genesis_config},
+        spl_pod::optional_keys::OptionalNonZeroPubkey,
         spl_token_2022::{
             extension::{
                 immutable_owner::ImmutableOwner, memo_transfer::MemoTransfer,
                 mint_close_authority::MintCloseAuthority, ExtensionType, StateWithExtensionsMut,
             },
-            pod::OptionalNonZeroPubkey,
             solana_program::{program_option::COption, program_pack::Pack},
         },
         std::collections::BTreeMap,
@@ -291,7 +291,8 @@ mod test {
 
         let mint_authority = Pubkey::new_unique();
         let mint_size =
-            ExtensionType::get_account_len::<Mint>(&[ExtensionType::MintCloseAuthority]);
+            ExtensionType::try_calculate_account_len::<Mint>(&[ExtensionType::MintCloseAuthority])
+                .unwrap();
         let mint_base = Mint {
             mint_authority: COption::None,
             supply: 4242,
@@ -339,10 +340,11 @@ mod test {
             delegated_amount: 0,
             close_authority: COption::None,
         };
-        let account_size = ExtensionType::get_account_len::<TokenAccount>(&[
+        let account_size = ExtensionType::try_calculate_account_len::<TokenAccount>(&[
             ExtensionType::ImmutableOwner,
             ExtensionType::MemoTransfer,
-        ]);
+        ])
+        .unwrap();
         let mut account_data = vec![0; account_size];
         let mut account_state =
             StateWithExtensionsMut::<TokenAccount>::unpack_uninitialized(&mut account_data)
@@ -381,10 +383,11 @@ mod test {
             delegated_amount: 0,
             close_authority: COption::None,
         };
-        let account_size = ExtensionType::get_account_len::<TokenAccount>(&[
+        let account_size = ExtensionType::try_calculate_account_len::<TokenAccount>(&[
             ExtensionType::ImmutableOwner,
             ExtensionType::MemoTransfer,
-        ]);
+        ])
+        .unwrap();
         let mut account_data = vec![0; account_size];
         let mut account_state =
             StateWithExtensionsMut::<TokenAccount>::unpack_uninitialized(&mut account_data)

+ 1 - 1
program-test/src/programs.rs

@@ -30,7 +30,7 @@ static SPL_PROGRAMS: &[(Pubkey, Pubkey, &[u8])] = &[
     (
         spl_token_2022::ID,
         solana_sdk::bpf_loader_upgradeable::ID,
-        include_bytes!("programs/spl_token_2022-0.6.0.so"),
+        include_bytes!("programs/spl_token_2022-0.9.0.so"),
     ),
     (
         spl_memo_1_0::ID,

二進制
program-test/src/programs/spl_token_2022-0.6.0.so


二進制
program-test/src/programs/spl_token_2022-0.9.0.so


+ 183 - 37
programs/sbf/Cargo.lock

@@ -2918,6 +2918,17 @@ dependencies = [
  "syn 1.0.109",
 ]
 
+[[package]]
+name = "num-derive"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9e6a0fd4f737c707bd9086cc16c925f294943eb62eb71499e9fd4cf71f8b9f4e"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.37",
+]
+
 [[package]]
 name = "num-integer"
 version = "0.1.42"
@@ -2972,39 +2983,39 @@ dependencies = [
 
 [[package]]
 name = "num_enum"
-version = "0.5.9"
+version = "0.6.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8d829733185c1ca374f17e52b762f24f535ec625d2cc1f070e34c8a9068f341b"
+checksum = "7a015b430d3c108a207fd776d2e2196aaf8b1cf8cf93253e3a097ff3085076a1"
 dependencies = [
- "num_enum_derive 0.5.9",
+ "num_enum_derive 0.6.1",
 ]
 
 [[package]]
 name = "num_enum"
-version = "0.6.1"
+version = "0.7.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7a015b430d3c108a207fd776d2e2196aaf8b1cf8cf93253e3a097ff3085076a1"
+checksum = "70bf6736f74634d299d00086f02986875b3c2d924781a6a2cb6c201e73da0ceb"
 dependencies = [
- "num_enum_derive 0.6.1",
+ "num_enum_derive 0.7.0",
 ]
 
 [[package]]
 name = "num_enum_derive"
-version = "0.5.9"
+version = "0.6.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2be1598bf1c313dcdd12092e3f1920f463462525a21b7b4e11b4168353d0123e"
+checksum = "96667db765a921f7b295ffee8b60472b686a51d4f21c2ee4ffdb94c7013b65a6"
 dependencies = [
  "proc-macro-crate 1.1.3",
  "proc-macro2",
  "quote",
- "syn 1.0.109",
+ "syn 2.0.37",
 ]
 
 [[package]]
 name = "num_enum_derive"
-version = "0.6.1"
+version = "0.7.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "96667db765a921f7b295ffee8b60472b686a51d4f21c2ee4ffdb94c7013b65a6"
+checksum = "56ea360eafe1022f7cc56cd7b869ed57330fb2453d0c7831d99b74c65d2f5597"
 dependencies = [
  "proc-macro-crate 1.1.3",
  "proc-macro2",
@@ -4450,6 +4461,7 @@ dependencies = [
  "solana-sdk",
  "spl-token",
  "spl-token-2022",
+ "spl-token-metadata-interface",
  "thiserror",
  "zstd",
 ]
@@ -4478,7 +4490,7 @@ dependencies = [
  "lz4",
  "memmap2",
  "modular-bitfield",
- "num-derive",
+ "num-derive 0.3.0",
  "num-traits",
  "num_cpus",
  "num_enum 0.6.1",
@@ -4518,7 +4530,7 @@ dependencies = [
  "bincode",
  "bytemuck",
  "log",
- "num-derive",
+ "num-derive 0.3.0",
  "num-traits",
  "rustc_version",
  "serde",
@@ -5241,7 +5253,7 @@ dependencies = [
  "log",
  "memoffset 0.9.0",
  "num-bigint 0.4.4",
- "num-derive",
+ "num-derive 0.3.0",
  "num-traits",
  "parking_lot 0.12.1",
  "rand 0.8.5",
@@ -5273,7 +5285,7 @@ dependencies = [
  "itertools",
  "libc",
  "log",
- "num-derive",
+ "num-derive 0.3.0",
  "num-traits",
  "percentage",
  "rand 0.8.5",
@@ -5378,7 +5390,7 @@ dependencies = [
  "console",
  "dialoguer",
  "log",
- "num-derive",
+ "num-derive 0.3.0",
  "num-traits",
  "parking_lot 0.12.1",
  "qstring",
@@ -5525,7 +5537,7 @@ dependencies = [
  "lz4",
  "memmap2",
  "modular-bitfield",
- "num-derive",
+ "num-derive 0.3.0",
  "num-traits",
  "num_cpus",
  "num_enum 0.6.1",
@@ -5695,7 +5707,7 @@ dependencies = [
 name = "solana-sbf-rust-error-handling"
 version = "1.17.0"
 dependencies = [
- "num-derive",
+ "num-derive 0.3.0",
  "num-traits",
  "solana-program",
  "thiserror",
@@ -6020,7 +6032,7 @@ dependencies = [
  "libsecp256k1 0.6.0",
  "log",
  "memmap2",
- "num-derive",
+ "num-derive 0.3.0",
  "num-traits",
  "num_enum 0.6.1",
  "pbkdf2 0.11.0",
@@ -6246,7 +6258,7 @@ dependencies = [
  "Inflector",
  "base64 0.21.4",
  "bincode",
- "borsh 0.9.3",
+ "borsh 0.10.3",
  "bs58",
  "lazy_static",
  "log",
@@ -6409,7 +6421,7 @@ version = "1.17.0"
 dependencies = [
  "bincode",
  "log",
- "num-derive",
+ "num-derive 0.3.0",
  "num-traits",
  "rustc_version",
  "serde",
@@ -6428,7 +6440,7 @@ name = "solana-zk-token-proof-program"
 version = "1.17.0"
 dependencies = [
  "bytemuck",
- "num-derive",
+ "num-derive 0.3.0",
  "num-traits",
  "solana-program-runtime",
  "solana-sdk",
@@ -6449,7 +6461,7 @@ dependencies = [
  "itertools",
  "lazy_static",
  "merlin",
- "num-derive",
+ "num-derive 0.3.0",
  "num-traits",
  "rand 0.7.3",
  "serde",
@@ -6505,13 +6517,13 @@ dependencies = [
 
 [[package]]
 name = "spl-associated-token-account"
-version = "1.1.3"
+version = "2.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "978dba3bcbe88d0c2c58366c254d9ea41c5f73357e72fc0bdee4d6b5fc99c8f4"
+checksum = "385e31c29981488f2820b2022d8e731aae3b02e6e18e2fd854e4c9a94dc44fc3"
 dependencies = [
  "assert_matches",
- "borsh 0.9.3",
- "num-derive",
+ "borsh 0.10.3",
+ "num-derive 0.4.0",
  "num-traits",
  "solana-program",
  "spl-token",
@@ -6519,48 +6531,182 @@ dependencies = [
  "thiserror",
 ]
 
+[[package]]
+name = "spl-discriminator"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cce5d563b58ef1bb2cdbbfe0dfb9ffdc24903b10ae6a4df2d8f425ece375033f"
+dependencies = [
+ "bytemuck",
+ "solana-program",
+ "spl-discriminator-derive",
+]
+
+[[package]]
+name = "spl-discriminator-derive"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fadbefec4f3c678215ca72bd71862697bb06b41fd77c0088902dd3203354387b"
+dependencies = [
+ "quote",
+ "spl-discriminator-syn",
+ "syn 2.0.37",
+]
+
+[[package]]
+name = "spl-discriminator-syn"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0e5f2044ca42c8938d54d1255ce599c79a1ffd86b677dfab695caa20f9ffc3f2"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "sha2 0.10.7",
+ "syn 2.0.37",
+ "thiserror",
+]
+
 [[package]]
 name = "spl-memo"
-version = "3.0.1"
+version = "4.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f0f180b03318c3dbab3ef4e1e4d46d5211ae3c780940dd0a28695aba4b59a75a"
+dependencies = [
+ "solana-program",
+]
+
+[[package]]
+name = "spl-pod"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2881dddfca792737c0706fa0175345ab282b1b0879c7d877bad129645737c079"
+dependencies = [
+ "borsh 0.10.3",
+ "bytemuck",
+ "solana-program",
+ "solana-zk-token-sdk",
+ "spl-program-error",
+]
+
+[[package]]
+name = "spl-program-error"
+version = "0.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bd0dc6f70db6bacea7ff25870b016a65ba1d1b6013536f08e4fd79a8f9005325"
+checksum = "249e0318493b6bcf27ae9902600566c689b7dfba9f1bdff5893e92253374e78c"
 dependencies = [
+ "num-derive 0.4.0",
+ "num-traits",
  "solana-program",
+ "spl-program-error-derive",
+ "thiserror",
+]
+
+[[package]]
+name = "spl-program-error-derive"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ab5269c8e868da17b6552ef35a51355a017bd8e0eae269c201fef830d35fa52c"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "sha2 0.10.7",
+ "syn 2.0.37",
+]
+
+[[package]]
+name = "spl-tlv-account-resolution"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "062e148d3eab7b165582757453632ffeef490c02c86a48bfdb4988f63eefb3b9"
+dependencies = [
+ "bytemuck",
+ "solana-program",
+ "spl-discriminator",
+ "spl-pod",
+ "spl-program-error",
+ "spl-type-length-value",
 ]
 
 [[package]]
 name = "spl-token"
-version = "3.5.0"
+version = "4.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8e85e168a785e82564160dcb87b2a8e04cee9bfd1f4d488c729d53d6a4bd300d"
+checksum = "08459ba1b8f7c1020b4582c4edf0f5c7511a5e099a7a97570c9698d4f2337060"
 dependencies = [
  "arrayref",
  "bytemuck",
- "num-derive",
+ "num-derive 0.3.0",
  "num-traits",
- "num_enum 0.5.9",
+ "num_enum 0.6.1",
  "solana-program",
  "thiserror",
 ]
 
 [[package]]
 name = "spl-token-2022"
-version = "0.6.1"
+version = "0.9.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0043b590232c400bad5ee9eb983ced003d15163c4c5d56b090ac6d9a57457b47"
+checksum = "e4abf34a65ba420584a0c35f3903f8d727d1f13ababbdc3f714c6b065a686e86"
 dependencies = [
  "arrayref",
  "bytemuck",
- "num-derive",
+ "num-derive 0.4.0",
  "num-traits",
- "num_enum 0.5.9",
+ "num_enum 0.7.0",
  "solana-program",
  "solana-zk-token-sdk",
  "spl-memo",
+ "spl-pod",
  "spl-token",
+ "spl-token-metadata-interface",
+ "spl-transfer-hook-interface",
+ "spl-type-length-value",
  "thiserror",
 ]
 
+[[package]]
+name = "spl-token-metadata-interface"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4c16ce3ba6979645fb7627aa1e435576172dd63088dc7848cb09aa331fa1fe4f"
+dependencies = [
+ "borsh 0.10.3",
+ "solana-program",
+ "spl-discriminator",
+ "spl-pod",
+ "spl-program-error",
+ "spl-type-length-value",
+]
+
+[[package]]
+name = "spl-transfer-hook-interface"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "051d31803f873cabe71aec3c1b849f35248beae5d19a347d93a5c9cccc5d5a9b"
+dependencies = [
+ "arrayref",
+ "bytemuck",
+ "solana-program",
+ "spl-discriminator",
+ "spl-pod",
+ "spl-program-error",
+ "spl-tlv-account-resolution",
+ "spl-type-length-value",
+]
+
+[[package]]
+name = "spl-type-length-value"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a468e6f6371f9c69aae760186ea9f1a01c2908351b06a5e0026d21cfc4d7ecac"
+dependencies = [
+ "bytemuck",
+ "solana-program",
+ "spl-discriminator",
+ "spl-pod",
+ "spl-program-error",
+]
+
 [[package]]
 name = "static_assertions"
 version = "1.1.0"

+ 2 - 0
programs/sbf/Cargo.toml

@@ -171,8 +171,10 @@ targets = ["x86_64-unknown-linux-gnu"]
 #  * spl-associated-token-account
 #  * spl-instruction-padding
 #  * spl-memo
+#  * spl-pod
 #  * spl-token
 #  * spl-token-2022
+#  * spl-token-metadata-interface
 #
 # They are included indirectly, for example, `account-decoder` depends on
 #

+ 1 - 0
rpc/Cargo.toml

@@ -64,6 +64,7 @@ tokio-util = { workspace = true, features = ["codec", "compat"] }
 serial_test = { workspace = true }
 solana-net-utils = { workspace = true }
 solana-stake-program = { workspace = true }
+spl-pod = { workspace = true }
 symlink = { workspace = true }
 
 [lib]

+ 15 - 9
rpc/src/rpc.rs

@@ -4683,12 +4683,12 @@ pub mod tests {
             vote_instruction,
             vote_state::{self, Vote, VoteInit, VoteStateVersions, MAX_LOCKOUT_HISTORY},
         },
+        spl_pod::optional_keys::OptionalNonZeroPubkey,
         spl_token_2022::{
             extension::{
                 immutable_owner::ImmutableOwner, memo_transfer::MemoTransfer,
                 mint_close_authority::MintCloseAuthority, ExtensionType, StateWithExtensionsMut,
             },
-            pod::OptionalNonZeroPubkey,
             solana_program::{program_option::COption, pubkey::Pubkey as SplTokenPubkey},
             state::{AccountState as TokenAccountState, Mint},
         },
@@ -7439,10 +7439,11 @@ pub mod tests {
                     delegated_amount: 30,
                     close_authority: COption::Some(owner),
                 };
-                let account_size = ExtensionType::get_account_len::<TokenAccount>(&[
+                let account_size = ExtensionType::try_calculate_account_len::<TokenAccount>(&[
                     ExtensionType::ImmutableOwner,
                     ExtensionType::MemoTransfer,
-                ]);
+                ])
+                .unwrap();
                 let mut account_data = vec![0; account_size];
                 let mut account_state =
                     StateWithExtensionsMut::<TokenAccount>::unpack_uninitialized(&mut account_data)
@@ -7466,8 +7467,10 @@ pub mod tests {
                 bank.store_account(&token_account_pubkey, &token_account);
 
                 // Add the mint
-                let mint_size =
-                    ExtensionType::get_account_len::<Mint>(&[ExtensionType::MintCloseAuthority]);
+                let mint_size = ExtensionType::try_calculate_account_len::<Mint>(&[
+                    ExtensionType::MintCloseAuthority,
+                ])
+                .unwrap();
                 let mint_base = Mint {
                     mint_authority: COption::Some(owner),
                     supply: 500,
@@ -7931,10 +7934,11 @@ pub mod tests {
                     delegated_amount: 30,
                     close_authority: COption::Some(owner),
                 };
-                let account_size = ExtensionType::get_account_len::<TokenAccount>(&[
+                let account_size = ExtensionType::try_calculate_account_len::<TokenAccount>(&[
                     ExtensionType::ImmutableOwner,
                     ExtensionType::MemoTransfer,
-                ]);
+                ])
+                .unwrap();
                 let mut account_data = vec![0; account_size];
                 let mut account_state =
                     StateWithExtensionsMut::<TokenAccount>::unpack_uninitialized(&mut account_data)
@@ -7957,8 +7961,10 @@ pub mod tests {
                 });
                 bank.store_account(&token_account_pubkey, &token_account);
 
-                let mint_size =
-                    ExtensionType::get_account_len::<Mint>(&[ExtensionType::MintCloseAuthority]);
+                let mint_size = ExtensionType::try_calculate_account_len::<Mint>(&[
+                    ExtensionType::MintCloseAuthority,
+                ])
+                .unwrap();
                 let mint_base = Mint {
                     mint_authority: COption::Some(owner),
                     supply: 500,

+ 1 - 2
transaction-status/Cargo.toml

@@ -13,8 +13,7 @@ edition = { workspace = true }
 Inflector = { workspace = true }
 base64 = { workspace = true }
 bincode = { workspace = true }
-# NOTE: Use the workspace version once spl-associated-token-account uses borsh 0.10.
-borsh0-9 = { package = "borsh", version = "0.9.3" }
+borsh = { workspace = true }
 bs58 = { workspace = true }
 lazy_static = { workspace = true }
 log = { workspace = true }

+ 1 - 1
transaction-status/src/parse_associated_token.rs

@@ -2,7 +2,7 @@ use {
     crate::parse_instruction::{
         check_num_accounts, ParsableProgram, ParseInstructionError, ParsedInstructionEnum,
     },
-    borsh0_9::BorshDeserialize,
+    borsh::BorshDeserialize,
     serde_json::json,
     solana_sdk::{instruction::CompiledInstruction, message::AccountKeys, pubkey::Pubkey},
     spl_associated_token_account::instruction::AssociatedTokenAccountInstruction,

+ 88 - 4
transaction-status/src/parse_token.rs

@@ -3,9 +3,10 @@ use {
         check_num_accounts, ParsableProgram, ParseInstructionError, ParsedInstructionEnum,
     },
     extension::{
-        confidential_transfer::*, cpi_guard::*, default_account_state::*, interest_bearing_mint::*,
-        memo_transfer::*, mint_close_authority::*, permanent_delegate::*, reallocate::*,
-        transfer_fee::*,
+        confidential_transfer::*, confidential_transfer_fee::*, cpi_guard::*,
+        default_account_state::*, interest_bearing_mint::*, memo_transfer::*, metadata_pointer::*,
+        mint_close_authority::*, permanent_delegate::*, reallocate::*, transfer_fee::*,
+        transfer_hook::*,
     },
     serde_json::{json, Map, Value},
     solana_account_decoder::parse_token::{token_amount_to_ui_amount, UiAccountState},
@@ -229,7 +230,10 @@ pub fn parse_token(
                 | AuthorityType::CloseMint
                 | AuthorityType::InterestRate
                 | AuthorityType::PermanentDelegate
-                | AuthorityType::ConfidentialTransferMint => "mint",
+                | AuthorityType::ConfidentialTransferMint
+                | AuthorityType::TransferHookProgramId
+                | AuthorityType::ConfidentialTransferFeeConfig
+                | AuthorityType::MetadataPointer => "mint",
                 AuthorityType::AccountOwner | AuthorityType::CloseAccount => "account",
             };
             let mut value = json!({
@@ -590,6 +594,62 @@ pub fn parse_token(
                 account_keys,
             )
         }
+        TokenInstruction::TransferHookExtension => {
+            if instruction.data.len() < 2 {
+                return Err(ParseInstructionError::InstructionNotParsable(
+                    ParsableProgram::SplToken,
+                ));
+            }
+            parse_transfer_hook_instruction(
+                &instruction.data[1..],
+                &instruction.accounts,
+                account_keys,
+            )
+        }
+        TokenInstruction::ConfidentialTransferFeeExtension => {
+            if instruction.data.len() < 2 {
+                return Err(ParseInstructionError::InstructionNotParsable(
+                    ParsableProgram::SplToken,
+                ));
+            }
+            parse_confidential_transfer_fee_instruction(
+                &instruction.data[1..],
+                &instruction.accounts,
+                account_keys,
+            )
+        }
+        TokenInstruction::WithdrawExcessLamports => {
+            check_num_token_accounts(&instruction.accounts, 3)?;
+            let mut value = json!({
+                "source": account_keys[instruction.accounts[0] as usize].to_string(),
+                "destination": account_keys[instruction.accounts[1] as usize].to_string(),
+            });
+            let map = value.as_object_mut().unwrap();
+            parse_signers(
+                map,
+                2,
+                account_keys,
+                &instruction.accounts,
+                "authority",
+                "multisigAuthority",
+            );
+            Ok(ParsedInstructionEnum {
+                instruction_type: "withdrawExcessLamports".to_string(),
+                info: value,
+            })
+        }
+        TokenInstruction::MetadataPointerExtension => {
+            if instruction.data.len() < 2 {
+                return Err(ParseInstructionError::InstructionNotParsable(
+                    ParsableProgram::SplToken,
+                ));
+            }
+            parse_metadata_pointer_instruction(
+                &instruction.data[1..],
+                &instruction.accounts,
+                account_keys,
+            )
+        }
     }
 }
 
@@ -606,6 +666,9 @@ pub enum UiAuthorityType {
     InterestRate,
     PermanentDelegate,
     ConfidentialTransferMint,
+    TransferHookProgramId,
+    ConfidentialTransferFeeConfig,
+    MetadataPointer,
 }
 
 impl From<AuthorityType> for UiAuthorityType {
@@ -621,6 +684,11 @@ impl From<AuthorityType> for UiAuthorityType {
             AuthorityType::InterestRate => UiAuthorityType::InterestRate,
             AuthorityType::PermanentDelegate => UiAuthorityType::PermanentDelegate,
             AuthorityType::ConfidentialTransferMint => UiAuthorityType::ConfidentialTransferMint,
+            AuthorityType::TransferHookProgramId => UiAuthorityType::TransferHookProgramId,
+            AuthorityType::ConfidentialTransferFeeConfig => {
+                UiAuthorityType::ConfidentialTransferFeeConfig
+            }
+            AuthorityType::MetadataPointer => UiAuthorityType::MetadataPointer,
         }
     }
 }
@@ -642,6 +710,12 @@ pub enum UiExtensionType {
     CpiGuard,
     PermanentDelegate,
     NonTransferableAccount,
+    TransferHook,
+    TransferHookAccount,
+    ConfidentialTransferFeeConfig,
+    ConfidentialTransferFeeAmount,
+    MetadataPointer,
+    TokenMetadata,
 }
 
 impl From<ExtensionType> for UiExtensionType {
@@ -663,6 +737,16 @@ impl From<ExtensionType> for UiExtensionType {
             ExtensionType::CpiGuard => UiExtensionType::CpiGuard,
             ExtensionType::PermanentDelegate => UiExtensionType::PermanentDelegate,
             ExtensionType::NonTransferableAccount => UiExtensionType::NonTransferableAccount,
+            ExtensionType::TransferHook => UiExtensionType::TransferHook,
+            ExtensionType::TransferHookAccount => UiExtensionType::TransferHookAccount,
+            ExtensionType::ConfidentialTransferFeeConfig => {
+                UiExtensionType::ConfidentialTransferFeeConfig
+            }
+            ExtensionType::ConfidentialTransferFeeAmount => {
+                UiExtensionType::ConfidentialTransferFeeAmount
+            }
+            ExtensionType::MetadataPointer => UiExtensionType::MetadataPointer,
+            ExtensionType::TokenMetadata => UiExtensionType::TokenMetadata,
         }
     }
 }

+ 25 - 73
transaction-status/src/parse_token/extension/confidential_transfer.rs

@@ -192,8 +192,8 @@ pub(in crate::parse_token) fn parse_confidential_transfer_instruction(
             let proof_instruction_offset: i8 = transfer_data.proof_instruction_offset;
             let mut value = json!({
                 "source": account_keys[account_indexes[0] as usize].to_string(),
-                "destination": account_keys[account_indexes[1] as usize].to_string(),
-                "mint": account_keys[account_indexes[2] as usize].to_string(),
+                "mint": account_keys[account_indexes[1] as usize].to_string(),
+                "destination": account_keys[account_indexes[2] as usize].to_string(),
                 "instructionsSysvar": account_keys[account_indexes[3] as usize].to_string(),
                 "newSourceDecryptableAvailableBalance": format!("{}", transfer_data.new_source_decryptable_available_balance),
                 "proofInstructionOffset": proof_instruction_offset,
@@ -322,85 +322,37 @@ pub(in crate::parse_token) fn parse_confidential_transfer_instruction(
                 info: value,
             })
         }
-        ConfidentialTransferInstruction::WithdrawWithheldTokensFromMint => {
-            check_num_token_accounts(account_indexes, 4)?;
-            let withdraw_withheld_data: WithdrawWithheldTokensFromMintData =
-                *decode_instruction_data(instruction_data).map_err(|_| {
-                    ParseInstructionError::InstructionNotParsable(ParsableProgram::SplToken)
-                })?;
-            let proof_instruction_offset: i8 = withdraw_withheld_data.proof_instruction_offset;
-            let mut value = json!({
-                "mint": account_keys[account_indexes[0] as usize].to_string(),
-                "feeRecipient": account_keys[account_indexes[1] as usize].to_string(),
-                "instructionsSysvar": account_keys[account_indexes[2] as usize].to_string(),
-                "proofInstructionOffset": proof_instruction_offset,
-
-            });
-            let map = value.as_object_mut().unwrap();
-            parse_signers(
-                map,
-                3,
-                account_keys,
-                account_indexes,
-                "withdrawWithheldAuthority",
-                "multisigWithdrawWithheldAuthority",
-            );
-            Ok(ParsedInstructionEnum {
-                instruction_type: "withdrawWithheldConfidentialTransferTokensFromMint".to_string(),
-                info: value,
-            })
-        }
-        ConfidentialTransferInstruction::WithdrawWithheldTokensFromAccounts => {
-            let withdraw_withheld_data: WithdrawWithheldTokensFromAccountsData =
+        ConfidentialTransferInstruction::TransferWithSplitProofs => {
+            check_num_token_accounts(account_indexes, 7)?;
+            let transfer_data: TransferWithSplitProofsInstructionData =
                 *decode_instruction_data(instruction_data).map_err(|_| {
                     ParseInstructionError::InstructionNotParsable(ParsableProgram::SplToken)
                 })?;
-            let num_token_accounts = withdraw_withheld_data.num_token_accounts;
-            check_num_token_accounts(account_indexes, 4 + num_token_accounts as usize)?;
-            let proof_instruction_offset: i8 = withdraw_withheld_data.proof_instruction_offset;
-            let mut value = json!({
-                "mint": account_keys[account_indexes[0] as usize].to_string(),
-                "feeRecipient": account_keys[account_indexes[1] as usize].to_string(),
-                "instructionsSysvar": account_keys[account_indexes[2] as usize].to_string(),
-                "proofInstructionOffset": proof_instruction_offset,
-            });
-            let map = value.as_object_mut().unwrap();
-            let mut source_accounts: Vec<String> = vec![];
-            let first_source_account_index = account_indexes
-                .len()
-                .saturating_sub(num_token_accounts as usize);
-            for i in account_indexes[first_source_account_index..].iter() {
-                source_accounts.push(account_keys[*i as usize].to_string());
-            }
-            map.insert("sourceAccounts".to_string(), json!(source_accounts));
-            parse_signers(
-                map,
-                3,
-                account_keys,
-                &account_indexes[..first_source_account_index],
-                "withdrawWithheldAuthority",
-                "multisigWithdrawWithheldAuthority",
-            );
-            Ok(ParsedInstructionEnum {
-                instruction_type: "withdrawWithheldConfidentialTransferTokensFromAccounts"
-                    .to_string(),
-                info: value,
-            })
-        }
-        ConfidentialTransferInstruction::HarvestWithheldTokensToMint => {
-            check_num_token_accounts(account_indexes, 1)?;
             let mut value = json!({
-                "mint": account_keys[account_indexes[0] as usize].to_string(),
-
+                "source": account_keys[account_indexes[0] as usize].to_string(),
+                "mint": account_keys[account_indexes[1] as usize].to_string(),
+                "destination": account_keys[account_indexes[2] as usize].to_string(),
+                "ciphertextCommitmentEqualityContext": account_keys[account_indexes[3] as usize].to_string(),
+                "batchedGroupedCiphertext2HandlesValidityContext": account_keys[account_indexes[4] as usize].to_string(),
+                "batchedRangeProofContext": account_keys[account_indexes[5] as usize].to_string(),
+                "owner": account_keys[account_indexes[6] as usize].to_string(),
+                "newSourceDecryptableAvailableBalance": format!("{}", transfer_data.new_source_decryptable_available_balance),
+                "noOpOnUninitializedSplitContextState": bool::from(transfer_data.no_op_on_uninitialized_split_context_state),
+                "closeSplitContextStateOnExecution": bool::from(transfer_data.close_split_context_state_on_execution),
             });
             let map = value.as_object_mut().unwrap();
-            let mut source_accounts: Vec<String> = vec![];
-            for i in account_indexes.iter().skip(1) {
-                source_accounts.push(account_keys[*i as usize].to_string());
+            if transfer_data.close_split_context_state_on_execution.into() {
+                map.insert(
+                    "lamportDestination".to_string(),
+                    json!(account_keys[account_indexes[7] as usize].to_string()),
+                );
+                map.insert(
+                    "contextStateOwner".to_string(),
+                    json!(account_keys[account_indexes[8] as usize].to_string()),
+                );
             }
-            map.insert("sourceAccounts".to_string(), json!(source_accounts));
             Ok(ParsedInstructionEnum {
-                instruction_type: "harvestWithheldConfidentialTransferTokensToMint".to_string(),
+                instruction_type: "confidentialTransferWithSplitProofs".to_string(),
                 info: value,
             })
         }

+ 159 - 0
transaction-status/src/parse_token/extension/confidential_transfer_fee.rs

@@ -0,0 +1,159 @@
+use {
+    super::*,
+    solana_account_decoder::parse_token_extension::UiConfidentialTransferFeeConfig,
+    spl_token_2022::{
+        extension::confidential_transfer_fee::{instruction::*, ConfidentialTransferFeeConfig},
+        instruction::{decode_instruction_data, decode_instruction_type},
+    },
+};
+
+pub(in crate::parse_token) fn parse_confidential_transfer_fee_instruction(
+    instruction_data: &[u8],
+    account_indexes: &[u8],
+    account_keys: &AccountKeys,
+) -> Result<ParsedInstructionEnum, ParseInstructionError> {
+    match decode_instruction_type(instruction_data)
+        .map_err(|_| ParseInstructionError::InstructionNotParsable(ParsableProgram::SplToken))?
+    {
+        ConfidentialTransferFeeInstruction::InitializeConfidentialTransferFeeConfig => {
+            check_num_token_accounts(account_indexes, 1)?;
+            let confidential_transfer_mint: ConfidentialTransferFeeConfig =
+                *decode_instruction_data(instruction_data).map_err(|_| {
+                    ParseInstructionError::InstructionNotParsable(ParsableProgram::SplToken)
+                })?;
+            let confidential_transfer_mint: UiConfidentialTransferFeeConfig =
+                confidential_transfer_mint.into();
+            let mut value = json!({
+                "mint": account_keys[account_indexes[0] as usize].to_string(),
+            });
+            let map = value.as_object_mut().unwrap();
+            map.append(json!(confidential_transfer_mint).as_object_mut().unwrap());
+            Ok(ParsedInstructionEnum {
+                instruction_type: "initializeConfidentialTransferFeeConfig".to_string(),
+                info: value,
+            })
+        }
+        ConfidentialTransferFeeInstruction::WithdrawWithheldTokensFromMint => {
+            check_num_token_accounts(account_indexes, 4)?;
+            let withdraw_withheld_data: WithdrawWithheldTokensFromMintData =
+                *decode_instruction_data(instruction_data).map_err(|_| {
+                    ParseInstructionError::InstructionNotParsable(ParsableProgram::SplToken)
+                })?;
+            let proof_instruction_offset: i8 = withdraw_withheld_data.proof_instruction_offset;
+            let mut value = json!({
+                "mint": account_keys[account_indexes[0] as usize].to_string(),
+                "feeRecipient": account_keys[account_indexes[1] as usize].to_string(),
+                "instructionsSysvar": account_keys[account_indexes[2] as usize].to_string(),
+                "proofInstructionOffset": proof_instruction_offset,
+
+            });
+            let map = value.as_object_mut().unwrap();
+            parse_signers(
+                map,
+                3,
+                account_keys,
+                account_indexes,
+                "withdrawWithheldAuthority",
+                "multisigWithdrawWithheldAuthority",
+            );
+            Ok(ParsedInstructionEnum {
+                instruction_type: "withdrawWithheldConfidentialTransferTokensFromMint".to_string(),
+                info: value,
+            })
+        }
+        ConfidentialTransferFeeInstruction::WithdrawWithheldTokensFromAccounts => {
+            let withdraw_withheld_data: WithdrawWithheldTokensFromAccountsData =
+                *decode_instruction_data(instruction_data).map_err(|_| {
+                    ParseInstructionError::InstructionNotParsable(ParsableProgram::SplToken)
+                })?;
+            let num_token_accounts = withdraw_withheld_data.num_token_accounts;
+            check_num_token_accounts(account_indexes, 4 + num_token_accounts as usize)?;
+            let proof_instruction_offset: i8 = withdraw_withheld_data.proof_instruction_offset;
+            let mut value = json!({
+                "mint": account_keys[account_indexes[0] as usize].to_string(),
+                "feeRecipient": account_keys[account_indexes[1] as usize].to_string(),
+                "instructionsSysvar": account_keys[account_indexes[2] as usize].to_string(),
+                "proofInstructionOffset": proof_instruction_offset,
+            });
+            let map = value.as_object_mut().unwrap();
+            let mut source_accounts: Vec<String> = vec![];
+            let first_source_account_index = account_indexes
+                .len()
+                .saturating_sub(num_token_accounts as usize);
+            for i in account_indexes[first_source_account_index..].iter() {
+                source_accounts.push(account_keys[*i as usize].to_string());
+            }
+            map.insert("sourceAccounts".to_string(), json!(source_accounts));
+            parse_signers(
+                map,
+                3,
+                account_keys,
+                &account_indexes[..first_source_account_index],
+                "withdrawWithheldAuthority",
+                "multisigWithdrawWithheldAuthority",
+            );
+            Ok(ParsedInstructionEnum {
+                instruction_type: "withdrawWithheldConfidentialTransferTokensFromAccounts"
+                    .to_string(),
+                info: value,
+            })
+        }
+        ConfidentialTransferFeeInstruction::HarvestWithheldTokensToMint => {
+            check_num_token_accounts(account_indexes, 1)?;
+            let mut value = json!({
+                "mint": account_keys[account_indexes[0] as usize].to_string(),
+
+            });
+            let map = value.as_object_mut().unwrap();
+            let mut source_accounts: Vec<String> = vec![];
+            for i in account_indexes.iter().skip(1) {
+                source_accounts.push(account_keys[*i as usize].to_string());
+            }
+            map.insert("sourceAccounts".to_string(), json!(source_accounts));
+            Ok(ParsedInstructionEnum {
+                instruction_type: "harvestWithheldConfidentialTransferTokensToMint".to_string(),
+                info: value,
+            })
+        }
+        ConfidentialTransferFeeInstruction::EnableHarvestToMint => {
+            check_num_token_accounts(account_indexes, 2)?;
+            let mut value = json!({
+                "account": account_keys[account_indexes[0] as usize].to_string(),
+
+            });
+            let map = value.as_object_mut().unwrap();
+            parse_signers(
+                map,
+                1,
+                account_keys,
+                account_indexes,
+                "owner",
+                "multisigOwner",
+            );
+            Ok(ParsedInstructionEnum {
+                instruction_type: "enableConfidentialTransferFeeHarvestToMint".to_string(),
+                info: value,
+            })
+        }
+        ConfidentialTransferFeeInstruction::DisableHarvestToMint => {
+            check_num_token_accounts(account_indexes, 2)?;
+            let mut value = json!({
+                "account": account_keys[account_indexes[0] as usize].to_string(),
+
+            });
+            let map = value.as_object_mut().unwrap();
+            parse_signers(
+                map,
+                1,
+                account_keys,
+                account_indexes,
+                "owner",
+                "multisigOwner",
+            );
+            Ok(ParsedInstructionEnum {
+                instruction_type: "disableConfidentialTransferFeeHarvestToMint".to_string(),
+                info: value,
+            })
+        }
+    }
+}

+ 192 - 0
transaction-status/src/parse_token/extension/metadata_pointer.rs

@@ -0,0 +1,192 @@
+use {
+    super::*,
+    spl_token_2022::{
+        extension::metadata_pointer::instruction::*,
+        instruction::{decode_instruction_data, decode_instruction_type},
+    },
+};
+
+pub(in crate::parse_token) fn parse_metadata_pointer_instruction(
+    instruction_data: &[u8],
+    account_indexes: &[u8],
+    account_keys: &AccountKeys,
+) -> Result<ParsedInstructionEnum, ParseInstructionError> {
+    match decode_instruction_type(instruction_data)
+        .map_err(|_| ParseInstructionError::InstructionNotParsable(ParsableProgram::SplToken))?
+    {
+        MetadataPointerInstruction::Initialize => {
+            check_num_token_accounts(account_indexes, 1)?;
+            let InitializeInstructionData {
+                authority,
+                metadata_address,
+            } = *decode_instruction_data(instruction_data).map_err(|_| {
+                ParseInstructionError::InstructionNotParsable(ParsableProgram::SplToken)
+            })?;
+            let mut value = json!({
+                "mint": account_keys[account_indexes[0] as usize].to_string(),
+            });
+            let map = value.as_object_mut().unwrap();
+            if let Some(authority) = Option::<Pubkey>::from(authority) {
+                map.insert("authority".to_string(), json!(authority.to_string()));
+            }
+            if let Some(metadata_address) = Option::<Pubkey>::from(metadata_address) {
+                map.insert(
+                    "metadataAddress".to_string(),
+                    json!(metadata_address.to_string()),
+                );
+            }
+            Ok(ParsedInstructionEnum {
+                instruction_type: "initializeMetadataPointer".to_string(),
+                info: value,
+            })
+        }
+        MetadataPointerInstruction::Update => {
+            check_num_token_accounts(account_indexes, 2)?;
+            let UpdateInstructionData { metadata_address } =
+                *decode_instruction_data(instruction_data).map_err(|_| {
+                    ParseInstructionError::InstructionNotParsable(ParsableProgram::SplToken)
+                })?;
+            let mut value = json!({
+                "mint": account_keys[account_indexes[0] as usize].to_string(),
+            });
+            let map = value.as_object_mut().unwrap();
+            if let Some(metadata_address) = Option::<Pubkey>::from(metadata_address) {
+                map.insert(
+                    "metadataAddress".to_string(),
+                    json!(metadata_address.to_string()),
+                );
+            }
+            parse_signers(
+                map,
+                1,
+                account_keys,
+                account_indexes,
+                "authority",
+                "multisigAuthority",
+            );
+            Ok(ParsedInstructionEnum {
+                instruction_type: "updateMetadataPointer".to_string(),
+                info: value,
+            })
+        }
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use {
+        super::*, crate::parse_token::test::*, solana_sdk::pubkey::Pubkey,
+        spl_token_2022::solana_program::message::Message,
+    };
+
+    #[test]
+    fn test_parse_metadata_pointer_instruction() {
+        let mint_pubkey = Pubkey::new_unique();
+        let authority = Pubkey::new_unique();
+        let metadata_address = Pubkey::new_unique();
+
+        // Initialize variations
+        let init_ix = initialize(
+            &spl_token_2022::id(),
+            &mint_pubkey,
+            Some(authority),
+            Some(metadata_address),
+        )
+        .unwrap();
+        let message = Message::new(&[init_ix], None);
+        let compiled_instruction = convert_compiled_instruction(&message.instructions[0]);
+        assert_eq!(
+            parse_token(
+                &compiled_instruction,
+                &AccountKeys::new(&message.account_keys, None)
+            )
+            .unwrap(),
+            ParsedInstructionEnum {
+                instruction_type: "initializeMetadataPointer".to_string(),
+                info: json!({
+                    "mint": mint_pubkey.to_string(),
+                    "authority": authority.to_string(),
+                    "metadataAddress": metadata_address.to_string(),
+                })
+            }
+        );
+
+        let init_ix = initialize(&spl_token_2022::id(), &mint_pubkey, None, None).unwrap();
+        let message = Message::new(&[init_ix], None);
+        let compiled_instruction = convert_compiled_instruction(&message.instructions[0]);
+        assert_eq!(
+            parse_token(
+                &compiled_instruction,
+                &AccountKeys::new(&message.account_keys, None)
+            )
+            .unwrap(),
+            ParsedInstructionEnum {
+                instruction_type: "initializeMetadataPointer".to_string(),
+                info: json!({
+                    "mint": mint_pubkey.to_string(),
+                })
+            }
+        );
+
+        // Single owner Update
+        let update_ix = update(
+            &spl_token_2022::id(),
+            &mint_pubkey,
+            &authority,
+            &[],
+            Some(metadata_address),
+        )
+        .unwrap();
+        let message = Message::new(&[update_ix], None);
+        let compiled_instruction = convert_compiled_instruction(&message.instructions[0]);
+        assert_eq!(
+            parse_token(
+                &compiled_instruction,
+                &AccountKeys::new(&message.account_keys, None)
+            )
+            .unwrap(),
+            ParsedInstructionEnum {
+                instruction_type: "updateMetadataPointer".to_string(),
+                info: json!({
+                    "mint": mint_pubkey.to_string(),
+                    "authority": authority.to_string(),
+                    "metadataAddress": metadata_address.to_string(),
+                })
+            }
+        );
+
+        // Multisig Update
+        let multisig_pubkey = Pubkey::new_unique();
+        let multisig_signer0 = Pubkey::new_unique();
+        let multisig_signer1 = Pubkey::new_unique();
+        let update_ix = update(
+            &spl_token_2022::id(),
+            &mint_pubkey,
+            &multisig_pubkey,
+            &[&multisig_signer0, &multisig_signer1],
+            Some(metadata_address),
+        )
+        .unwrap();
+        let message = Message::new(&[update_ix], None);
+        let compiled_instruction = convert_compiled_instruction(&message.instructions[0]);
+        assert_eq!(
+            parse_token(
+                &compiled_instruction,
+                &AccountKeys::new(&message.account_keys, None)
+            )
+            .unwrap(),
+            ParsedInstructionEnum {
+                instruction_type: "updateMetadataPointer".to_string(),
+                info: json!({
+                    "mint": mint_pubkey.to_string(),
+                    "metadataAddress": metadata_address.to_string(),
+                    "multisigAuthority": multisig_pubkey.to_string(),
+                    "signers": vec![
+                        multisig_signer0.to_string(),
+                        multisig_signer1.to_string(),
+                    ],
+                })
+            }
+        );
+    }
+}

+ 3 - 0
transaction-status/src/parse_token/extension/mod.rs

@@ -1,11 +1,14 @@
 use super::*;
 
 pub(super) mod confidential_transfer;
+pub(super) mod confidential_transfer_fee;
 pub(super) mod cpi_guard;
 pub(super) mod default_account_state;
 pub(super) mod interest_bearing_mint;
 pub(super) mod memo_transfer;
+pub(super) mod metadata_pointer;
 pub(super) mod mint_close_authority;
 pub(super) mod permanent_delegate;
 pub(super) mod reallocate;
 pub(super) mod transfer_fee;
+pub(super) mod transfer_hook;

+ 186 - 0
transaction-status/src/parse_token/extension/transfer_hook.rs

@@ -0,0 +1,186 @@
+use {
+    super::*,
+    spl_token_2022::{
+        extension::transfer_hook::instruction::*,
+        instruction::{decode_instruction_data, decode_instruction_type},
+    },
+};
+
+pub(in crate::parse_token) fn parse_transfer_hook_instruction(
+    instruction_data: &[u8],
+    account_indexes: &[u8],
+    account_keys: &AccountKeys,
+) -> Result<ParsedInstructionEnum, ParseInstructionError> {
+    match decode_instruction_type(instruction_data)
+        .map_err(|_| ParseInstructionError::InstructionNotParsable(ParsableProgram::SplToken))?
+    {
+        TransferHookInstruction::Initialize => {
+            check_num_token_accounts(account_indexes, 1)?;
+            let InitializeInstructionData {
+                authority,
+                program_id,
+            } = *decode_instruction_data(instruction_data).map_err(|_| {
+                ParseInstructionError::InstructionNotParsable(ParsableProgram::SplToken)
+            })?;
+            let mut value = json!({
+                "mint": account_keys[account_indexes[0] as usize].to_string(),
+            });
+            let map = value.as_object_mut().unwrap();
+            if let Some(authority) = Option::<Pubkey>::from(authority) {
+                map.insert("authority".to_string(), json!(authority.to_string()));
+            }
+            if let Some(program_id) = Option::<Pubkey>::from(program_id) {
+                map.insert("programId".to_string(), json!(program_id.to_string()));
+            }
+            Ok(ParsedInstructionEnum {
+                instruction_type: "initializeTransferHook".to_string(),
+                info: value,
+            })
+        }
+        TransferHookInstruction::Update => {
+            check_num_token_accounts(account_indexes, 2)?;
+            let UpdateInstructionData { program_id } = *decode_instruction_data(instruction_data)
+                .map_err(|_| {
+                ParseInstructionError::InstructionNotParsable(ParsableProgram::SplToken)
+            })?;
+            let mut value = json!({
+                "mint": account_keys[account_indexes[0] as usize].to_string(),
+            });
+            let map = value.as_object_mut().unwrap();
+            if let Some(program_id) = Option::<Pubkey>::from(program_id) {
+                map.insert("programId".to_string(), json!(program_id.to_string()));
+            }
+            parse_signers(
+                map,
+                1,
+                account_keys,
+                account_indexes,
+                "authority",
+                "multisigAuthority",
+            );
+            Ok(ParsedInstructionEnum {
+                instruction_type: "updateTransferHook".to_string(),
+                info: value,
+            })
+        }
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use {
+        super::*, crate::parse_token::test::*, solana_sdk::pubkey::Pubkey,
+        spl_token_2022::solana_program::message::Message,
+    };
+
+    #[test]
+    fn test_parse_transfer_hook_instruction() {
+        let mint_pubkey = Pubkey::new_unique();
+        let authority = Pubkey::new_unique();
+        let program_id = Pubkey::new_unique();
+
+        // Initialize variations
+        let init_ix = initialize(
+            &spl_token_2022::id(),
+            &mint_pubkey,
+            Some(authority),
+            Some(program_id),
+        )
+        .unwrap();
+        let message = Message::new(&[init_ix], None);
+        let compiled_instruction = convert_compiled_instruction(&message.instructions[0]);
+        assert_eq!(
+            parse_token(
+                &compiled_instruction,
+                &AccountKeys::new(&message.account_keys, None)
+            )
+            .unwrap(),
+            ParsedInstructionEnum {
+                instruction_type: "initializeTransferHook".to_string(),
+                info: json!({
+                    "mint": mint_pubkey.to_string(),
+                    "authority": authority.to_string(),
+                    "programId": program_id.to_string(),
+                })
+            }
+        );
+
+        let init_ix = initialize(&spl_token_2022::id(), &mint_pubkey, None, None).unwrap();
+        let message = Message::new(&[init_ix], None);
+        let compiled_instruction = convert_compiled_instruction(&message.instructions[0]);
+        assert_eq!(
+            parse_token(
+                &compiled_instruction,
+                &AccountKeys::new(&message.account_keys, None)
+            )
+            .unwrap(),
+            ParsedInstructionEnum {
+                instruction_type: "initializeTransferHook".to_string(),
+                info: json!({
+                    "mint": mint_pubkey.to_string(),
+                })
+            }
+        );
+
+        // Single owner Update
+        let update_ix = update(
+            &spl_token_2022::id(),
+            &mint_pubkey,
+            &authority,
+            &[],
+            Some(program_id),
+        )
+        .unwrap();
+        let message = Message::new(&[update_ix], None);
+        let compiled_instruction = convert_compiled_instruction(&message.instructions[0]);
+        assert_eq!(
+            parse_token(
+                &compiled_instruction,
+                &AccountKeys::new(&message.account_keys, None)
+            )
+            .unwrap(),
+            ParsedInstructionEnum {
+                instruction_type: "updateTransferHook".to_string(),
+                info: json!({
+                    "mint": mint_pubkey.to_string(),
+                    "authority": authority.to_string(),
+                    "programId": program_id.to_string(),
+                })
+            }
+        );
+
+        // Multisig Update
+        let multisig_pubkey = Pubkey::new_unique();
+        let multisig_signer0 = Pubkey::new_unique();
+        let multisig_signer1 = Pubkey::new_unique();
+        let update_ix = update(
+            &spl_token_2022::id(),
+            &mint_pubkey,
+            &multisig_pubkey,
+            &[&multisig_signer0, &multisig_signer1],
+            Some(program_id),
+        )
+        .unwrap();
+        let message = Message::new(&[update_ix], None);
+        let compiled_instruction = convert_compiled_instruction(&message.instructions[0]);
+        assert_eq!(
+            parse_token(
+                &compiled_instruction,
+                &AccountKeys::new(&message.account_keys, None)
+            )
+            .unwrap(),
+            ParsedInstructionEnum {
+                instruction_type: "updateTransferHook".to_string(),
+                info: json!({
+                    "mint": mint_pubkey.to_string(),
+                    "programId": program_id.to_string(),
+                    "multisigAuthority": multisig_pubkey.to_string(),
+                    "signers": vec![
+                        multisig_signer0.to_string(),
+                        multisig_signer1.to_string(),
+                    ],
+                })
+            }
+        );
+    }
+}