Browse Source

v1.17: spl: Bump token-2022 to v1 (backport of #34412) (#34572)

* spl: Bump token-2022 to v1 (#34412)

* Update toml and lockfiles

* account-decoder: Add group and group member extensions

* transaction-status: Add token group + pointer extensions

* program-test: Update token-2022 binary

(cherry picked from commit 8a8466cd86030a667a04e446d1dc174aa39cfad0)

# Conflicts:
#	Cargo.toml

* Fix merge conflicts

---------

Co-authored-by: Jon Cinque <me@jonc.dev>
mergify[bot] 1 year ago
parent
commit
bbdce0f273

+ 36 - 14
Cargo.lock

@@ -3388,11 +3388,11 @@ dependencies = [
 
 [[package]]
 name = "num_enum"
-version = "0.7.0"
+version = "0.7.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "70bf6736f74634d299d00086f02986875b3c2d924781a6a2cb6c201e73da0ceb"
+checksum = "683751d591e6d81200c39fb0d1032608b77724f34114db54f571ff1317b337c0"
 dependencies = [
- "num_enum_derive 0.7.0",
+ "num_enum_derive 0.7.1",
 ]
 
 [[package]]
@@ -3421,9 +3421,9 @@ dependencies = [
 
 [[package]]
 name = "num_enum_derive"
-version = "0.7.0"
+version = "0.7.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "56ea360eafe1022f7cc56cd7b869ed57330fb2453d0c7831d99b74c65d2f5597"
+checksum = "6c11e44798ad209ccdd91fc192f0526a369a01234f7373e1b141c96d7cee4f0e"
 dependencies = [
  "proc-macro-crate 1.1.0",
  "proc-macro2",
@@ -5116,6 +5116,7 @@ dependencies = [
  "spl-pod",
  "spl-token",
  "spl-token-2022",
+ "spl-token-group-interface",
  "spl-token-metadata-interface",
  "thiserror",
  "zstd",
@@ -6991,6 +6992,12 @@ dependencies = [
  "syn 2.0.38",
 ]
 
+[[package]]
+name = "solana-security-txt"
+version = "1.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "468aa43b7edb1f9b7b7b686d5c3aeb6630dc1708e86e31343499dd5c4d775183"
+
 [[package]]
 name = "solana-send-transaction-service"
 version = "1.17.14"
@@ -7608,9 +7615,9 @@ dependencies = [
 
 [[package]]
 name = "spl-associated-token-account"
-version = "2.2.0"
+version = "2.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "385e31c29981488f2820b2022d8e731aae3b02e6e18e2fd854e4c9a94dc44fc3"
+checksum = "992d9c64c2564cc8f63a4b508bf3ebcdf2254b0429b13cd1d31adb6162432a5f"
 dependencies = [
  "assert_matches",
  "borsh 0.10.3",
@@ -7716,9 +7723,9 @@ dependencies = [
 
 [[package]]
 name = "spl-tlv-account-resolution"
-version = "0.4.0"
+version = "0.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "062e148d3eab7b165582757453632ffeef490c02c86a48bfdb4988f63eefb3b9"
+checksum = "3f7020347c07892c08560d230fbb8a980316c9e198e22b198b7b9d951ff96047"
 dependencies = [
  "bytemuck",
  "solana-program",
@@ -7745,26 +7752,41 @@ dependencies = [
 
 [[package]]
 name = "spl-token-2022"
-version = "0.9.0"
+version = "1.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e4abf34a65ba420584a0c35f3903f8d727d1f13ababbdc3f714c6b065a686e86"
+checksum = "d697fac19fd74ff472dfcc13f0b442dd71403178ce1de7b5d16f83a33561c059"
 dependencies = [
  "arrayref",
  "bytemuck",
  "num-derive 0.4.1",
  "num-traits",
- "num_enum 0.7.0",
+ "num_enum 0.7.1",
  "solana-program",
+ "solana-security-txt",
  "solana-zk-token-sdk",
  "spl-memo",
  "spl-pod",
  "spl-token",
+ "spl-token-group-interface",
  "spl-token-metadata-interface",
  "spl-transfer-hook-interface",
  "spl-type-length-value",
  "thiserror",
 ]
 
+[[package]]
+name = "spl-token-group-interface"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b889509d49fa74a4a033ca5dae6c2307e9e918122d97e58562f5c4ffa795c75d"
+dependencies = [
+ "bytemuck",
+ "solana-program",
+ "spl-discriminator",
+ "spl-pod",
+ "spl-program-error",
+]
+
 [[package]]
 name = "spl-token-metadata-interface"
 version = "0.2.0"
@@ -7781,9 +7803,9 @@ dependencies = [
 
 [[package]]
 name = "spl-transfer-hook-interface"
-version = "0.3.0"
+version = "0.4.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "051d31803f873cabe71aec3c1b849f35248beae5d19a347d93a5c9cccc5d5a9b"
+checksum = "7aabdb7c471566f6ddcee724beb8618449ea24b399e58d464d6b5bc7db550259"
 dependencies = [
  "arrayref",
  "bytemuck",

+ 3 - 2
Cargo.toml

@@ -373,12 +373,13 @@ solana-vote-program = { path = "programs/vote", version = "=1.17.14" }
 solana-zk-keygen = { path = "zk-keygen", version = "=1.17.14" }
 solana-zk-token-proof-program = { path = "programs/zk-token-proof", version = "=1.17.14" }
 solana-zk-token-sdk = { path = "zk-token-sdk", version = "=1.17.14" }
-spl-associated-token-account = "=2.2.0"
+spl-associated-token-account = "=2.3.0"
 spl-instruction-padding = "0.1"
 spl-memo = "=4.0.0"
 spl-pod = "=0.1.0"
 spl-token = "=4.0.0"
-spl-token-2022 = "=0.9.0"
+spl-token-2022 = "=1.0.0"
+spl-token-group-interface = "=0.1.0"
 spl-token-metadata-interface = "=0.2.0"
 static_assertions = "1.1.0"
 stream-cancel = "0.8.1"

+ 1 - 0
account-decoder/Cargo.toml

@@ -23,6 +23,7 @@ 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-group-interface = { workspace = true }
 spl-token-metadata-interface = { workspace = true }
 thiserror = { workspace = true }
 zstd = { workspace = true }

+ 96 - 0
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_group_interface::state::{TokenGroup, TokenGroupMember},
     spl_token_metadata_interface::state::TokenMetadata,
 };
 
@@ -32,6 +33,10 @@ pub enum UiExtension {
     TransferHookAccount(UiTransferHookAccount),
     MetadataPointer(UiMetadataPointer),
     TokenMetadata(UiTokenMetadata),
+    GroupPointer(UiGroupPointer),
+    GroupMemberPointer(UiGroupMemberPointer),
+    TokenGroup(UiTokenGroup),
+    TokenGroupMember(UiTokenGroupMember),
     UnparseableExtension,
 }
 
@@ -108,6 +113,22 @@ pub fn parse_extension<S: BaseState>(
             .get_extension::<extension::transfer_hook::TransferHookAccount>()
             .map(|&extension| UiExtension::TransferHookAccount(extension.into()))
             .unwrap_or(UiExtension::UnparseableExtension),
+        ExtensionType::GroupPointer => account
+            .get_extension::<extension::group_pointer::GroupPointer>()
+            .map(|&extension| UiExtension::GroupPointer(extension.into()))
+            .unwrap_or(UiExtension::UnparseableExtension),
+        ExtensionType::GroupMemberPointer => account
+            .get_extension::<extension::group_member_pointer::GroupMemberPointer>()
+            .map(|&extension| UiExtension::GroupMemberPointer(extension.into()))
+            .unwrap_or(UiExtension::UnparseableExtension),
+        ExtensionType::TokenGroup => account
+            .get_extension::<TokenGroup>()
+            .map(|&extension| UiExtension::TokenGroup(extension.into()))
+            .unwrap_or(UiExtension::UnparseableExtension),
+        ExtensionType::TokenGroupMember => account
+            .get_extension::<TokenGroupMember>()
+            .map(|&extension| UiExtension::TokenGroupMember(extension.into()))
+            .unwrap_or(UiExtension::UnparseableExtension),
     }
 }
 
@@ -481,3 +502,78 @@ impl From<extension::transfer_hook::TransferHookAccount> for UiTransferHookAccou
         }
     }
 }
+
+#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
+#[serde(rename_all = "camelCase")]
+pub struct UiGroupPointer {
+    pub authority: Option<String>,
+    pub group_address: Option<String>,
+}
+
+impl From<extension::group_pointer::GroupPointer> for UiGroupPointer {
+    fn from(group_pointer: extension::group_pointer::GroupPointer) -> Self {
+        let authority: Option<Pubkey> = group_pointer.authority.into();
+        let group_address: Option<Pubkey> = group_pointer.group_address.into();
+        Self {
+            authority: authority.map(|pubkey| pubkey.to_string()),
+            group_address: group_address.map(|pubkey| pubkey.to_string()),
+        }
+    }
+}
+
+#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
+#[serde(rename_all = "camelCase")]
+pub struct UiGroupMemberPointer {
+    pub authority: Option<String>,
+    pub member_address: Option<String>,
+}
+
+impl From<extension::group_member_pointer::GroupMemberPointer> for UiGroupMemberPointer {
+    fn from(member_pointer: extension::group_member_pointer::GroupMemberPointer) -> Self {
+        let authority: Option<Pubkey> = member_pointer.authority.into();
+        let member_address: Option<Pubkey> = member_pointer.member_address.into();
+        Self {
+            authority: authority.map(|pubkey| pubkey.to_string()),
+            member_address: member_address.map(|pubkey| pubkey.to_string()),
+        }
+    }
+}
+
+#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
+#[serde(rename_all = "camelCase")]
+pub struct UiTokenGroup {
+    pub update_authority: Option<String>,
+    pub mint: String,
+    pub size: u32,
+    pub max_size: u32,
+}
+
+impl From<TokenGroup> for UiTokenGroup {
+    fn from(token_group: TokenGroup) -> Self {
+        let update_authority: Option<Pubkey> = token_group.update_authority.into();
+        Self {
+            update_authority: update_authority.map(|pubkey| pubkey.to_string()),
+            mint: token_group.mint.to_string(),
+            size: token_group.size.into(),
+            max_size: token_group.max_size.into(),
+        }
+    }
+}
+
+#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
+#[serde(rename_all = "camelCase")]
+pub struct UiTokenGroupMember {
+    pub mint: String,
+    pub group: String,
+    pub member_number: u32,
+}
+
+impl From<TokenGroupMember> for UiTokenGroupMember {
+    fn from(member: TokenGroupMember) -> Self {
+        Self {
+            mint: member.mint.to_string(),
+            group: member.group.to_string(),
+            member_number: member.member_number.into(),
+        }
+    }
+}

+ 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.9.0.so"),
+        include_bytes!("programs/spl_token_2022-1.0.0.so"),
     ),
     (
         spl_memo_1_0::ID,

BIN
program-test/src/programs/spl_token_2022-0.9.0.so


BIN
program-test/src/programs/spl_token_2022-1.0.0.so


+ 36 - 14
programs/sbf/Cargo.lock

@@ -2993,11 +2993,11 @@ dependencies = [
 
 [[package]]
 name = "num_enum"
-version = "0.7.0"
+version = "0.7.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "70bf6736f74634d299d00086f02986875b3c2d924781a6a2cb6c201e73da0ceb"
+checksum = "683751d591e6d81200c39fb0d1032608b77724f34114db54f571ff1317b337c0"
 dependencies = [
- "num_enum_derive 0.7.0",
+ "num_enum_derive 0.7.1",
 ]
 
 [[package]]
@@ -3014,9 +3014,9 @@ dependencies = [
 
 [[package]]
 name = "num_enum_derive"
-version = "0.7.0"
+version = "0.7.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "56ea360eafe1022f7cc56cd7b869ed57330fb2453d0c7831d99b74c65d2f5597"
+checksum = "6c11e44798ad209ccdd91fc192f0526a369a01234f7373e1b141c96d7cee4f0e"
 dependencies = [
  "proc-macro-crate 1.1.3",
  "proc-macro2",
@@ -4462,6 +4462,7 @@ dependencies = [
  "solana-sdk",
  "spl-token",
  "spl-token-2022",
+ "spl-token-group-interface",
  "spl-token-metadata-interface",
  "thiserror",
  "zstd",
@@ -6075,6 +6076,12 @@ dependencies = [
  "syn 2.0.37",
 ]
 
+[[package]]
+name = "solana-security-txt"
+version = "1.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "468aa43b7edb1f9b7b7b686d5c3aeb6630dc1708e86e31343499dd5c4d775183"
+
 [[package]]
 name = "solana-send-transaction-service"
 version = "1.17.14"
@@ -6522,9 +6529,9 @@ dependencies = [
 
 [[package]]
 name = "spl-associated-token-account"
-version = "2.2.0"
+version = "2.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "385e31c29981488f2820b2022d8e731aae3b02e6e18e2fd854e4c9a94dc44fc3"
+checksum = "992d9c64c2564cc8f63a4b508bf3ebcdf2254b0429b13cd1d31adb6162432a5f"
 dependencies = [
  "assert_matches",
  "borsh 0.10.3",
@@ -6620,9 +6627,9 @@ dependencies = [
 
 [[package]]
 name = "spl-tlv-account-resolution"
-version = "0.4.0"
+version = "0.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "062e148d3eab7b165582757453632ffeef490c02c86a48bfdb4988f63eefb3b9"
+checksum = "3f7020347c07892c08560d230fbb8a980316c9e198e22b198b7b9d951ff96047"
 dependencies = [
  "bytemuck",
  "solana-program",
@@ -6649,26 +6656,41 @@ dependencies = [
 
 [[package]]
 name = "spl-token-2022"
-version = "0.9.0"
+version = "1.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e4abf34a65ba420584a0c35f3903f8d727d1f13ababbdc3f714c6b065a686e86"
+checksum = "d697fac19fd74ff472dfcc13f0b442dd71403178ce1de7b5d16f83a33561c059"
 dependencies = [
  "arrayref",
  "bytemuck",
  "num-derive 0.4.0",
  "num-traits",
- "num_enum 0.7.0",
+ "num_enum 0.7.1",
  "solana-program",
+ "solana-security-txt",
  "solana-zk-token-sdk",
  "spl-memo",
  "spl-pod",
  "spl-token",
+ "spl-token-group-interface",
  "spl-token-metadata-interface",
  "spl-transfer-hook-interface",
  "spl-type-length-value",
  "thiserror",
 ]
 
+[[package]]
+name = "spl-token-group-interface"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b889509d49fa74a4a033ca5dae6c2307e9e918122d97e58562f5c4ffa795c75d"
+dependencies = [
+ "bytemuck",
+ "solana-program",
+ "spl-discriminator",
+ "spl-pod",
+ "spl-program-error",
+]
+
 [[package]]
 name = "spl-token-metadata-interface"
 version = "0.2.0"
@@ -6685,9 +6707,9 @@ dependencies = [
 
 [[package]]
 name = "spl-transfer-hook-interface"
-version = "0.3.0"
+version = "0.4.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "051d31803f873cabe71aec3c1b849f35248beae5d19a347d93a5c9cccc5d5a9b"
+checksum = "7aabdb7c471566f6ddcee724beb8618449ea24b399e58d464d6b5bc7db550259"
 dependencies = [
  "arrayref",
  "bytemuck",

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

@@ -4,9 +4,9 @@ use {
     },
     extension::{
         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::*,
+        default_account_state::*, group_member_pointer::*, group_pointer::*,
+        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},
@@ -233,7 +233,9 @@ pub fn parse_token(
                 | AuthorityType::ConfidentialTransferMint
                 | AuthorityType::TransferHookProgramId
                 | AuthorityType::ConfidentialTransferFeeConfig
-                | AuthorityType::MetadataPointer => "mint",
+                | AuthorityType::MetadataPointer
+                | AuthorityType::GroupPointer
+                | AuthorityType::GroupMemberPointer => "mint",
                 AuthorityType::AccountOwner | AuthorityType::CloseAccount => "account",
             };
             let mut value = json!({
@@ -650,6 +652,30 @@ pub fn parse_token(
                 account_keys,
             )
         }
+        TokenInstruction::GroupPointerExtension => {
+            if instruction.data.len() < 2 {
+                return Err(ParseInstructionError::InstructionNotParsable(
+                    ParsableProgram::SplToken,
+                ));
+            }
+            parse_group_pointer_instruction(
+                &instruction.data[1..],
+                &instruction.accounts,
+                account_keys,
+            )
+        }
+        TokenInstruction::GroupMemberPointerExtension => {
+            if instruction.data.len() < 2 {
+                return Err(ParseInstructionError::InstructionNotParsable(
+                    ParsableProgram::SplToken,
+                ));
+            }
+            parse_group_member_pointer_instruction(
+                &instruction.data[1..],
+                &instruction.accounts,
+                account_keys,
+            )
+        }
     }
 }
 
@@ -669,6 +695,8 @@ pub enum UiAuthorityType {
     TransferHookProgramId,
     ConfidentialTransferFeeConfig,
     MetadataPointer,
+    GroupPointer,
+    GroupMemberPointer,
 }
 
 impl From<AuthorityType> for UiAuthorityType {
@@ -689,6 +717,8 @@ impl From<AuthorityType> for UiAuthorityType {
                 UiAuthorityType::ConfidentialTransferFeeConfig
             }
             AuthorityType::MetadataPointer => UiAuthorityType::MetadataPointer,
+            AuthorityType::GroupPointer => UiAuthorityType::GroupPointer,
+            AuthorityType::GroupMemberPointer => UiAuthorityType::GroupMemberPointer,
         }
     }
 }
@@ -716,6 +746,10 @@ pub enum UiExtensionType {
     ConfidentialTransferFeeAmount,
     MetadataPointer,
     TokenMetadata,
+    GroupPointer,
+    GroupMemberPointer,
+    TokenGroup,
+    TokenGroupMember,
 }
 
 impl From<ExtensionType> for UiExtensionType {
@@ -747,6 +781,10 @@ impl From<ExtensionType> for UiExtensionType {
             }
             ExtensionType::MetadataPointer => UiExtensionType::MetadataPointer,
             ExtensionType::TokenMetadata => UiExtensionType::TokenMetadata,
+            ExtensionType::GroupPointer => UiExtensionType::GroupPointer,
+            ExtensionType::GroupMemberPointer => UiExtensionType::GroupMemberPointer,
+            ExtensionType::TokenGroup => UiExtensionType::TokenGroup,
+            ExtensionType::TokenGroupMember => UiExtensionType::TokenGroupMember,
         }
     }
 }

+ 189 - 0
transaction-status/src/parse_token/extension/group_member_pointer.rs

@@ -0,0 +1,189 @@
+use {
+    super::*,
+    spl_token_2022::{
+        extension::group_member_pointer::instruction::*,
+        instruction::{decode_instruction_data, decode_instruction_type},
+    },
+};
+
+pub(in crate::parse_token) fn parse_group_member_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))?
+    {
+        GroupMemberPointerInstruction::Initialize => {
+            check_num_token_accounts(account_indexes, 1)?;
+            let InitializeInstructionData {
+                authority,
+                member_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(member_address) = Option::<Pubkey>::from(member_address) {
+                map.insert(
+                    "memberAddress".to_string(),
+                    json!(member_address.to_string()),
+                );
+            }
+            Ok(ParsedInstructionEnum {
+                instruction_type: "initializeGroupMemberPointer".to_string(),
+                info: value,
+            })
+        }
+        GroupMemberPointerInstruction::Update => {
+            check_num_token_accounts(account_indexes, 2)?;
+            let UpdateInstructionData { member_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(member_address) = Option::<Pubkey>::from(member_address) {
+                map.insert(
+                    "memberAddress".to_string(),
+                    json!(member_address.to_string()),
+                );
+            }
+            parse_signers(
+                map,
+                1,
+                account_keys,
+                account_indexes,
+                "authority",
+                "multisigAuthority",
+            );
+            Ok(ParsedInstructionEnum {
+                instruction_type: "updateGroupMemberPointer".to_string(),
+                info: value,
+            })
+        }
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use {super::*, solana_sdk::pubkey::Pubkey, spl_token_2022::solana_program::message::Message};
+
+    #[test]
+    fn test_parse_group_member_pointer_instruction() {
+        let mint_pubkey = Pubkey::new_unique();
+        let authority = Pubkey::new_unique();
+        let member_address = Pubkey::new_unique();
+
+        // Initialize variations
+        let init_ix = initialize(
+            &spl_token_2022::id(),
+            &mint_pubkey,
+            Some(authority),
+            Some(member_address),
+        )
+        .unwrap();
+        let mut message = Message::new(&[init_ix], None);
+        let compiled_instruction = &mut message.instructions[0];
+        assert_eq!(
+            parse_token(
+                compiled_instruction,
+                &AccountKeys::new(&message.account_keys, None)
+            )
+            .unwrap(),
+            ParsedInstructionEnum {
+                instruction_type: "initializeGroupMemberPointer".to_string(),
+                info: json!({
+                    "mint": mint_pubkey.to_string(),
+                    "authority": authority.to_string(),
+                    "memberAddress": member_address.to_string(),
+                })
+            }
+        );
+
+        let init_ix = initialize(&spl_token_2022::id(), &mint_pubkey, None, None).unwrap();
+        let mut message = Message::new(&[init_ix], None);
+        let compiled_instruction = &mut message.instructions[0];
+        assert_eq!(
+            parse_token(
+                compiled_instruction,
+                &AccountKeys::new(&message.account_keys, None)
+            )
+            .unwrap(),
+            ParsedInstructionEnum {
+                instruction_type: "initializeGroupMemberPointer".to_string(),
+                info: json!({
+                    "mint": mint_pubkey.to_string(),
+                })
+            }
+        );
+
+        // Single owner Update
+        let update_ix = update(
+            &spl_token_2022::id(),
+            &mint_pubkey,
+            &authority,
+            &[],
+            Some(member_address),
+        )
+        .unwrap();
+        let mut message = Message::new(&[update_ix], None);
+        let compiled_instruction = &mut message.instructions[0];
+        assert_eq!(
+            parse_token(
+                compiled_instruction,
+                &AccountKeys::new(&message.account_keys, None)
+            )
+            .unwrap(),
+            ParsedInstructionEnum {
+                instruction_type: "updateGroupMemberPointer".to_string(),
+                info: json!({
+                    "mint": mint_pubkey.to_string(),
+                    "authority": authority.to_string(),
+                    "memberAddress": member_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(member_address),
+        )
+        .unwrap();
+        let mut message = Message::new(&[update_ix], None);
+        let compiled_instruction = &mut message.instructions[0];
+        assert_eq!(
+            parse_token(
+                compiled_instruction,
+                &AccountKeys::new(&message.account_keys, None)
+            )
+            .unwrap(),
+            ParsedInstructionEnum {
+                instruction_type: "updateGroupMemberPointer".to_string(),
+                info: json!({
+                    "mint": mint_pubkey.to_string(),
+                    "memberAddress": member_address.to_string(),
+                    "multisigAuthority": multisig_pubkey.to_string(),
+                    "signers": vec![
+                        multisig_signer0.to_string(),
+                        multisig_signer1.to_string(),
+                    ],
+                })
+            }
+        );
+    }
+}

+ 183 - 0
transaction-status/src/parse_token/extension/group_pointer.rs

@@ -0,0 +1,183 @@
+use {
+    super::*,
+    spl_token_2022::{
+        extension::group_pointer::instruction::*,
+        instruction::{decode_instruction_data, decode_instruction_type},
+    },
+};
+
+pub(in crate::parse_token) fn parse_group_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))?
+    {
+        GroupPointerInstruction::Initialize => {
+            check_num_token_accounts(account_indexes, 1)?;
+            let InitializeInstructionData {
+                authority,
+                group_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(group_address) = Option::<Pubkey>::from(group_address) {
+                map.insert("groupAddress".to_string(), json!(group_address.to_string()));
+            }
+            Ok(ParsedInstructionEnum {
+                instruction_type: "initializeGroupPointer".to_string(),
+                info: value,
+            })
+        }
+        GroupPointerInstruction::Update => {
+            check_num_token_accounts(account_indexes, 2)?;
+            let UpdateInstructionData { group_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(group_address) = Option::<Pubkey>::from(group_address) {
+                map.insert("groupAddress".to_string(), json!(group_address.to_string()));
+            }
+            parse_signers(
+                map,
+                1,
+                account_keys,
+                account_indexes,
+                "authority",
+                "multisigAuthority",
+            );
+            Ok(ParsedInstructionEnum {
+                instruction_type: "updateGroupPointer".to_string(),
+                info: value,
+            })
+        }
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use {super::*, solana_sdk::pubkey::Pubkey, spl_token_2022::solana_program::message::Message};
+
+    #[test]
+    fn test_parse_group_pointer_instruction() {
+        let mint_pubkey = Pubkey::new_unique();
+        let authority = Pubkey::new_unique();
+        let group_address = Pubkey::new_unique();
+
+        // Initialize variations
+        let init_ix = initialize(
+            &spl_token_2022::id(),
+            &mint_pubkey,
+            Some(authority),
+            Some(group_address),
+        )
+        .unwrap();
+        let mut message = Message::new(&[init_ix], None);
+        let compiled_instruction = &mut message.instructions[0];
+        assert_eq!(
+            parse_token(
+                compiled_instruction,
+                &AccountKeys::new(&message.account_keys, None)
+            )
+            .unwrap(),
+            ParsedInstructionEnum {
+                instruction_type: "initializeGroupPointer".to_string(),
+                info: json!({
+                    "mint": mint_pubkey.to_string(),
+                    "authority": authority.to_string(),
+                    "groupAddress": group_address.to_string(),
+                })
+            }
+        );
+
+        let init_ix = initialize(&spl_token_2022::id(), &mint_pubkey, None, None).unwrap();
+        let mut message = Message::new(&[init_ix], None);
+        let compiled_instruction = &mut message.instructions[0];
+        assert_eq!(
+            parse_token(
+                compiled_instruction,
+                &AccountKeys::new(&message.account_keys, None)
+            )
+            .unwrap(),
+            ParsedInstructionEnum {
+                instruction_type: "initializeGroupPointer".to_string(),
+                info: json!({
+                    "mint": mint_pubkey.to_string(),
+                })
+            }
+        );
+
+        // Single owner Update
+        let update_ix = update(
+            &spl_token_2022::id(),
+            &mint_pubkey,
+            &authority,
+            &[],
+            Some(group_address),
+        )
+        .unwrap();
+        let mut message = Message::new(&[update_ix], None);
+        let compiled_instruction = &mut message.instructions[0];
+        assert_eq!(
+            parse_token(
+                compiled_instruction,
+                &AccountKeys::new(&message.account_keys, None)
+            )
+            .unwrap(),
+            ParsedInstructionEnum {
+                instruction_type: "updateGroupPointer".to_string(),
+                info: json!({
+                    "mint": mint_pubkey.to_string(),
+                    "authority": authority.to_string(),
+                    "groupAddress": group_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(group_address),
+        )
+        .unwrap();
+        let mut message = Message::new(&[update_ix], None);
+        let compiled_instruction = &mut message.instructions[0];
+        assert_eq!(
+            parse_token(
+                compiled_instruction,
+                &AccountKeys::new(&message.account_keys, None)
+            )
+            .unwrap(),
+            ParsedInstructionEnum {
+                instruction_type: "updateGroupPointer".to_string(),
+                info: json!({
+                    "mint": mint_pubkey.to_string(),
+                    "groupAddress": group_address.to_string(),
+                    "multisigAuthority": multisig_pubkey.to_string(),
+                    "signers": vec![
+                        multisig_signer0.to_string(),
+                        multisig_signer1.to_string(),
+                    ],
+                })
+            }
+        );
+    }
+}

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

@@ -4,6 +4,8 @@ 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 group_member_pointer;
+pub(super) mod group_pointer;
 pub(super) mod interest_bearing_mint;
 pub(super) mod memo_transfer;
 pub(super) mod metadata_pointer;