Jelajahi Sumber

solana/token_bridge: fix metaplex metadata handling and add support for token2022 tokens (including metadata pointers)

Csongor Kiss 1 bulan lalu
induk
melakukan
ebaf26a8b9
26 mengubah file dengan 5087 tambahan dan 43 penghapusan
  1. 6 2
      solana/Cargo.lock
  2. 4 0
      solana/Cargo.toml
  3. 2 2
      solana/Makefile
  4. 7 1
      solana/bridge/program/src/accounts/claim.rs
  5. 7 1
      solana/bridge/program/src/accounts/guardian_set.rs
  6. 7 1
      solana/bridge/program/src/accounts/posted_vaa.rs
  7. 7 1
      solana/bridge/program/src/accounts/sequence.rs
  8. 4 0
      solana/migration/src/types.rs
  9. 7 0
      solana/modules/nft_bridge/program/src/types.rs
  10. 1 0
      solana/modules/token_bridge/program/Cargo.toml
  11. 119 13
      solana/modules/token_bridge/program/src/accounts.rs
  12. 3 2
      solana/modules/token_bridge/program/src/api/attest.rs
  13. 10 4
      solana/modules/token_bridge/program/src/api/complete_transfer.rs
  14. 6 2
      solana/modules/token_bridge/program/src/api/complete_transfer_payload.rs
  15. 14 3
      solana/modules/token_bridge/program/src/api/create_wrapped.rs
  16. 12 4
      solana/modules/token_bridge/program/src/api/transfer.rs
  17. 1 0
      solana/modules/token_bridge/program/src/lib.rs
  18. 119 3
      solana/modules/token_bridge/program/src/types.rs
  19. 3905 0
      solana/modules/token_bridge/token_metadata_parser/Cargo.lock
  20. 21 0
      solana/modules/token_bridge/token_metadata_parser/Cargo.toml
  21. 13 0
      solana/modules/token_bridge/token_metadata_parser/README.md
  22. 661 0
      solana/modules/token_bridge/token_metadata_parser/src/lib.rs
  23. 42 3
      solana/solitaire/program/src/macros.rs
  24. 5 0
      solana/solitaire/program/src/processors/peel.rs
  25. 100 1
      solana/solitaire/program/src/processors/seeded.rs
  26. 4 0
      whitepapers/0003_token_bridge.md

+ 6 - 2
solana/Cargo.lock

@@ -3969,8 +3969,7 @@ dependencies = [
 [[package]]
 name = "spl-token"
 version = "3.3.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0cc67166ef99d10c18cb5e9c208901e6d8255c6513bb1f877977eba48e6cc4fb"
+source = "git+https://github.com/wormhole-foundation/spl-token.git?rev=7ae1b55#7ae1b553adb5889e57e7afb054e557ab7dd0d873"
 dependencies = [
  "arrayref",
  "num-derive",
@@ -4284,10 +4283,15 @@ dependencies = [
  "solana-sdk",
  "solitaire",
  "spl-token",
+ "token-metadata-parser",
  "wasm-bindgen",
  "wormhole-bridge-solana",
 ]
 
+[[package]]
+name = "token-metadata-parser"
+version = "0.1.0"
+
 [[package]]
 name = "token_bridge_client"
 version = "0.1.0"

+ 4 - 0
solana/Cargo.toml

@@ -11,6 +11,10 @@ members = [
     "solitaire/program",
     "solitaire/rocksalt",
 ]
+exclude = [
+    "modules/token_bridge/token_metadata_parser",
+]
 
 [patch.crates-io]
 memmap2 = { path = "bridge/memmap2-rs" }
+spl-token = { git = "https://github.com/wormhole-foundation/spl-token.git", rev = "7ae1b55" }

+ 2 - 2
solana/Makefile

@@ -8,9 +8,9 @@ nft_bridge_AUTHORITY_mainnet=3cVZHphy4QUYnU1hYFyvHF9joeZJ6ZTxpWx1nzavaUa8
 
 # Testnet buffer authority is the deployer public key
 bridge_ADDRESS_testnet=3u8hJUVTA4jH1wYAyUur7FFZVQ8H635K3tSHHF4ssjQ5
-bridge_AUTHORITY_testnet=9r6q2iEg4MBevjC8reaLmQUDxueF3vabUoqDkZ2LoAYe
+bridge_AUTHORITY_testnet=J8am6SkUHRTtLPJpnfUd6Uy38U7Yh17fa7ZtiqaLoJcV
 token_bridge_ADDRESS_testnet=DZnkkTmCiFWfYTfT41X3Rd1kDgozqzxWaHqsw6W4x2oe
-token_bridge_AUTHORITY_testnet=9r6q2iEg4MBevjC8reaLmQUDxueF3vabUoqDkZ2LoAYe
+token_bridge_AUTHORITY_testnet=FQAHqBcVHiiiLP8qXKPDQGr3mEXLv7RSdvfHJ3ZLugBV
 nft_bridge_ADDRESS_testnet=2rHhojZ7hpu1zA91nvZmT8TqWWvMcKmmNBCr2mKTtMq4
 nft_bridge_AUTHORITY_testnet=9r6q2iEg4MBevjC8reaLmQUDxueF3vabUoqDkZ2LoAYe
 

+ 7 - 1
solana/bridge/program/src/accounts/claim.rs

@@ -34,7 +34,10 @@ use serde::{
 };
 use solana_program::pubkey::Pubkey;
 use solitaire::{
-    processors::seeded::Seeded,
+    processors::seeded::{
+        Seeded,
+        SingleOwned,
+    },
     AccountOwner,
     AccountState::*,
     CreationLamports::Exempt,
@@ -100,6 +103,9 @@ impl Owned for ClaimData {
     }
 }
 
+impl SingleOwned for ClaimData {
+}
+
 pub struct ClaimDerivationData {
     pub emitter_address: [u8; 32],
     pub emitter_chain: u16,

+ 7 - 1
solana/bridge/program/src/accounts/guardian_set.rs

@@ -11,7 +11,10 @@ use serde::{
     Serialize,
 };
 use solitaire::{
-    processors::seeded::Seeded,
+    processors::seeded::{
+        Seeded,
+        SingleOwned,
+    },
     AccountOwner,
     AccountState,
     Data,
@@ -63,3 +66,6 @@ impl Owned for GuardianSetData {
         AccountOwner::This
     }
 }
+
+impl SingleOwned for GuardianSetData {
+}

+ 7 - 1
solana/bridge/program/src/accounts/posted_vaa.rs

@@ -4,7 +4,10 @@ use borsh::{
     BorshSerialize,
 };
 use solitaire::{
-    processors::seeded::Seeded,
+    processors::seeded::{
+        Seeded,
+        SingleOwned,
+    },
     AccountOwner,
     AccountState,
     Data,
@@ -94,6 +97,9 @@ impl Owned for PostedVAAData {
     }
 }
 
+impl SingleOwned for PostedVAAData {
+}
+
 #[cfg(feature = "cpi")]
 impl Owned for PostedVAAData {
     fn owner(&self) -> AccountOwner {

+ 7 - 1
solana/bridge/program/src/accounts/sequence.rs

@@ -4,7 +4,10 @@ use borsh::{
 };
 use solana_program::pubkey::Pubkey;
 use solitaire::{
-    processors::seeded::Seeded,
+    processors::seeded::{
+        Seeded,
+        SingleOwned,
+    },
     AccountOwner,
     AccountState,
     Data,
@@ -36,3 +39,6 @@ impl Owned for SequenceTracker {
         AccountOwner::This
     }
 }
+
+impl SingleOwned for SequenceTracker {
+}

+ 4 - 0
solana/migration/src/types.rs

@@ -12,6 +12,7 @@ use solitaire::{
     processors::seeded::{
         AccountOwner,
         Owned,
+        SingleOwned,
     },
 };
 use spl_token::state::{
@@ -31,5 +32,8 @@ impl Owned for PoolData {
     }
 }
 
+impl SingleOwned for PoolData {
+}
+
 pack_type!(SplMint, Mint, AccountOwner::Other(spl_token::id()));
 pack_type!(SplAccount, Account, AccountOwner::Other(spl_token::id()));

+ 7 - 0
solana/modules/nft_bridge/program/src/types.rs

@@ -12,6 +12,7 @@ use solitaire::{
     processors::seeded::{
         AccountOwner,
         Owned,
+        SingleOwned,
     },
 };
 use spl_token::state::{
@@ -45,6 +46,9 @@ impl Owned for EndpointRegistration {
     }
 }
 
+impl SingleOwned for EndpointRegistration {
+}
+
 #[derive(Default, Clone, Copy, BorshDeserialize, BorshSerialize, Serialize, Deserialize)]
 pub struct WrappedMeta {
     pub chain: ChainID,
@@ -58,5 +62,8 @@ impl Owned for WrappedMeta {
     }
 }
 
+impl SingleOwned for WrappedMeta {
+}
+
 pack_type!(SplMint, Mint, AccountOwner::Other(spl_token::id()));
 pack_type!(SplAccount, Account, AccountOwner::Other(spl_token::id()));

+ 1 - 0
solana/modules/token_bridge/program/Cargo.toml

@@ -19,6 +19,7 @@ instructions = []
 
 [dependencies]
 wormhole-bridge-solana = { path = "../../../bridge/program", features = ["no-entrypoint", "cpi"] }
+token-metadata-parser = { path = "../token_metadata_parser" }
 borsh = "=0.9.3"
 bstr = "0.2.16"
 byteorder = "1.4.3"

+ 119 - 13
solana/modules/token_bridge/program/src/accounts.rs

@@ -90,6 +90,9 @@ impl<'b, const STATE: AccountState> Seeded<&EndpointDerivationData> for Endpoint
     }
 }
 
+/// Metadata account for the token. This used to be exclusively the Metaplex
+/// metadata account, but with token2022's metadata pointer extension, this may be any account.
+/// `deserialize_and_verify_metadata` verifies that this account is what the token specifies (or falls back to Metaplex).
 pub type SplTokenMeta<'b> = Info<'b>;
 
 pub struct SplTokenMetaDerivationData {
@@ -106,39 +109,142 @@ impl<'b> Seeded<&SplTokenMetaDerivationData> for SplTokenMeta<'b> {
     }
 }
 
+/// New data length of spl token metadata account (post https://developers.metaplex.com/token-metadata/guides/account-size-reduction)
+/// The naming convention we adopt for future resizes is iterated NEW_* prefixes, e.g. NEW_NEW_NEW_NEW_NEW_MAX_METADATA_LEN.
+/// This will continue until morale improves.
+pub const NEW_MAX_METADATA_LEN: usize = 607;
+
+/// Converts Token-2022 metadata to Metaplex metadata format for compatibility
+fn convert_token2022_to_metaplex_metadata(
+    token_metadata: &token_metadata_parser::TokenMetadata,
+) -> spl_token_metadata::state::Metadata {
+    use spl_token_metadata::state::{
+        Data,
+        Key,
+        Metadata,
+    };
+
+    let data = Data {
+        name: token_metadata.name.clone(),
+        symbol: token_metadata.symbol.clone(),
+        uri: token_metadata.uri.clone(),
+        seller_fee_basis_points: 0, // Token-2022 doesn't have this concept
+        creators: None,             // Token-2022 doesn't have creators
+    };
+
+    Metadata {
+        key: Key::MetadataV1,
+        update_authority: token_metadata
+            .update_authority
+            .map(|pubkey| Pubkey::new(&pubkey.0))
+            .unwrap_or_default(),
+        mint: Pubkey::new(&token_metadata.mint.0),
+        data,
+        primary_sale_happened: false, // Default for Token-2022
+        is_mutable: token_metadata.update_authority.is_some(),
+        edition_nonce: None,
+        token_standard: None,
+        collection: None,
+        uses: None,
+        collection_details: None,
+        programmable_config: None,
+    }
+}
+
 /// This method removes code duplication when checking token metadata. When metadata is read for
 /// attestation and transfers, Token Bridge does not invoke Metaplex's Token Metadata program, so
 /// it must validate the account the same way Token Metadata program does to ensure the correct
 /// account is passed into Token Bridge's instruction context.
 pub fn deserialize_and_verify_metadata(
-    info: &Info,
+    mint: &Info,
+    metadata: &Info,
     derivation_data: SplTokenMetaDerivationData,
-) -> Result<spl_token_metadata::state::Metadata> {
-    // Verify pda.
-    info.verify_derivation(&spl_token_metadata::id(), &derivation_data)?;
+) -> Result<Option<spl_token_metadata::state::Metadata>> {
+    let mint_metadata = token_metadata_parser::parse_token2022_metadata(
+        token_metadata_parser::Pubkey::new(mint.key.to_bytes()),
+        &mint.data.borrow(),
+    )
+    .map_err(|_| TokenBridgeError::InvalidMetadata)?;
+
+    // we constrain the `metadata` account in every case.
+    // 1. if mint is token-2022 with embedded metadata, we return that metadata (in this case, `metadata` == `mint`. `token_metadata_parser` ensures this)
+    // 2. if mint is token-2022 with external metadata pointer, we verify `metadata` matches the pointer
+    //    a. if `metadata` is owned by spl-token-metadata, we verify the pda and deserialise it as standard Metaplex metadata
+    //    b. if `metadata` is not owned by spl-token-metadata, we don't verify that it's a pda (we know it matches the pointer already)
+    // 3. if mint doesn't include a metadata pointer, we ensure `metadata` is the metaplex pda.
+    //    this is the legacy case, but it applies to token2022 tokens as well (that have no metadata pointer extension)
+    //
+    // Note that in every case other than 1 (which is a well-defined spec via
+    // the token metadata extension), we parse the `metadata` account following
+    // the standard metaplex format.
+    //
+    // In case of 2b, this is a best-effort guess, because the metadata pointer
+    // extension makes no guarantees about the shape of the metadata account. However, a common practice is to just follow the metaplex format.
+    // What this means is that if the metadata account is not owned by the metaplex program, and is not in the metaplex format, the deserialisation will fail.
+    // We just don't support these tokens.
+
+    match mint_metadata {
+        // 1.
+        token_metadata_parser::MintMetadata::Embedded(token_metadata) => {
+            // token-2022 mint with embedded metadata
+            Ok(Some(convert_token2022_to_metaplex_metadata(
+                &token_metadata,
+            )))
+        }
+        // 2.
+        token_metadata_parser::MintMetadata::External(pointer) => {
+            if pointer.metadata_address
+                != token_metadata_parser::Pubkey::new(metadata.key.to_bytes())
+            {
+                return Err(TokenBridgeError::WrongMetadataAccount.into());
+            }
 
-    // There must be account data for token's metadata.
-    if info.data_is_empty() {
-        return Err(TokenBridgeError::NonexistentTokenMetadataAccount.into());
+            // 2a.
+            if *metadata.owner == spl_token_metadata::id() {
+                // Standard Metaplex metadata verification and parsing
+                // Verify pda.
+                metadata.verify_derivation(&spl_token_metadata::id(), &derivation_data)?;
+            }
+            deserialize_metaplex_formatted_metadata_account(metadata)
+        }
+        // 3.
+        token_metadata_parser::MintMetadata::None => {
+            // Account must belong to Metaplex Token Metadata program.
+            if *metadata.owner != Pubkey::default() && *metadata.owner != spl_token_metadata::id() {
+                return Err(TokenBridgeError::WrongAccountOwner.into());
+            }
+            // Standard Metaplex metadata verification and parsing
+            // Verify pda.
+            metadata.verify_derivation(&spl_token_metadata::id(), &derivation_data)?;
+            deserialize_metaplex_formatted_metadata_account(metadata)
+        }
     }
+}
 
-    // Account must belong to Metaplex Token Metadata program.
-    if *info.owner != spl_token_metadata::id() {
-        return Err(TokenBridgeError::WrongAccountOwner.into());
+/// Deserialises a Metaplex formatted metadata account. Here we assume that the
+/// metadata account has been checked to be the correct account for the mint.
+/// As such, if it's empty, it just means there is no metadata.
+fn deserialize_metaplex_formatted_metadata_account(
+    metadata: &Info,
+) -> Result<Option<spl_token_metadata::state::Metadata>> {
+    if metadata.data_is_empty() {
+        return Ok(None);
     }
 
     // Account must be the expected Metadata length.
-    if info.data_len() != spl_token_metadata::state::MAX_METADATA_LEN {
+    if metadata.data_len() != spl_token_metadata::state::MAX_METADATA_LEN
+        && metadata.data_len() != NEW_MAX_METADATA_LEN
+    {
         return Err(TokenBridgeError::InvalidMetadata.into());
     }
 
-    let mut data: &[u8] = &info.data.borrow_mut();
+    let mut data: &[u8] = &metadata.data.borrow_mut();
 
     // Unfortunately we cannot use `map_err` easily, so we will match certain deserialization conditions.
     match spl_token_metadata::utils::meta_deser_unchecked(&mut data) {
         Ok(deserialized) => {
             if deserialized.key == MetadataV1 {
-                Ok(deserialized)
+                Ok(Some(deserialized))
             } else {
                 Err(TokenBridgeError::NotMetadataV1Account.into())
             }

+ 3 - 2
solana/modules/token_bridge/program/src/api/attest.rs

@@ -116,8 +116,9 @@ pub fn attest_token(
     };
 
     // Assign metadata if an SPL Metadata account exists for the SPL token in question.
-    if !accs.spl_metadata.data_is_empty() {
-        let metadata = deserialize_and_verify_metadata(&accs.spl_metadata, (&*accs).into())?;
+    if let Some(metadata) =
+        deserialize_and_verify_metadata(accs.mint.info(), &accs.spl_metadata, (&*accs).into())?
+    {
         payload.name = metadata.data.name.clone();
         payload.symbol = metadata.data.symbol;
     }

+ 10 - 4
solana/modules/token_bridge/program/src/api/complete_transfer.rs

@@ -133,9 +133,11 @@ pub fn complete_native(
         .checked_sub(fee)
         .ok_or(SolitaireError::InsufficientFunds)?;
 
+    let token_program = accs.mint.info().owner;
+
     // Transfer tokens
     let transfer_ix = spl_token::instruction::transfer(
-        &spl_token::id(),
+        token_program,
         accs.custody.info().key,
         accs.to.info().key,
         accs.custody_signer.key,
@@ -144,9 +146,11 @@ pub fn complete_native(
     )?;
     invoke_seeded(&transfer_ix, ctx, &accs.custody_signer, None)?;
 
+    let token_program = accs.mint.info().owner;
+
     // Transfer fees
     let transfer_ix = spl_token::instruction::transfer(
-        &spl_token::id(),
+        token_program,
         accs.custody.info().key,
         accs.to_fees.info().key,
         accs.custody_signer.key,
@@ -249,9 +253,11 @@ pub fn complete_wrapped(
         .checked_sub(accs.vaa.fee.as_u64())
         .ok_or(SolitaireError::InsufficientFunds)?;
 
+    let token_program = accs.mint.info().owner;
+
     // Mint tokens
     let mint_ix = spl_token::instruction::mint_to(
-        &spl_token::id(),
+        token_program,
         accs.mint.info().key,
         accs.to.info().key,
         accs.mint_authority.key,
@@ -262,7 +268,7 @@ pub fn complete_wrapped(
 
     // Mint fees
     let mint_ix = spl_token::instruction::mint_to(
-        &spl_token::id(),
+        token_program,
         accs.mint.info().key,
         accs.to_fees.info().key,
         accs.mint_authority.key,

+ 6 - 2
solana/modules/token_bridge/program/src/api/complete_transfer_payload.rs

@@ -193,9 +193,11 @@ pub fn complete_native_with_payload(
         amount *= 10u64.pow((accs.mint.decimals - 8) as u32);
     }
 
+    let token_program = accs.mint.info().owner;
+
     // Transfer tokens
     let transfer_ix = spl_token::instruction::transfer(
-        &spl_token::id(),
+        token_program,
         accs.custody.info().key,
         accs.to.info().key,
         accs.custody_signer.key,
@@ -298,9 +300,11 @@ pub fn complete_wrapped_with_payload(
 
     claim::consume(ctx, accs.payer.key, &mut accs.claim, &accs.vaa)?;
 
+    let token_program = accs.mint.info().owner;
+
     // Mint tokens
     let mint_ix = spl_token::instruction::mint_to(
-        &spl_token::id(),
+        token_program,
         accs.mint.info().key,
         accs.to.info().key,
         accs.mint_authority.key,

+ 14 - 3
solana/modules/token_bridge/program/src/api/create_wrapped.rs

@@ -34,6 +34,7 @@ use solana_program::{
 use solitaire::{
     processors::seeded::{
         invoke_seeded,
+        CreatableWithOwner,
         Seeded,
     },
     CreationLamports::Exempt,
@@ -130,9 +131,17 @@ pub fn create_accounts(
     accs: &mut CreateWrapped,
     _data: CreateWrappedData,
 ) -> Result<()> {
+    // wrapped accounts use legacy spl
+    let token_program = spl_token::id();
+
     // Create mint account
-    accs.mint
-        .create(&((&*accs).into()), ctx, accs.payer.key, Exempt)?;
+    accs.mint.create_with_owner(
+        &token_program,
+        &((&*accs).into()),
+        ctx,
+        accs.payer.key,
+        Exempt,
+    )?;
 
     // Initialize mint
     let init_ix = spl_token::instruction::initialize_mint(
@@ -196,11 +205,13 @@ pub fn update_accounts(
     // Checks in this method are redundant with what occurs in `update_metadata_accounts_v2`, but we want to make
     // sure that the account we are deserializing is legitimate.
     let metadata = deserialize_and_verify_metadata(
+        accs.mint.info(),
         &accs.spl_metadata,
         SplTokenMetaDerivationData {
             mint: *accs.mint.info().key,
         },
-    )?;
+    )?
+    .unwrap();
 
     // Normalize token metadata's name and symbol.
     let new_data_v2 = spl_token_metadata::state::DataV2 {

+ 12 - 4
solana/modules/token_bridge/program/src/api/transfer.rs

@@ -45,6 +45,7 @@ use solana_program::{
 use solitaire::{
     processors::seeded::{
         invoke_seeded,
+        CreatableWithOwner,
         Seeded,
     },
     CreationLamports::Exempt,
@@ -203,11 +204,13 @@ pub fn verify_and_execute_native_transfers(
         }
     }
 
+    let token_program = mint.info().owner;
+
     if !custody.is_initialized() {
-        custody.create(derivation_data, ctx, payer.key, Exempt)?;
+        custody.create_with_owner(token_program, derivation_data, ctx, payer.key, Exempt)?;
 
         let init_ix = spl_token::instruction::initialize_account(
-            &spl_token::id(),
+            token_program,
             custody.info().key,
             mint.info().key,
             custody_signer.key,
@@ -222,9 +225,12 @@ pub fn verify_and_execute_native_transfers(
     // Untruncate the amount to drop the remainder so we don't  "burn" user's funds.
     let amount_trunc: u64 = amount * trunc_divisor;
 
+    // the token program is the owner of the mint account (either spl or token2022)
+    let token_program = mint.info().owner;
+
     // Transfer tokens
     let transfer_ix = spl_token::instruction::transfer(
-        &spl_token::id(),
+        token_program,
         from.info().key,
         custody.info().key,
         authority_signer.key,
@@ -397,9 +403,11 @@ pub fn verify_and_execute_wrapped_transfers(
     // Verify that meta is correct
     wrapped_meta.verify_derivation(ctx.program_id, derivation_data)?;
 
+    let token_program = mint.info().owner;
+
     // Burn tokens
     let burn_ix = spl_token::instruction::burn(
-        &spl_token::id(),
+        token_program,
         from.info().key,
         mint.info().key,
         authority_signer.key,

+ 1 - 0
solana/modules/token_bridge/program/src/lib.rs

@@ -91,6 +91,7 @@ pub enum TokenBridgeError {
     InvalidVAA,
     NonexistentTokenMetadataAccount,
     NotMetadataV1Account,
+    WrongMetadataAccount,
 }
 
 impl From<TokenBridgeError> for SolitaireError {

+ 119 - 3
solana/modules/token_bridge/program/src/types.rs

@@ -6,12 +6,20 @@ use serde::{
     Deserialize,
     Serialize,
 };
-use solana_program::pubkey::Pubkey;
+use solana_program::{
+    program_error::ProgramError,
+    program_pack::{
+        IsInitialized,
+        Pack,
+    },
+    pubkey::Pubkey,
+};
 use solitaire::{
     pack_type,
     processors::seeded::{
         AccountOwner,
         Owned,
+        SingleOwned,
     },
 };
 use spl_token::state::{
@@ -55,6 +63,9 @@ impl Owned for EndpointRegistration {
     }
 }
 
+impl SingleOwned for EndpointRegistration {
+}
+
 #[cfg(feature = "cpi")]
 impl Owned for EndpointRegistration {
     fn owner(&self) -> AccountOwner {
@@ -70,6 +81,9 @@ pub struct WrappedMeta {
     pub original_decimals: u8,
 }
 
+impl SingleOwned for WrappedMeta {
+}
+
 #[cfg(not(feature = "cpi"))]
 impl Owned for WrappedMeta {
     fn owner(&self) -> AccountOwner {
@@ -85,5 +99,107 @@ impl Owned for WrappedMeta {
     }
 }
 
-pack_type!(SplMint, Mint, AccountOwner::Other(spl_token::id()));
-pack_type!(SplAccount, Account, AccountOwner::Other(spl_token::id()));
+pub mod spl_token_2022 {
+    use solana_program::pubkey::Pubkey;
+    use std::str::FromStr;
+
+    pub fn id() -> Pubkey {
+        Pubkey::from_str("TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb").unwrap()
+    }
+}
+
+trait MintExtensionPack: Pack {
+    fn unpack(input: &[u8]) -> Result<Self, ProgramError>;
+}
+
+// from: https://github.com/solana-program/token-2022/blob/9cbf7a1e9bab57aabb71b4b84fc84e3670108573/interface/src/extension/mod.rs#L262-L268
+// Since there is no account discriminator in these accounts, it's possible to confuse a multisig account for a mint account.
+// This check prevents that by ensuring the length is not equal to a multisig account length.
+// Mint accounts that happen to be 355 bytes long are out of luck (but this won't concern us, if it's even possible).
+fn check_min_len_and_not_multisig(input: &[u8], minimum_len: usize) -> Result<(), ProgramError> {
+    const MULTISIG_LEN: usize = 355; // spl_token::state::Multisig::LEN;
+    if input.len() == MULTISIG_LEN || input.len() < minimum_len {
+        Err(ProgramError::InvalidAccountData)
+    } else {
+        Ok(())
+    }
+}
+
+impl MintExtensionPack for Mint {
+    // this implementation is almost identical to the default Pack::unpack,
+    // except for the length check. Instead of requiring exact length, we require
+    // a minimum length, to allow for extensions.
+    fn unpack(input: &[u8]) -> Result<Self, ProgramError> {
+        check_min_len_and_not_multisig(input, Self::LEN)?;
+        let value: Mint = solana_program::program_pack::Pack::unpack_from_slice(input)?;
+        if value.is_initialized() {
+            Ok(value)
+        } else {
+            Err(ProgramError::UninitializedAccount)
+        }
+    }
+}
+
+pack_type!(
+    SplMint,
+    Mint,
+    AccountOwner::OneOf(vec![spl_token::id(), spl_token_2022::id()]),
+    MintExtensionPack
+);
+pack_type!(
+    SplAccount,
+    Account,
+    AccountOwner::OneOf(vec![spl_token::id(), spl_token_2022::id()])
+);
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn test_mint_unpack_from_slice_old_token() {
+        let src: [u8; 82] = [
+            0x01, 0x00, 0x00, 0x00, 0x64, 0xf1, 0x33, 0x5f, 0xe8, 0x35, 0x98, 0x99, 0x99, 0xfb,
+            0xd2, 0x84, 0x35, 0xc9, 0x0b, 0x89, 0x47, 0xfb, 0x25, 0x8f, 0x7a, 0xea, 0xcb, 0x19,
+            0xc8, 0x8f, 0x9b, 0x09, 0x7a, 0xe2, 0xc7, 0xe7, 0x00, 0xc0, 0x57, 0x73, 0xa5, 0x7c,
+            0x02, 0x00, 0x09, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        ];
+        let mint = <Mint as MintExtensionPack>::unpack(&src).unwrap();
+        assert!(mint.is_initialized);
+    }
+
+    #[test]
+    fn test_mint_unpack_from_slice_new_token() {
+        let src: [u8; 344] = [
+            0x01, 0x00, 0x00, 0x00, 0x67, 0x94, 0x7e, 0xf1, 0x3a, 0x15, 0x8c, 0xb9, 0xbf, 0xca,
+            0xbe, 0xa0, 0x18, 0xb3, 0xf8, 0xd2, 0xe5, 0x5b, 0x22, 0x81, 0xa7, 0x63, 0x62, 0x62,
+            0x42, 0x73, 0x97, 0x1d, 0xba, 0xfa, 0x1e, 0x99, 0x00, 0xe8, 0x76, 0x48, 0x17, 0x00,
+            0x00, 0x00, 0x09, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x12, 0x00,
+            0x40, 0x00, 0x67, 0x94, 0x7e, 0xf1, 0x3a, 0x15, 0x8c, 0xb9, 0xbf, 0xca, 0xbe, 0xa0,
+            0x18, 0xb3, 0xf8, 0xd2, 0xe5, 0x5b, 0x22, 0x81, 0xa7, 0x63, 0x62, 0x62, 0x42, 0x73,
+            0x97, 0x1d, 0xba, 0xfa, 0x1e, 0x99, 0x94, 0xf5, 0xf0, 0x2e, 0xe1, 0x66, 0xcd, 0x5e,
+            0x14, 0xa4, 0x1e, 0x22, 0xeb, 0x6d, 0x93, 0xda, 0x79, 0xd7, 0x50, 0xda, 0x9d, 0xbc,
+            0xa4, 0x84, 0xf1, 0x88, 0x3d, 0x47, 0x55, 0xbc, 0x6f, 0x5b, 0x13, 0x00, 0x6a, 0x00,
+            0x67, 0x94, 0x7e, 0xf1, 0x3a, 0x15, 0x8c, 0xb9, 0xbf, 0xca, 0xbe, 0xa0, 0x18, 0xb3,
+            0xf8, 0xd2, 0xe5, 0x5b, 0x22, 0x81, 0xa7, 0x63, 0x62, 0x62, 0x42, 0x73, 0x97, 0x1d,
+            0xba, 0xfa, 0x1e, 0x99, 0x94, 0xf5, 0xf0, 0x2e, 0xe1, 0x66, 0xcd, 0x5e, 0x14, 0xa4,
+            0x1e, 0x22, 0xeb, 0x6d, 0x93, 0xda, 0x79, 0xd7, 0x50, 0xda, 0x9d, 0xbc, 0xa4, 0x84,
+            0xf1, 0x88, 0x3d, 0x47, 0x55, 0xbc, 0x6f, 0x5b, 0x04, 0x00, 0x00, 0x00, 0x54, 0x65,
+            0x73, 0x74, 0x04, 0x00, 0x00, 0x00, 0x54, 0x65, 0x73, 0x74, 0x12, 0x00, 0x00, 0x00,
+            0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65,
+            0x2e, 0x63, 0x6f, 0x6d, 0x00, 0x00, 0x00, 0x00,
+        ];
+        let mint = <Mint as MintExtensionPack>::unpack(&src).unwrap();
+        assert!(mint.is_initialized);
+    }
+}

+ 3905 - 0
solana/modules/token_bridge/token_metadata_parser/Cargo.lock

@@ -0,0 +1,3905 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 4
+
+[[package]]
+name = "aead"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0"
+dependencies = [
+ "crypto-common",
+ "generic-array",
+]
+
+[[package]]
+name = "aes"
+version = "0.8.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0"
+dependencies = [
+ "cfg-if",
+ "cipher",
+ "cpufeatures",
+]
+
+[[package]]
+name = "aes-gcm-siv"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ae0784134ba9375416d469ec31e7c5f9fa94405049cf08c5ce5b4698be673e0d"
+dependencies = [
+ "aead",
+ "aes",
+ "cipher",
+ "ctr",
+ "polyval",
+ "subtle",
+ "zeroize",
+]
+
+[[package]]
+name = "ahash"
+version = "0.8.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75"
+dependencies = [
+ "cfg-if",
+ "getrandom 0.3.3",
+ "once_cell",
+ "version_check",
+ "zerocopy",
+]
+
+[[package]]
+name = "aho-corasick"
+version = "1.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "ark-bn254"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a22f4561524cd949590d78d7d4c5df8f592430d221f7f3c9497bbafd8972120f"
+dependencies = [
+ "ark-ec",
+ "ark-ff",
+ "ark-std",
+]
+
+[[package]]
+name = "ark-ec"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "defd9a439d56ac24968cca0571f598a61bc8c55f71d50a89cda591cb750670ba"
+dependencies = [
+ "ark-ff",
+ "ark-poly",
+ "ark-serialize",
+ "ark-std",
+ "derivative",
+ "hashbrown 0.13.2",
+ "itertools 0.10.5",
+ "num-traits",
+ "zeroize",
+]
+
+[[package]]
+name = "ark-ff"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec847af850f44ad29048935519032c33da8aa03340876d351dfab5660d2966ba"
+dependencies = [
+ "ark-ff-asm",
+ "ark-ff-macros",
+ "ark-serialize",
+ "ark-std",
+ "derivative",
+ "digest 0.10.7",
+ "itertools 0.10.5",
+ "num-bigint",
+ "num-traits",
+ "paste",
+ "rustc_version",
+ "zeroize",
+]
+
+[[package]]
+name = "ark-ff-asm"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3ed4aa4fe255d0bc6d79373f7e31d2ea147bcf486cba1be5ba7ea85abdb92348"
+dependencies = [
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "ark-ff-macros"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7abe79b0e4288889c4574159ab790824d0033b9fdcb2a112a3182fac2e514565"
+dependencies = [
+ "num-bigint",
+ "num-traits",
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "ark-poly"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d320bfc44ee185d899ccbadfa8bc31aab923ce1558716e1997a1e74057fe86bf"
+dependencies = [
+ "ark-ff",
+ "ark-serialize",
+ "ark-std",
+ "derivative",
+ "hashbrown 0.13.2",
+]
+
+[[package]]
+name = "ark-serialize"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "adb7b85a02b83d2f22f89bd5cac66c9c89474240cb6207cb1efc16d098e822a5"
+dependencies = [
+ "ark-serialize-derive",
+ "ark-std",
+ "digest 0.10.7",
+ "num-bigint",
+]
+
+[[package]]
+name = "ark-serialize-derive"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ae3281bc6d0fd7e549af32b52511e1302185bd688fd3359fa36423346ff682ea"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "ark-std"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "94893f1e0c6eeab764ade8dc4c0db24caf4fe7cbbaafc0eba0a9030f447b5185"
+dependencies = [
+ "num-traits",
+ "rand 0.8.5",
+]
+
+[[package]]
+name = "arrayref"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb"
+
+[[package]]
+name = "arrayvec"
+version = "0.7.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
+
+[[package]]
+name = "atty"
+version = "0.2.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
+dependencies = [
+ "hermit-abi",
+ "libc",
+ "winapi",
+]
+
+[[package]]
+name = "autocfg"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
+
+[[package]]
+name = "base64"
+version = "0.12.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff"
+
+[[package]]
+name = "base64"
+version = "0.22.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
+
+[[package]]
+name = "bincode"
+version = "1.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "bitflags"
+version = "2.9.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34efbcccd345379ca2868b2b2c9d3782e9cc58ba87bc7d79d5b53d9c9ae6f25d"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "blake3"
+version = "1.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3888aaa89e4b2a40fca9848e400f6a658a5a3978de7be858e209cafa8be9a4a0"
+dependencies = [
+ "arrayref",
+ "arrayvec",
+ "cc",
+ "cfg-if",
+ "constant_time_eq",
+ "digest 0.10.7",
+]
+
+[[package]]
+name = "block-buffer"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4"
+dependencies = [
+ "generic-array",
+]
+
+[[package]]
+name = "block-buffer"
+version = "0.10.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
+dependencies = [
+ "generic-array",
+]
+
+[[package]]
+name = "borsh"
+version = "0.10.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "115e54d64eb62cdebad391c19efc9dce4981c690c85a33a12199d99bb9546fee"
+dependencies = [
+ "borsh-derive 0.10.4",
+ "hashbrown 0.13.2",
+]
+
+[[package]]
+name = "borsh"
+version = "1.5.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ad8646f98db542e39fc66e68a20b2144f6a732636df7c2354e74645faaa433ce"
+dependencies = [
+ "borsh-derive 1.5.7",
+ "cfg_aliases",
+]
+
+[[package]]
+name = "borsh-derive"
+version = "0.10.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "831213f80d9423998dd696e2c5345aba6be7a0bd8cd19e31c5243e13df1cef89"
+dependencies = [
+ "borsh-derive-internal",
+ "borsh-schema-derive-internal",
+ "proc-macro-crate 0.1.5",
+ "proc-macro2",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "borsh-derive"
+version = "1.5.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fdd1d3c0c2f5833f22386f252fe8ed005c7f59fdcddeef025c01b4c3b9fd9ac3"
+dependencies = [
+ "once_cell",
+ "proc-macro-crate 3.3.0",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.106",
+]
+
+[[package]]
+name = "borsh-derive-internal"
+version = "0.10.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "65d6ba50644c98714aa2a70d13d7df3cd75cd2b523a2b452bf010443800976b3"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "borsh-schema-derive-internal"
+version = "0.10.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "276691d96f063427be83e6692b86148e488ebba9f48f77788724ca027ba3b6d4"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "bs58"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4"
+dependencies = [
+ "tinyvec",
+]
+
+[[package]]
+name = "bumpalo"
+version = "3.19.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43"
+
+[[package]]
+name = "bv"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8834bb1d8ee5dc048ee3124f2c7c1afcc6bc9aed03f11e9dfd8c69470a5db340"
+dependencies = [
+ "feature-probe",
+ "serde",
+]
+
+[[package]]
+name = "bytemuck"
+version = "1.23.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3995eaeebcdf32f91f980d360f78732ddc061097ab4e39991ae7a6ace9194677"
+dependencies = [
+ "bytemuck_derive",
+]
+
+[[package]]
+name = "bytemuck_derive"
+version = "1.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4f154e572231cb6ba2bd1176980827e3d5dc04cc183a75dea38109fbdd672d29"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.106",
+]
+
+[[package]]
+name = "byteorder"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
+
+[[package]]
+name = "cc"
+version = "1.2.34"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "42bc4aea80032b7bf409b0bc7ccad88853858911b7713a8062fdc0623867bedc"
+dependencies = [
+ "shlex",
+]
+
+[[package]]
+name = "cfg-if"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9"
+
+[[package]]
+name = "cfg_aliases"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
+
+[[package]]
+name = "cfg_eval"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "45565fc9416b9896014f5732ac776f810ee53a66730c17e4020c3ec064a8f88f"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.106",
+]
+
+[[package]]
+name = "chrono"
+version = "0.4.41"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d"
+dependencies = [
+ "num-traits",
+]
+
+[[package]]
+name = "cipher"
+version = "0.4.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
+dependencies = [
+ "crypto-common",
+ "inout",
+]
+
+[[package]]
+name = "console_error_panic_hook"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc"
+dependencies = [
+ "cfg-if",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "console_log"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e89f72f65e8501878b8a004d5a1afb780987e2ce2b4532c562e367a72c57499f"
+dependencies = [
+ "log",
+ "web-sys",
+]
+
+[[package]]
+name = "constant_time_eq"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6"
+
+[[package]]
+name = "cpufeatures"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "crunchy"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5"
+
+[[package]]
+name = "crypto-common"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
+dependencies = [
+ "generic-array",
+ "rand_core 0.6.4",
+ "typenum",
+]
+
+[[package]]
+name = "crypto-mac"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b584a330336237c1eecd3e94266efb216c56ed91225d634cb2991c5f3fd1aeab"
+dependencies = [
+ "generic-array",
+ "subtle",
+]
+
+[[package]]
+name = "ctr"
+version = "0.9.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835"
+dependencies = [
+ "cipher",
+]
+
+[[package]]
+name = "curve25519-dalek"
+version = "3.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b9fdf9972b2bd6af2d913799d9ebc165ea4d2e65878e329d9c6b372c4491b61"
+dependencies = [
+ "byteorder",
+ "digest 0.9.0",
+ "rand_core 0.5.1",
+ "subtle",
+ "zeroize",
+]
+
+[[package]]
+name = "curve25519-dalek"
+version = "4.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be"
+dependencies = [
+ "cfg-if",
+ "cpufeatures",
+ "curve25519-dalek-derive",
+ "digest 0.10.7",
+ "fiat-crypto",
+ "rand_core 0.6.4",
+ "rustc_version",
+ "serde",
+ "subtle",
+ "zeroize",
+]
+
+[[package]]
+name = "curve25519-dalek-derive"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.106",
+]
+
+[[package]]
+name = "darling"
+version = "0.20.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee"
+dependencies = [
+ "darling_core",
+ "darling_macro",
+]
+
+[[package]]
+name = "darling_core"
+version = "0.20.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e"
+dependencies = [
+ "fnv",
+ "ident_case",
+ "proc-macro2",
+ "quote",
+ "strsim",
+ "syn 2.0.106",
+]
+
+[[package]]
+name = "darling_macro"
+version = "0.20.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead"
+dependencies = [
+ "darling_core",
+ "quote",
+ "syn 2.0.106",
+]
+
+[[package]]
+name = "derivation-path"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6e5c37193a1db1d8ed868c03ec7b152175f26160a5b740e5e484143877e0adf0"
+
+[[package]]
+name = "derivative"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "digest"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066"
+dependencies = [
+ "generic-array",
+]
+
+[[package]]
+name = "digest"
+version = "0.10.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
+dependencies = [
+ "block-buffer 0.10.4",
+ "crypto-common",
+ "subtle",
+]
+
+[[package]]
+name = "ed25519"
+version = "1.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "91cff35c70bba8a626e3185d8cd48cc11b5437e1a5bcd15b9b5fa3c64b6dfee7"
+dependencies = [
+ "signature",
+]
+
+[[package]]
+name = "ed25519-dalek"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c762bae6dcaf24c4c84667b8579785430908723d5c889f469d76a41d59cc7a9d"
+dependencies = [
+ "curve25519-dalek 3.2.0",
+ "ed25519",
+ "rand 0.7.3",
+ "serde",
+ "sha2 0.9.9",
+ "zeroize",
+]
+
+[[package]]
+name = "ed25519-dalek-bip32"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9d2be62a4061b872c8c0873ee4fc6f101ce7b889d039f019c5fa2af471a59908"
+dependencies = [
+ "derivation-path",
+ "ed25519-dalek",
+ "hmac 0.12.1",
+ "sha2 0.10.9",
+]
+
+[[package]]
+name = "either"
+version = "1.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
+
+[[package]]
+name = "env_logger"
+version = "0.9.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a12e6657c4c97ebab115a42dcee77225f7f482cdd841cf7088c657a42e9e00e7"
+dependencies = [
+ "atty",
+ "humantime",
+ "log",
+ "regex",
+ "termcolor",
+]
+
+[[package]]
+name = "equivalent"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
+
+[[package]]
+name = "feature-probe"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "835a3dc7d1ec9e75e2b5fb4ba75396837112d2060b03f7d43bc1897c7f7211da"
+
+[[package]]
+name = "fiat-crypto"
+version = "0.2.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d"
+
+[[package]]
+name = "five8"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a75b8549488b4715defcb0d8a8a1c1c76a80661b5fa106b4ca0e7fce59d7d875"
+dependencies = [
+ "five8_core",
+]
+
+[[package]]
+name = "five8_const"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "26dec3da8bc3ef08f2c04f61eab298c3ab334523e55f076354d6d6f613799a7b"
+dependencies = [
+ "five8_core",
+]
+
+[[package]]
+name = "five8_core"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2551bf44bc5f776c15044b9b94153a00198be06743e262afaaa61f11ac7523a5"
+
+[[package]]
+name = "fnv"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
+
+[[package]]
+name = "foreign-types"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
+dependencies = [
+ "foreign-types-shared",
+]
+
+[[package]]
+name = "foreign-types-shared"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
+
+[[package]]
+name = "generic-array"
+version = "0.14.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
+dependencies = [
+ "typenum",
+ "version_check",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.1.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce"
+dependencies = [
+ "cfg-if",
+ "js-sys",
+ "libc",
+ "wasi 0.9.0+wasi-snapshot-preview1",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.2.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592"
+dependencies = [
+ "cfg-if",
+ "js-sys",
+ "libc",
+ "wasi 0.11.1+wasi-snapshot-preview1",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "r-efi",
+ "wasi 0.14.2+wasi-0.2.4",
+]
+
+[[package]]
+name = "hashbrown"
+version = "0.13.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e"
+dependencies = [
+ "ahash",
+]
+
+[[package]]
+name = "hashbrown"
+version = "0.15.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
+
+[[package]]
+name = "hermit-abi"
+version = "0.1.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "hex"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
+
+[[package]]
+name = "hmac"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "126888268dcc288495a26bf004b38c5fdbb31682f992c84ceb046a1f0fe38840"
+dependencies = [
+ "crypto-mac",
+ "digest 0.9.0",
+]
+
+[[package]]
+name = "hmac"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e"
+dependencies = [
+ "digest 0.10.7",
+]
+
+[[package]]
+name = "hmac-drbg"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "17ea0a1394df5b6574da6e0c1ade9e78868c9fb0a4e5ef4428e32da4676b85b1"
+dependencies = [
+ "digest 0.9.0",
+ "generic-array",
+ "hmac 0.8.1",
+]
+
+[[package]]
+name = "humantime"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b112acc8b3adf4b107a8ec20977da0273a8c386765a3ec0229bd500a1443f9f"
+
+[[package]]
+name = "ident_case"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
+
+[[package]]
+name = "indexmap"
+version = "2.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f2481980430f9f78649238835720ddccc57e52df14ffce1c6f37391d61b563e9"
+dependencies = [
+ "equivalent",
+ "hashbrown 0.15.5",
+]
+
+[[package]]
+name = "inout"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01"
+dependencies = [
+ "generic-array",
+]
+
+[[package]]
+name = "itertools"
+version = "0.10.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
+dependencies = [
+ "either",
+]
+
+[[package]]
+name = "itertools"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569"
+dependencies = [
+ "either",
+]
+
+[[package]]
+name = "itoa"
+version = "1.0.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
+
+[[package]]
+name = "js-sys"
+version = "0.3.77"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f"
+dependencies = [
+ "once_cell",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "keccak"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654"
+dependencies = [
+ "cpufeatures",
+]
+
+[[package]]
+name = "lazy_static"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
+
+[[package]]
+name = "libc"
+version = "0.2.175"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543"
+
+[[package]]
+name = "libsecp256k1"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c9d220bc1feda2ac231cb78c3d26f27676b8cf82c96971f7aeef3d0cf2797c73"
+dependencies = [
+ "arrayref",
+ "base64 0.12.3",
+ "digest 0.9.0",
+ "hmac-drbg",
+ "libsecp256k1-core",
+ "libsecp256k1-gen-ecmult",
+ "libsecp256k1-gen-genmult",
+ "rand 0.7.3",
+ "serde",
+ "sha2 0.9.9",
+ "typenum",
+]
+
+[[package]]
+name = "libsecp256k1-core"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d0f6ab710cec28cef759c5f18671a27dae2a5f952cdaaee1d8e2908cb2478a80"
+dependencies = [
+ "crunchy",
+ "digest 0.9.0",
+ "subtle",
+]
+
+[[package]]
+name = "libsecp256k1-gen-ecmult"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ccab96b584d38fac86a83f07e659f0deafd0253dc096dab5a36d53efe653c5c3"
+dependencies = [
+ "libsecp256k1-core",
+]
+
+[[package]]
+name = "libsecp256k1-gen-genmult"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "67abfe149395e3aa1c48a2beb32b068e2334402df8181f818d3aee2b304c4f5d"
+dependencies = [
+ "libsecp256k1-core",
+]
+
+[[package]]
+name = "lock_api"
+version = "0.4.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765"
+dependencies = [
+ "autocfg",
+ "scopeguard",
+]
+
+[[package]]
+name = "log"
+version = "0.4.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
+
+[[package]]
+name = "memchr"
+version = "2.7.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0"
+
+[[package]]
+name = "memmap2"
+version = "0.5.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "83faa42c0a078c393f6b29d5db232d8be22776a891f8f56e5284faee4a20b327"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "memoffset"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "merlin"
+version = "3.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "58c38e2799fc0978b65dfff8023ec7843e2330bb462f19198840b34b6582397d"
+dependencies = [
+ "byteorder",
+ "keccak",
+ "rand_core 0.6.4",
+ "zeroize",
+]
+
+[[package]]
+name = "num-bigint"
+version = "0.4.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9"
+dependencies = [
+ "num-integer",
+ "num-traits",
+]
+
+[[package]]
+name = "num-derive"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.106",
+]
+
+[[package]]
+name = "num-integer"
+version = "0.1.46"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f"
+dependencies = [
+ "num-traits",
+]
+
+[[package]]
+name = "num-traits"
+version = "0.2.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "num_enum"
+version = "0.7.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a973b4e44ce6cad84ce69d797acf9a044532e4184c4f267913d1b546a0727b7a"
+dependencies = [
+ "num_enum_derive",
+ "rustversion",
+]
+
+[[package]]
+name = "num_enum_derive"
+version = "0.7.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "77e878c846a8abae00dd069496dbe8751b16ac1c3d6bd2a7283a938e8228f90d"
+dependencies = [
+ "proc-macro-crate 3.3.0",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.106",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.21.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
+
+[[package]]
+name = "opaque-debug"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381"
+
+[[package]]
+name = "openssl"
+version = "0.10.73"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8505734d46c8ab1e19a1dce3aef597ad87dcb4c37e7188231769bd6bd51cebf8"
+dependencies = [
+ "bitflags",
+ "cfg-if",
+ "foreign-types",
+ "libc",
+ "once_cell",
+ "openssl-macros",
+ "openssl-sys",
+]
+
+[[package]]
+name = "openssl-macros"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.106",
+]
+
+[[package]]
+name = "openssl-sys"
+version = "0.9.109"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "90096e2e47630d78b7d1c20952dc621f957103f8bc2c8359ec81290d75238571"
+dependencies = [
+ "cc",
+ "libc",
+ "pkg-config",
+ "vcpkg",
+]
+
+[[package]]
+name = "parking_lot"
+version = "0.12.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13"
+dependencies = [
+ "lock_api",
+ "parking_lot_core",
+]
+
+[[package]]
+name = "parking_lot_core"
+version = "0.9.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "redox_syscall",
+ "smallvec",
+ "windows-targets 0.52.6",
+]
+
+[[package]]
+name = "paste"
+version = "1.0.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
+
+[[package]]
+name = "pbkdf2"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917"
+dependencies = [
+ "digest 0.10.7",
+]
+
+[[package]]
+name = "percent-encoding"
+version = "2.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
+
+[[package]]
+name = "pkg-config"
+version = "0.3.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
+
+[[package]]
+name = "polyval"
+version = "0.6.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25"
+dependencies = [
+ "cfg-if",
+ "cpufeatures",
+ "opaque-debug",
+ "universal-hash",
+]
+
+[[package]]
+name = "ppv-lite86"
+version = "0.2.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
+dependencies = [
+ "zerocopy",
+]
+
+[[package]]
+name = "proc-macro-crate"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1d6ea3c4595b96363c13943497db34af4460fb474a95c43f4446ad341b8c9785"
+dependencies = [
+ "toml",
+]
+
+[[package]]
+name = "proc-macro-crate"
+version = "3.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "edce586971a4dfaa28950c6f18ed55e0406c1ab88bbce2c6f6293a7aaba73d35"
+dependencies = [
+ "toml_edit",
+]
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.101"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "qstring"
+version = "0.7.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d464fae65fff2680baf48019211ce37aaec0c78e9264c84a3e484717f965104e"
+dependencies = [
+ "percent-encoding",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.40"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "r-efi"
+version = "5.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
+
+[[package]]
+name = "rand"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03"
+dependencies = [
+ "getrandom 0.1.16",
+ "libc",
+ "rand_chacha 0.2.2",
+ "rand_core 0.5.1",
+ "rand_hc",
+]
+
+[[package]]
+name = "rand"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
+dependencies = [
+ "libc",
+ "rand_chacha 0.3.1",
+ "rand_core 0.6.4",
+]
+
+[[package]]
+name = "rand"
+version = "0.9.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1"
+dependencies = [
+ "rand_chacha 0.9.0",
+ "rand_core 0.9.3",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402"
+dependencies = [
+ "ppv-lite86",
+ "rand_core 0.5.1",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
+dependencies = [
+ "ppv-lite86",
+ "rand_core 0.6.4",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
+dependencies = [
+ "ppv-lite86",
+ "rand_core 0.9.3",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19"
+dependencies = [
+ "getrandom 0.1.16",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
+dependencies = [
+ "getrandom 0.2.16",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.9.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38"
+dependencies = [
+ "getrandom 0.3.3",
+]
+
+[[package]]
+name = "rand_hc"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c"
+dependencies = [
+ "rand_core 0.5.1",
+]
+
+[[package]]
+name = "redox_syscall"
+version = "0.5.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77"
+dependencies = [
+ "bitflags",
+]
+
+[[package]]
+name = "regex"
+version = "1.11.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "23d7fd106d8c02486a8d64e778353d1cffe08ce79ac2e82f540c86d0facf6912"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-automata",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-automata"
+version = "0.4.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6b9458fa0bfeeac22b5ca447c63aaf45f28439a709ccd244698632f9aa6394d6"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-syntax"
+version = "0.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001"
+
+[[package]]
+name = "rustc_version"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
+dependencies = [
+ "semver",
+]
+
+[[package]]
+name = "rustversion"
+version = "1.0.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
+
+[[package]]
+name = "ryu"
+version = "1.0.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
+
+[[package]]
+name = "scopeguard"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
+
+[[package]]
+name = "semver"
+version = "1.0.26"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0"
+
+[[package]]
+name = "serde"
+version = "1.0.219"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde-big-array"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "11fc7cc2c76d73e0f27ee52abbd64eec84d46f370c88371120433196934e4b7f"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "serde_bytes"
+version = "0.11.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8437fd221bde2d4ca316d61b90e337e9e702b3820b87d63caa9ba6c02bd06d96"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.219"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.106",
+]
+
+[[package]]
+name = "serde_json"
+version = "1.0.143"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d401abef1d108fbd9cbaebc3e46611f4b1021f714a0597a71f41ee463f5f4a5a"
+dependencies = [
+ "itoa",
+ "memchr",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "serde_with"
+version = "3.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f2c45cd61fefa9db6f254525d46e392b852e0e61d9a1fd36e5bd183450a556d5"
+dependencies = [
+ "serde",
+ "serde_derive",
+ "serde_with_macros",
+]
+
+[[package]]
+name = "serde_with_macros"
+version = "3.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "de90945e6565ce0d9a25098082ed4ee4002e047cb59892c318d66821e14bb30f"
+dependencies = [
+ "darling",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.106",
+]
+
+[[package]]
+name = "sha2"
+version = "0.9.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800"
+dependencies = [
+ "block-buffer 0.9.0",
+ "cfg-if",
+ "cpufeatures",
+ "digest 0.9.0",
+ "opaque-debug",
+]
+
+[[package]]
+name = "sha2"
+version = "0.10.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
+dependencies = [
+ "cfg-if",
+ "cpufeatures",
+ "digest 0.10.7",
+]
+
+[[package]]
+name = "sha3"
+version = "0.10.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60"
+dependencies = [
+ "digest 0.10.7",
+ "keccak",
+]
+
+[[package]]
+name = "shlex"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
+
+[[package]]
+name = "signal-hook"
+version = "0.3.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2"
+dependencies = [
+ "libc",
+ "signal-hook-registry",
+]
+
+[[package]]
+name = "signal-hook-registry"
+version = "1.4.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b2a4719bff48cee6b39d12c020eeb490953ad2443b7055bd0b21fca26bd8c28b"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "signature"
+version = "1.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c"
+
+[[package]]
+name = "siphasher"
+version = "0.3.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d"
+
+[[package]]
+name = "smallvec"
+version = "1.15.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
+
+[[package]]
+name = "solana-account"
+version = "2.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0f949fe4edaeaea78c844023bfc1c898e0b1f5a100f8a8d2d0f85d0a7b090258"
+dependencies = [
+ "bincode",
+ "serde",
+ "serde_bytes",
+ "serde_derive",
+ "solana-account-info",
+ "solana-clock",
+ "solana-instruction",
+ "solana-pubkey",
+ "solana-sdk-ids",
+ "solana-sysvar",
+]
+
+[[package]]
+name = "solana-account-info"
+version = "2.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c8f5152a288ef1912300fc6efa6c2d1f9bb55d9398eb6c72326360b8063987da"
+dependencies = [
+ "bincode",
+ "serde",
+ "solana-program-error",
+ "solana-program-memory",
+ "solana-pubkey",
+]
+
+[[package]]
+name = "solana-address-lookup-table-interface"
+version = "2.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d1673f67efe870b64a65cb39e6194be5b26527691ce5922909939961a6e6b395"
+dependencies = [
+ "bincode",
+ "bytemuck",
+ "serde",
+ "serde_derive",
+ "solana-clock",
+ "solana-instruction",
+ "solana-pubkey",
+ "solana-sdk-ids",
+ "solana-slot-hashes",
+]
+
+[[package]]
+name = "solana-atomic-u64"
+version = "2.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d52e52720efe60465b052b9e7445a01c17550666beec855cce66f44766697bc2"
+dependencies = [
+ "parking_lot",
+]
+
+[[package]]
+name = "solana-big-mod-exp"
+version = "2.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "75db7f2bbac3e62cfd139065d15bcda9e2428883ba61fc8d27ccb251081e7567"
+dependencies = [
+ "num-bigint",
+ "num-traits",
+ "solana-define-syscall",
+]
+
+[[package]]
+name = "solana-bincode"
+version = "2.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "19a3787b8cf9c9fe3dd360800e8b70982b9e5a8af9e11c354b6665dd4a003adc"
+dependencies = [
+ "bincode",
+ "serde",
+ "solana-instruction",
+]
+
+[[package]]
+name = "solana-blake3-hasher"
+version = "2.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a1a0801e25a1b31a14494fc80882a036be0ffd290efc4c2d640bfcca120a4672"
+dependencies = [
+ "blake3",
+ "solana-define-syscall",
+ "solana-hash",
+ "solana-sanitize",
+]
+
+[[package]]
+name = "solana-bn254"
+version = "2.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4420f125118732833f36facf96a27e7b78314b2d642ba07fa9ffdacd8d79e243"
+dependencies = [
+ "ark-bn254",
+ "ark-ec",
+ "ark-ff",
+ "ark-serialize",
+ "bytemuck",
+ "solana-define-syscall",
+ "thiserror 2.0.16",
+]
+
+[[package]]
+name = "solana-borsh"
+version = "2.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "718333bcd0a1a7aed6655aa66bef8d7fb047944922b2d3a18f49cbc13e73d004"
+dependencies = [
+ "borsh 0.10.4",
+ "borsh 1.5.7",
+]
+
+[[package]]
+name = "solana-client-traits"
+version = "2.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "83f0071874e629f29e0eb3dab8a863e98502ac7aba55b7e0df1803fc5cac72a7"
+dependencies = [
+ "solana-account",
+ "solana-commitment-config",
+ "solana-epoch-info",
+ "solana-hash",
+ "solana-instruction",
+ "solana-keypair",
+ "solana-message",
+ "solana-pubkey",
+ "solana-signature",
+ "solana-signer",
+ "solana-system-interface",
+ "solana-transaction",
+ "solana-transaction-error",
+]
+
+[[package]]
+name = "solana-clock"
+version = "2.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1bb482ab70fced82ad3d7d3d87be33d466a3498eb8aa856434ff3c0dfc2e2e31"
+dependencies = [
+ "serde",
+ "serde_derive",
+ "solana-sdk-ids",
+ "solana-sdk-macro",
+ "solana-sysvar-id",
+]
+
+[[package]]
+name = "solana-cluster-type"
+version = "2.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7ace9fea2daa28354d107ea879cff107181d85cd4e0f78a2bedb10e1a428c97e"
+dependencies = [
+ "serde",
+ "serde_derive",
+ "solana-hash",
+]
+
+[[package]]
+name = "solana-commitment-config"
+version = "2.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac49c4dde3edfa832de1697e9bcdb7c3b3f7cb7a1981b7c62526c8bb6700fb73"
+dependencies = [
+ "serde",
+ "serde_derive",
+]
+
+[[package]]
+name = "solana-compute-budget-interface"
+version = "2.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8432d2c4c22d0499aa06d62e4f7e333f81777b3d7c96050ae9e5cb71a8c3aee4"
+dependencies = [
+ "borsh 1.5.7",
+ "serde",
+ "serde_derive",
+ "solana-instruction",
+ "solana-sdk-ids",
+]
+
+[[package]]
+name = "solana-cpi"
+version = "2.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8dc71126edddc2ba014622fc32d0f5e2e78ec6c5a1e0eb511b85618c09e9ea11"
+dependencies = [
+ "solana-account-info",
+ "solana-define-syscall",
+ "solana-instruction",
+ "solana-program-error",
+ "solana-pubkey",
+ "solana-stable-layout",
+]
+
+[[package]]
+name = "solana-curve25519"
+version = "2.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b162f50499b391b785d57b2f2c73e3b9754d88fd4894bef444960b00bda8dcca"
+dependencies = [
+ "bytemuck",
+ "bytemuck_derive",
+ "curve25519-dalek 4.1.3",
+ "solana-define-syscall",
+ "subtle",
+ "thiserror 2.0.16",
+]
+
+[[package]]
+name = "solana-decode-error"
+version = "2.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8c781686a18db2f942e70913f7ca15dc120ec38dcab42ff7557db2c70c625a35"
+dependencies = [
+ "num-traits",
+]
+
+[[package]]
+name = "solana-define-syscall"
+version = "2.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2ae3e2abcf541c8122eafe9a625d4d194b4023c20adde1e251f94e056bb1aee2"
+
+[[package]]
+name = "solana-derivation-path"
+version = "2.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "939756d798b25c5ec3cca10e06212bdca3b1443cb9bb740a38124f58b258737b"
+dependencies = [
+ "derivation-path",
+ "qstring",
+ "uriparse",
+]
+
+[[package]]
+name = "solana-ed25519-program"
+version = "2.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a1feafa1691ea3ae588f99056f4bdd1293212c7ece28243d7da257c443e84753"
+dependencies = [
+ "bytemuck",
+ "bytemuck_derive",
+ "ed25519-dalek",
+ "solana-feature-set",
+ "solana-instruction",
+ "solana-precompile-error",
+ "solana-sdk-ids",
+]
+
+[[package]]
+name = "solana-epoch-info"
+version = "2.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "90ef6f0b449290b0b9f32973eefd95af35b01c5c0c34c569f936c34c5b20d77b"
+dependencies = [
+ "serde",
+ "serde_derive",
+]
+
+[[package]]
+name = "solana-epoch-rewards"
+version = "2.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "86b575d3dd323b9ea10bb6fe89bf6bf93e249b215ba8ed7f68f1a3633f384db7"
+dependencies = [
+ "serde",
+ "serde_derive",
+ "solana-hash",
+ "solana-sdk-ids",
+ "solana-sdk-macro",
+ "solana-sysvar-id",
+]
+
+[[package]]
+name = "solana-epoch-rewards-hasher"
+version = "2.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "96c5fd2662ae7574810904585fd443545ed2b568dbd304b25a31e79ccc76e81b"
+dependencies = [
+ "siphasher",
+ "solana-hash",
+ "solana-pubkey",
+]
+
+[[package]]
+name = "solana-epoch-schedule"
+version = "2.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3fce071fbddecc55d727b1d7ed16a629afe4f6e4c217bc8d00af3b785f6f67ed"
+dependencies = [
+ "serde",
+ "serde_derive",
+ "solana-sdk-ids",
+ "solana-sdk-macro",
+ "solana-sysvar-id",
+]
+
+[[package]]
+name = "solana-example-mocks"
+version = "2.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "84461d56cbb8bb8d539347151e0525b53910102e4bced875d49d5139708e39d3"
+dependencies = [
+ "serde",
+ "serde_derive",
+ "solana-address-lookup-table-interface",
+ "solana-clock",
+ "solana-hash",
+ "solana-instruction",
+ "solana-keccak-hasher",
+ "solana-message",
+ "solana-nonce",
+ "solana-pubkey",
+ "solana-sdk-ids",
+ "solana-system-interface",
+ "thiserror 2.0.16",
+]
+
+[[package]]
+name = "solana-feature-gate-interface"
+version = "2.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "43f5c5382b449e8e4e3016fb05e418c53d57782d8b5c30aa372fc265654b956d"
+dependencies = [
+ "bincode",
+ "serde",
+ "serde_derive",
+ "solana-account",
+ "solana-account-info",
+ "solana-instruction",
+ "solana-program-error",
+ "solana-pubkey",
+ "solana-rent",
+ "solana-sdk-ids",
+ "solana-system-interface",
+]
+
+[[package]]
+name = "solana-feature-set"
+version = "2.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93b93971e289d6425f88e6e3cb6668c4b05df78b3c518c249be55ced8efd6b6d"
+dependencies = [
+ "ahash",
+ "lazy_static",
+ "solana-epoch-schedule",
+ "solana-hash",
+ "solana-pubkey",
+ "solana-sha256-hasher",
+]
+
+[[package]]
+name = "solana-fee-calculator"
+version = "2.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d89bc408da0fb3812bc3008189d148b4d3e08252c79ad810b245482a3f70cd8d"
+dependencies = [
+ "log",
+ "serde",
+ "serde_derive",
+]
+
+[[package]]
+name = "solana-fee-structure"
+version = "2.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "33adf673581c38e810bf618f745bf31b683a0a4a4377682e6aaac5d9a058dd4e"
+dependencies = [
+ "serde",
+ "serde_derive",
+ "solana-message",
+ "solana-native-token",
+]
+
+[[package]]
+name = "solana-genesis-config"
+version = "2.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b3725085d47b96d37fef07a29d78d2787fc89a0b9004c66eed7753d1e554989f"
+dependencies = [
+ "bincode",
+ "chrono",
+ "memmap2",
+ "serde",
+ "serde_derive",
+ "solana-account",
+ "solana-clock",
+ "solana-cluster-type",
+ "solana-epoch-schedule",
+ "solana-fee-calculator",
+ "solana-hash",
+ "solana-inflation",
+ "solana-keypair",
+ "solana-logger",
+ "solana-poh-config",
+ "solana-pubkey",
+ "solana-rent",
+ "solana-sdk-ids",
+ "solana-sha256-hasher",
+ "solana-shred-version",
+ "solana-signer",
+ "solana-time-utils",
+]
+
+[[package]]
+name = "solana-hard-forks"
+version = "2.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6c28371f878e2ead55611d8ba1b5fb879847156d04edea13693700ad1a28baf"
+dependencies = [
+ "serde",
+ "serde_derive",
+]
+
+[[package]]
+name = "solana-hash"
+version = "2.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b5b96e9f0300fa287b545613f007dfe20043d7812bee255f418c1eb649c93b63"
+dependencies = [
+ "borsh 1.5.7",
+ "bytemuck",
+ "bytemuck_derive",
+ "five8",
+ "js-sys",
+ "serde",
+ "serde_derive",
+ "solana-atomic-u64",
+ "solana-sanitize",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "solana-inflation"
+version = "2.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "23eef6a09eb8e568ce6839573e4966850e85e9ce71e6ae1a6c930c1c43947de3"
+dependencies = [
+ "serde",
+ "serde_derive",
+]
+
+[[package]]
+name = "solana-instruction"
+version = "2.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "47298e2ce82876b64f71e9d13a46bc4b9056194e7f9937ad3084385befa50885"
+dependencies = [
+ "bincode",
+ "borsh 1.5.7",
+ "getrandom 0.2.16",
+ "js-sys",
+ "num-traits",
+ "serde",
+ "serde_derive",
+ "solana-define-syscall",
+ "solana-pubkey",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "solana-instructions-sysvar"
+version = "2.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e0e85a6fad5c2d0c4f5b91d34b8ca47118fc593af706e523cdbedf846a954f57"
+dependencies = [
+ "bitflags",
+ "solana-account-info",
+ "solana-instruction",
+ "solana-program-error",
+ "solana-pubkey",
+ "solana-sanitize",
+ "solana-sdk-ids",
+ "solana-serialize-utils",
+ "solana-sysvar-id",
+]
+
+[[package]]
+name = "solana-keccak-hasher"
+version = "2.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c7aeb957fbd42a451b99235df4942d96db7ef678e8d5061ef34c9b34cae12f79"
+dependencies = [
+ "sha3",
+ "solana-define-syscall",
+ "solana-hash",
+ "solana-sanitize",
+]
+
+[[package]]
+name = "solana-keypair"
+version = "2.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bd3f04aa1a05c535e93e121a95f66e7dcccf57e007282e8255535d24bf1e98bb"
+dependencies = [
+ "ed25519-dalek",
+ "ed25519-dalek-bip32",
+ "five8",
+ "rand 0.7.3",
+ "solana-derivation-path",
+ "solana-pubkey",
+ "solana-seed-derivable",
+ "solana-seed-phrase",
+ "solana-signature",
+ "solana-signer",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "solana-last-restart-slot"
+version = "2.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4a6360ac2fdc72e7463565cd256eedcf10d7ef0c28a1249d261ec168c1b55cdd"
+dependencies = [
+ "serde",
+ "serde_derive",
+ "solana-sdk-ids",
+ "solana-sdk-macro",
+ "solana-sysvar-id",
+]
+
+[[package]]
+name = "solana-loader-v2-interface"
+version = "2.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d8ab08006dad78ae7cd30df8eea0539e207d08d91eaefb3e1d49a446e1c49654"
+dependencies = [
+ "serde",
+ "serde_bytes",
+ "serde_derive",
+ "solana-instruction",
+ "solana-pubkey",
+ "solana-sdk-ids",
+]
+
+[[package]]
+name = "solana-loader-v3-interface"
+version = "5.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6f7162a05b8b0773156b443bccd674ea78bb9aa406325b467ea78c06c99a63a2"
+dependencies = [
+ "serde",
+ "serde_bytes",
+ "serde_derive",
+ "solana-instruction",
+ "solana-pubkey",
+ "solana-sdk-ids",
+ "solana-system-interface",
+]
+
+[[package]]
+name = "solana-loader-v4-interface"
+version = "2.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "706a777242f1f39a83e2a96a2a6cb034cb41169c6ecbee2cf09cb873d9659e7e"
+dependencies = [
+ "serde",
+ "serde_bytes",
+ "serde_derive",
+ "solana-instruction",
+ "solana-pubkey",
+ "solana-sdk-ids",
+ "solana-system-interface",
+]
+
+[[package]]
+name = "solana-logger"
+version = "2.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "db8e777ec1afd733939b532a42492d888ec7c88d8b4127a5d867eb45c6eb5cd5"
+dependencies = [
+ "env_logger",
+ "lazy_static",
+ "libc",
+ "log",
+ "signal-hook",
+]
+
+[[package]]
+name = "solana-message"
+version = "2.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1796aabce376ff74bf89b78d268fa5e683d7d7a96a0a4e4813ec34de49d5314b"
+dependencies = [
+ "bincode",
+ "blake3",
+ "lazy_static",
+ "serde",
+ "serde_derive",
+ "solana-bincode",
+ "solana-hash",
+ "solana-instruction",
+ "solana-pubkey",
+ "solana-sanitize",
+ "solana-sdk-ids",
+ "solana-short-vec",
+ "solana-system-interface",
+ "solana-transaction-error",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "solana-msg"
+version = "2.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f36a1a14399afaabc2781a1db09cb14ee4cc4ee5c7a5a3cfcc601811379a8092"
+dependencies = [
+ "solana-define-syscall",
+]
+
+[[package]]
+name = "solana-native-token"
+version = "2.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "61515b880c36974053dd499c0510066783f0cc6ac17def0c7ef2a244874cf4a9"
+
+[[package]]
+name = "solana-nonce"
+version = "2.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "703e22eb185537e06204a5bd9d509b948f0066f2d1d814a6f475dafb3ddf1325"
+dependencies = [
+ "serde",
+ "serde_derive",
+ "solana-fee-calculator",
+ "solana-hash",
+ "solana-pubkey",
+ "solana-sha256-hasher",
+]
+
+[[package]]
+name = "solana-nonce-account"
+version = "2.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cde971a20b8dbf60144d6a84439dda86b5466e00e2843091fe731083cda614da"
+dependencies = [
+ "solana-account",
+ "solana-hash",
+ "solana-nonce",
+ "solana-sdk-ids",
+]
+
+[[package]]
+name = "solana-offchain-message"
+version = "2.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b526398ade5dea37f1f147ce55dae49aa017a5d7326606359b0445ca8d946581"
+dependencies = [
+ "num_enum",
+ "solana-hash",
+ "solana-packet",
+ "solana-pubkey",
+ "solana-sanitize",
+ "solana-sha256-hasher",
+ "solana-signature",
+ "solana-signer",
+]
+
+[[package]]
+name = "solana-packet"
+version = "2.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "004f2d2daf407b3ec1a1ca5ec34b3ccdfd6866dd2d3c7d0715004a96e4b6d127"
+dependencies = [
+ "bincode",
+ "bitflags",
+ "cfg_eval",
+ "serde",
+ "serde_derive",
+ "serde_with",
+]
+
+[[package]]
+name = "solana-poh-config"
+version = "2.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d650c3b4b9060082ac6b0efbbb66865089c58405bfb45de449f3f2b91eccee75"
+dependencies = [
+ "serde",
+ "serde_derive",
+]
+
+[[package]]
+name = "solana-precompile-error"
+version = "2.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4d87b2c1f5de77dfe2b175ee8dd318d196aaca4d0f66f02842f80c852811f9f8"
+dependencies = [
+ "num-traits",
+ "solana-decode-error",
+]
+
+[[package]]
+name = "solana-precompiles"
+version = "2.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "36e92768a57c652edb0f5d1b30a7d0bc64192139c517967c18600debe9ae3832"
+dependencies = [
+ "lazy_static",
+ "solana-ed25519-program",
+ "solana-feature-set",
+ "solana-message",
+ "solana-precompile-error",
+ "solana-pubkey",
+ "solana-sdk-ids",
+ "solana-secp256k1-program",
+ "solana-secp256r1-program",
+]
+
+[[package]]
+name = "solana-presigner"
+version = "2.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "81a57a24e6a4125fc69510b6774cd93402b943191b6cddad05de7281491c90fe"
+dependencies = [
+ "solana-pubkey",
+ "solana-signature",
+ "solana-signer",
+]
+
+[[package]]
+name = "solana-program"
+version = "2.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "98eca145bd3545e2fbb07166e895370576e47a00a7d824e325390d33bf467210"
+dependencies = [
+ "bincode",
+ "blake3",
+ "borsh 0.10.4",
+ "borsh 1.5.7",
+ "bs58",
+ "bytemuck",
+ "console_error_panic_hook",
+ "console_log",
+ "getrandom 0.2.16",
+ "lazy_static",
+ "log",
+ "memoffset",
+ "num-bigint",
+ "num-derive",
+ "num-traits",
+ "rand 0.8.5",
+ "serde",
+ "serde_bytes",
+ "serde_derive",
+ "solana-account-info",
+ "solana-address-lookup-table-interface",
+ "solana-atomic-u64",
+ "solana-big-mod-exp",
+ "solana-bincode",
+ "solana-blake3-hasher",
+ "solana-borsh",
+ "solana-clock",
+ "solana-cpi",
+ "solana-decode-error",
+ "solana-define-syscall",
+ "solana-epoch-rewards",
+ "solana-epoch-schedule",
+ "solana-example-mocks",
+ "solana-feature-gate-interface",
+ "solana-fee-calculator",
+ "solana-hash",
+ "solana-instruction",
+ "solana-instructions-sysvar",
+ "solana-keccak-hasher",
+ "solana-last-restart-slot",
+ "solana-loader-v2-interface",
+ "solana-loader-v3-interface",
+ "solana-loader-v4-interface",
+ "solana-message",
+ "solana-msg",
+ "solana-native-token",
+ "solana-nonce",
+ "solana-program-entrypoint",
+ "solana-program-error",
+ "solana-program-memory",
+ "solana-program-option",
+ "solana-program-pack",
+ "solana-pubkey",
+ "solana-rent",
+ "solana-sanitize",
+ "solana-sdk-ids",
+ "solana-sdk-macro",
+ "solana-secp256k1-recover",
+ "solana-serde-varint",
+ "solana-serialize-utils",
+ "solana-sha256-hasher",
+ "solana-short-vec",
+ "solana-slot-hashes",
+ "solana-slot-history",
+ "solana-stable-layout",
+ "solana-stake-interface",
+ "solana-system-interface",
+ "solana-sysvar",
+ "solana-sysvar-id",
+ "solana-vote-interface",
+ "thiserror 2.0.16",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "solana-program-entrypoint"
+version = "2.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32ce041b1a0ed275290a5008ee1a4a6c48f5054c8a3d78d313c08958a06aedbd"
+dependencies = [
+ "solana-account-info",
+ "solana-msg",
+ "solana-program-error",
+ "solana-pubkey",
+]
+
+[[package]]
+name = "solana-program-error"
+version = "2.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9ee2e0217d642e2ea4bee237f37bd61bb02aec60da3647c48ff88f6556ade775"
+dependencies = [
+ "borsh 1.5.7",
+ "num-traits",
+ "serde",
+ "serde_derive",
+ "solana-decode-error",
+ "solana-instruction",
+ "solana-msg",
+ "solana-pubkey",
+]
+
+[[package]]
+name = "solana-program-memory"
+version = "2.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3a5426090c6f3fd6cfdc10685322fede9ca8e5af43cd6a59e98bfe4e91671712"
+dependencies = [
+ "solana-define-syscall",
+]
+
+[[package]]
+name = "solana-program-option"
+version = "2.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc677a2e9bc616eda6dbdab834d463372b92848b2bfe4a1ed4e4b4adba3397d0"
+
+[[package]]
+name = "solana-program-pack"
+version = "2.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "319f0ef15e6e12dc37c597faccb7d62525a509fec5f6975ecb9419efddeb277b"
+dependencies = [
+ "solana-program-error",
+]
+
+[[package]]
+name = "solana-pubkey"
+version = "2.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b62adb9c3261a052ca1f999398c388f1daf558a1b492f60a6d9e64857db4ff1"
+dependencies = [
+ "borsh 0.10.4",
+ "borsh 1.5.7",
+ "bytemuck",
+ "bytemuck_derive",
+ "curve25519-dalek 4.1.3",
+ "five8",
+ "five8_const",
+ "getrandom 0.2.16",
+ "js-sys",
+ "num-traits",
+ "rand 0.8.5",
+ "serde",
+ "serde_derive",
+ "solana-atomic-u64",
+ "solana-decode-error",
+ "solana-define-syscall",
+ "solana-sanitize",
+ "solana-sha256-hasher",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "solana-quic-definitions"
+version = "2.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fbf0d4d5b049eb1d0c35f7b18f305a27c8986fc5c0c9b383e97adaa35334379e"
+dependencies = [
+ "solana-keypair",
+]
+
+[[package]]
+name = "solana-rent"
+version = "2.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d1aea8fdea9de98ca6e8c2da5827707fb3842833521b528a713810ca685d2480"
+dependencies = [
+ "serde",
+ "serde_derive",
+ "solana-sdk-ids",
+ "solana-sdk-macro",
+ "solana-sysvar-id",
+]
+
+[[package]]
+name = "solana-rent-collector"
+version = "2.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "127e6dfa51e8c8ae3aa646d8b2672bc4ac901972a338a9e1cd249e030564fb9d"
+dependencies = [
+ "serde",
+ "serde_derive",
+ "solana-account",
+ "solana-clock",
+ "solana-epoch-schedule",
+ "solana-genesis-config",
+ "solana-pubkey",
+ "solana-rent",
+ "solana-sdk-ids",
+]
+
+[[package]]
+name = "solana-rent-debits"
+version = "2.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4f6f9113c6003492e74438d1288e30cffa8ccfdc2ef7b49b9e816d8034da18cd"
+dependencies = [
+ "solana-pubkey",
+ "solana-reward-info",
+]
+
+[[package]]
+name = "solana-reserved-account-keys"
+version = "2.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e4b22ea19ca2a3f28af7cd047c914abf833486bf7a7c4a10fc652fff09b385b1"
+dependencies = [
+ "lazy_static",
+ "solana-feature-set",
+ "solana-pubkey",
+ "solana-sdk-ids",
+]
+
+[[package]]
+name = "solana-reward-info"
+version = "2.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "18205b69139b1ae0ab8f6e11cdcb627328c0814422ad2482000fa2ca54ae4a2f"
+dependencies = [
+ "serde",
+ "serde_derive",
+]
+
+[[package]]
+name = "solana-sanitize"
+version = "2.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "61f1bc1357b8188d9c4a3af3fc55276e56987265eb7ad073ae6f8180ee54cecf"
+
+[[package]]
+name = "solana-sdk"
+version = "2.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8cc0e4a7635b902791c44b6581bfb82f3ada32c5bc0929a64f39fe4bb384c86a"
+dependencies = [
+ "bincode",
+ "bs58",
+ "getrandom 0.1.16",
+ "js-sys",
+ "serde",
+ "serde_json",
+ "solana-account",
+ "solana-bn254",
+ "solana-client-traits",
+ "solana-cluster-type",
+ "solana-commitment-config",
+ "solana-compute-budget-interface",
+ "solana-decode-error",
+ "solana-derivation-path",
+ "solana-ed25519-program",
+ "solana-epoch-info",
+ "solana-epoch-rewards-hasher",
+ "solana-feature-set",
+ "solana-fee-structure",
+ "solana-genesis-config",
+ "solana-hard-forks",
+ "solana-inflation",
+ "solana-instruction",
+ "solana-keypair",
+ "solana-message",
+ "solana-native-token",
+ "solana-nonce-account",
+ "solana-offchain-message",
+ "solana-packet",
+ "solana-poh-config",
+ "solana-precompile-error",
+ "solana-precompiles",
+ "solana-presigner",
+ "solana-program",
+ "solana-program-memory",
+ "solana-pubkey",
+ "solana-quic-definitions",
+ "solana-rent-collector",
+ "solana-rent-debits",
+ "solana-reserved-account-keys",
+ "solana-reward-info",
+ "solana-sanitize",
+ "solana-sdk-ids",
+ "solana-sdk-macro",
+ "solana-secp256k1-program",
+ "solana-secp256k1-recover",
+ "solana-secp256r1-program",
+ "solana-seed-derivable",
+ "solana-seed-phrase",
+ "solana-serde",
+ "solana-serde-varint",
+ "solana-short-vec",
+ "solana-shred-version",
+ "solana-signature",
+ "solana-signer",
+ "solana-system-transaction",
+ "solana-time-utils",
+ "solana-transaction",
+ "solana-transaction-context",
+ "solana-transaction-error",
+ "solana-validator-exit",
+ "thiserror 2.0.16",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "solana-sdk-ids"
+version = "2.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c5d8b9cc68d5c88b062a33e23a6466722467dde0035152d8fb1afbcdf350a5f"
+dependencies = [
+ "solana-pubkey",
+]
+
+[[package]]
+name = "solana-sdk-macro"
+version = "2.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "86280da8b99d03560f6ab5aca9de2e38805681df34e0bb8f238e69b29433b9df"
+dependencies = [
+ "bs58",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.106",
+]
+
+[[package]]
+name = "solana-secp256k1-program"
+version = "2.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f19833e4bc21558fe9ec61f239553abe7d05224347b57d65c2218aeeb82d6149"
+dependencies = [
+ "bincode",
+ "digest 0.10.7",
+ "libsecp256k1",
+ "serde",
+ "serde_derive",
+ "sha3",
+ "solana-feature-set",
+ "solana-instruction",
+ "solana-precompile-error",
+ "solana-sdk-ids",
+ "solana-signature",
+]
+
+[[package]]
+name = "solana-secp256k1-recover"
+version = "2.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baa3120b6cdaa270f39444f5093a90a7b03d296d362878f7a6991d6de3bbe496"
+dependencies = [
+ "borsh 1.5.7",
+ "libsecp256k1",
+ "solana-define-syscall",
+ "thiserror 2.0.16",
+]
+
+[[package]]
+name = "solana-secp256r1-program"
+version = "2.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ce0ae46da3071a900f02d367d99b2f3058fe2e90c5062ac50c4f20cfedad8f0f"
+dependencies = [
+ "bytemuck",
+ "openssl",
+ "solana-feature-set",
+ "solana-instruction",
+ "solana-precompile-error",
+ "solana-sdk-ids",
+]
+
+[[package]]
+name = "solana-security-txt"
+version = "1.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "468aa43b7edb1f9b7b7b686d5c3aeb6630dc1708e86e31343499dd5c4d775183"
+
+[[package]]
+name = "solana-seed-derivable"
+version = "2.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3beb82b5adb266c6ea90e5cf3967235644848eac476c5a1f2f9283a143b7c97f"
+dependencies = [
+ "solana-derivation-path",
+]
+
+[[package]]
+name = "solana-seed-phrase"
+version = "2.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "36187af2324f079f65a675ec22b31c24919cb4ac22c79472e85d819db9bbbc15"
+dependencies = [
+ "hmac 0.12.1",
+ "pbkdf2",
+ "sha2 0.10.9",
+]
+
+[[package]]
+name = "solana-serde"
+version = "2.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1931484a408af466e14171556a47adaa215953c7f48b24e5f6b0282763818b04"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "solana-serde-varint"
+version = "2.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2a7e155eba458ecfb0107b98236088c3764a09ddf0201ec29e52a0be40857113"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "solana-serialize-utils"
+version = "2.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "817a284b63197d2b27afdba829c5ab34231da4a9b4e763466a003c40ca4f535e"
+dependencies = [
+ "solana-instruction",
+ "solana-pubkey",
+ "solana-sanitize",
+]
+
+[[package]]
+name = "solana-sha256-hasher"
+version = "2.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5aa3feb32c28765f6aa1ce8f3feac30936f16c5c3f7eb73d63a5b8f6f8ecdc44"
+dependencies = [
+ "sha2 0.10.9",
+ "solana-define-syscall",
+ "solana-hash",
+]
+
+[[package]]
+name = "solana-short-vec"
+version = "2.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c54c66f19b9766a56fa0057d060de8378676cb64987533fa088861858fc5a69"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "solana-shred-version"
+version = "2.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "afd3db0461089d1ad1a78d9ba3f15b563899ca2386351d38428faa5350c60a98"
+dependencies = [
+ "solana-hard-forks",
+ "solana-hash",
+ "solana-sha256-hasher",
+]
+
+[[package]]
+name = "solana-signature"
+version = "2.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "64c8ec8e657aecfc187522fc67495142c12f35e55ddeca8698edbb738b8dbd8c"
+dependencies = [
+ "ed25519-dalek",
+ "five8",
+ "rand 0.8.5",
+ "serde",
+ "serde-big-array",
+ "serde_derive",
+ "solana-sanitize",
+]
+
+[[package]]
+name = "solana-signer"
+version = "2.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7c41991508a4b02f021c1342ba00bcfa098630b213726ceadc7cb032e051975b"
+dependencies = [
+ "solana-pubkey",
+ "solana-signature",
+ "solana-transaction-error",
+]
+
+[[package]]
+name = "solana-slot-hashes"
+version = "2.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0c8691982114513763e88d04094c9caa0376b867a29577939011331134c301ce"
+dependencies = [
+ "serde",
+ "serde_derive",
+ "solana-hash",
+ "solana-sdk-ids",
+ "solana-sysvar-id",
+]
+
+[[package]]
+name = "solana-slot-history"
+version = "2.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "97ccc1b2067ca22754d5283afb2b0126d61eae734fc616d23871b0943b0d935e"
+dependencies = [
+ "bv",
+ "serde",
+ "serde_derive",
+ "solana-sdk-ids",
+ "solana-sysvar-id",
+]
+
+[[package]]
+name = "solana-stable-layout"
+version = "2.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9f14f7d02af8f2bc1b5efeeae71bc1c2b7f0f65cd75bcc7d8180f2c762a57f54"
+dependencies = [
+ "solana-instruction",
+ "solana-pubkey",
+]
+
+[[package]]
+name = "solana-stake-interface"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5269e89fde216b4d7e1d1739cf5303f8398a1ff372a81232abbee80e554a838c"
+dependencies = [
+ "borsh 0.10.4",
+ "borsh 1.5.7",
+ "num-traits",
+ "serde",
+ "serde_derive",
+ "solana-clock",
+ "solana-cpi",
+ "solana-decode-error",
+ "solana-instruction",
+ "solana-program-error",
+ "solana-pubkey",
+ "solana-system-interface",
+ "solana-sysvar-id",
+]
+
+[[package]]
+name = "solana-system-interface"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "94d7c18cb1a91c6be5f5a8ac9276a1d7c737e39a21beba9ea710ab4b9c63bc90"
+dependencies = [
+ "js-sys",
+ "num-traits",
+ "serde",
+ "serde_derive",
+ "solana-decode-error",
+ "solana-instruction",
+ "solana-pubkey",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "solana-system-transaction"
+version = "2.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5bd98a25e5bcba8b6be8bcbb7b84b24c2a6a8178d7fb0e3077a916855ceba91a"
+dependencies = [
+ "solana-hash",
+ "solana-keypair",
+ "solana-message",
+ "solana-pubkey",
+ "solana-signer",
+ "solana-system-interface",
+ "solana-transaction",
+]
+
+[[package]]
+name = "solana-sysvar"
+version = "2.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b8c3595f95069f3d90f275bb9bd235a1973c4d059028b0a7f81baca2703815db"
+dependencies = [
+ "base64 0.22.1",
+ "bincode",
+ "bytemuck",
+ "bytemuck_derive",
+ "lazy_static",
+ "serde",
+ "serde_derive",
+ "solana-account-info",
+ "solana-clock",
+ "solana-define-syscall",
+ "solana-epoch-rewards",
+ "solana-epoch-schedule",
+ "solana-fee-calculator",
+ "solana-hash",
+ "solana-instruction",
+ "solana-instructions-sysvar",
+ "solana-last-restart-slot",
+ "solana-program-entrypoint",
+ "solana-program-error",
+ "solana-program-memory",
+ "solana-pubkey",
+ "solana-rent",
+ "solana-sanitize",
+ "solana-sdk-ids",
+ "solana-sdk-macro",
+ "solana-slot-hashes",
+ "solana-slot-history",
+ "solana-stake-interface",
+ "solana-sysvar-id",
+]
+
+[[package]]
+name = "solana-sysvar-id"
+version = "2.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5762b273d3325b047cfda250787f8d796d781746860d5d0a746ee29f3e8812c1"
+dependencies = [
+ "solana-pubkey",
+ "solana-sdk-ids",
+]
+
+[[package]]
+name = "solana-time-utils"
+version = "2.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6af261afb0e8c39252a04d026e3ea9c405342b08c871a2ad8aa5448e068c784c"
+
+[[package]]
+name = "solana-transaction"
+version = "2.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "80657d6088f721148f5d889c828ca60c7daeedac9a8679f9ec215e0c42bcbf41"
+dependencies = [
+ "bincode",
+ "serde",
+ "serde_derive",
+ "solana-bincode",
+ "solana-feature-set",
+ "solana-hash",
+ "solana-instruction",
+ "solana-keypair",
+ "solana-message",
+ "solana-precompiles",
+ "solana-pubkey",
+ "solana-sanitize",
+ "solana-sdk-ids",
+ "solana-short-vec",
+ "solana-signature",
+ "solana-signer",
+ "solana-system-interface",
+ "solana-transaction-error",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "solana-transaction-context"
+version = "2.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aefd75e49dd990f7fdbe562a539a7b046a839aadf43843845d766a2a6a2adfef"
+dependencies = [
+ "bincode",
+ "serde",
+ "serde_derive",
+ "solana-account",
+ "solana-instruction",
+ "solana-instructions-sysvar",
+ "solana-pubkey",
+ "solana-rent",
+ "solana-sdk-ids",
+]
+
+[[package]]
+name = "solana-transaction-error"
+version = "2.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "222a9dc8fdb61c6088baab34fc3a8b8473a03a7a5fd404ed8dd502fa79b67cb1"
+dependencies = [
+ "serde",
+ "serde_derive",
+ "solana-instruction",
+ "solana-sanitize",
+]
+
+[[package]]
+name = "solana-validator-exit"
+version = "2.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7bbf6d7a3c0b28dd5335c52c0e9eae49d0ae489a8f324917faf0ded65a812c1d"
+
+[[package]]
+name = "solana-vote-interface"
+version = "2.2.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b80d57478d6599d30acc31cc5ae7f93ec2361a06aefe8ea79bc81739a08af4c3"
+dependencies = [
+ "bincode",
+ "num-derive",
+ "num-traits",
+ "serde",
+ "serde_derive",
+ "solana-clock",
+ "solana-decode-error",
+ "solana-hash",
+ "solana-instruction",
+ "solana-pubkey",
+ "solana-rent",
+ "solana-sdk-ids",
+ "solana-serde-varint",
+ "solana-serialize-utils",
+ "solana-short-vec",
+ "solana-system-interface",
+]
+
+[[package]]
+name = "solana-zk-sdk"
+version = "2.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3bb171c0f76c420a7cb6aabbe5fa85a1a009d5bb4009189c43e1a03aff9446d7"
+dependencies = [
+ "aes-gcm-siv",
+ "base64 0.22.1",
+ "bincode",
+ "bytemuck",
+ "bytemuck_derive",
+ "curve25519-dalek 4.1.3",
+ "itertools 0.12.1",
+ "js-sys",
+ "merlin",
+ "num-derive",
+ "num-traits",
+ "rand 0.8.5",
+ "serde",
+ "serde_derive",
+ "serde_json",
+ "sha3",
+ "solana-derivation-path",
+ "solana-instruction",
+ "solana-pubkey",
+ "solana-sdk-ids",
+ "solana-seed-derivable",
+ "solana-seed-phrase",
+ "solana-signature",
+ "solana-signer",
+ "subtle",
+ "thiserror 2.0.16",
+ "wasm-bindgen",
+ "zeroize",
+]
+
+[[package]]
+name = "spl-discriminator"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a7398da23554a31660f17718164e31d31900956054f54f52d5ec1be51cb4f4b3"
+dependencies = [
+ "bytemuck",
+ "solana-program-error",
+ "solana-sha256-hasher",
+ "spl-discriminator-derive",
+]
+
+[[package]]
+name = "spl-discriminator-derive"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d9e8418ea6269dcfb01c712f0444d2c75542c04448b480e87de59d2865edc750"
+dependencies = [
+ "quote",
+ "spl-discriminator-syn",
+ "syn 2.0.106",
+]
+
+[[package]]
+name = "spl-discriminator-syn"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5d1dbc82ab91422345b6df40a79e2b78c7bce1ebb366da323572dd60b7076b67"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "sha2 0.10.9",
+ "syn 2.0.106",
+ "thiserror 1.0.69",
+]
+
+[[package]]
+name = "spl-elgamal-registry"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "56cc66fe64651a48c8deb4793d8a5deec8f8faf19f355b9df294387bc5a36b5f"
+dependencies = [
+ "bytemuck",
+ "solana-account-info",
+ "solana-cpi",
+ "solana-instruction",
+ "solana-msg",
+ "solana-program-entrypoint",
+ "solana-program-error",
+ "solana-pubkey",
+ "solana-rent",
+ "solana-sdk-ids",
+ "solana-security-txt",
+ "solana-system-interface",
+ "solana-sysvar",
+ "solana-zk-sdk",
+ "spl-pod",
+ "spl-token-confidential-transfer-proof-extraction",
+]
+
+[[package]]
+name = "spl-memo"
+version = "6.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9f09647c0974e33366efeb83b8e2daebb329f0420149e74d3a4bd2c08cf9f7cb"
+dependencies = [
+ "solana-account-info",
+ "solana-instruction",
+ "solana-msg",
+ "solana-program-entrypoint",
+ "solana-program-error",
+ "solana-pubkey",
+]
+
+[[package]]
+name = "spl-pod"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d994afaf86b779104b4a95ba9ca75b8ced3fdb17ee934e38cb69e72afbe17799"
+dependencies = [
+ "borsh 1.5.7",
+ "bytemuck",
+ "bytemuck_derive",
+ "num-derive",
+ "num-traits",
+ "solana-decode-error",
+ "solana-msg",
+ "solana-program-error",
+ "solana-program-option",
+ "solana-pubkey",
+ "solana-zk-sdk",
+ "thiserror 2.0.16",
+]
+
+[[package]]
+name = "spl-program-error"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9cdebc8b42553070b75aa5106f071fef2eb798c64a7ec63375da4b1f058688c6"
+dependencies = [
+ "num-derive",
+ "num-traits",
+ "solana-decode-error",
+ "solana-msg",
+ "solana-program-error",
+ "spl-program-error-derive",
+ "thiserror 2.0.16",
+]
+
+[[package]]
+name = "spl-program-error-derive"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2a2539e259c66910d78593475540e8072f0b10f0f61d7607bbf7593899ed52d0"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "sha2 0.10.9",
+ "syn 2.0.106",
+]
+
+[[package]]
+name = "spl-tlv-account-resolution"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1408e961215688715d5a1063cbdcf982de225c45f99c82b4f7d7e1dd22b998d7"
+dependencies = [
+ "bytemuck",
+ "num-derive",
+ "num-traits",
+ "solana-account-info",
+ "solana-decode-error",
+ "solana-instruction",
+ "solana-msg",
+ "solana-program-error",
+ "solana-pubkey",
+ "spl-discriminator",
+ "spl-pod",
+ "spl-program-error",
+ "spl-type-length-value",
+ "thiserror 2.0.16",
+]
+
+[[package]]
+name = "spl-token"
+version = "8.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "053067c6a82c705004f91dae058b11b4780407e9ccd6799dc9e7d0fab5f242da"
+dependencies = [
+ "arrayref",
+ "bytemuck",
+ "num-derive",
+ "num-traits",
+ "num_enum",
+ "solana-account-info",
+ "solana-cpi",
+ "solana-decode-error",
+ "solana-instruction",
+ "solana-msg",
+ "solana-program-entrypoint",
+ "solana-program-error",
+ "solana-program-memory",
+ "solana-program-option",
+ "solana-program-pack",
+ "solana-pubkey",
+ "solana-rent",
+ "solana-sdk-ids",
+ "solana-sysvar",
+ "thiserror 2.0.16",
+]
+
+[[package]]
+name = "spl-token-2022"
+version = "9.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "707d8237d17d857246b189d0fb278797dcd7cf6219374547791b231fd35a8cc8"
+dependencies = [
+ "arrayref",
+ "bytemuck",
+ "num-derive",
+ "num-traits",
+ "num_enum",
+ "solana-account-info",
+ "solana-clock",
+ "solana-cpi",
+ "solana-decode-error",
+ "solana-instruction",
+ "solana-msg",
+ "solana-native-token",
+ "solana-program-entrypoint",
+ "solana-program-error",
+ "solana-program-memory",
+ "solana-program-option",
+ "solana-program-pack",
+ "solana-pubkey",
+ "solana-rent",
+ "solana-sdk-ids",
+ "solana-security-txt",
+ "solana-system-interface",
+ "solana-sysvar",
+ "solana-zk-sdk",
+ "spl-elgamal-registry",
+ "spl-memo",
+ "spl-pod",
+ "spl-token",
+ "spl-token-confidential-transfer-ciphertext-arithmetic",
+ "spl-token-confidential-transfer-proof-extraction",
+ "spl-token-confidential-transfer-proof-generation",
+ "spl-token-group-interface",
+ "spl-token-metadata-interface",
+ "spl-transfer-hook-interface",
+ "spl-type-length-value",
+ "thiserror 2.0.16",
+]
+
+[[package]]
+name = "spl-token-confidential-transfer-ciphertext-arithmetic"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cddd52bfc0f1c677b41493dafa3f2dbbb4b47cf0990f08905429e19dc8289b35"
+dependencies = [
+ "base64 0.22.1",
+ "bytemuck",
+ "solana-curve25519",
+ "solana-zk-sdk",
+]
+
+[[package]]
+name = "spl-token-confidential-transfer-proof-extraction"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "512c85bdbbb4cbcc2038849a9e164c958b16541f252b53ea1a3933191c0a4a1a"
+dependencies = [
+ "bytemuck",
+ "solana-account-info",
+ "solana-curve25519",
+ "solana-instruction",
+ "solana-instructions-sysvar",
+ "solana-msg",
+ "solana-program-error",
+ "solana-pubkey",
+ "solana-sdk-ids",
+ "solana-zk-sdk",
+ "spl-pod",
+ "thiserror 2.0.16",
+]
+
+[[package]]
+name = "spl-token-confidential-transfer-proof-generation"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fa27b9174bea869a7ebf31e0be6890bce90b1a4288bc2bbf24bd413f80ae3fde"
+dependencies = [
+ "curve25519-dalek 4.1.3",
+ "solana-zk-sdk",
+ "thiserror 2.0.16",
+]
+
+[[package]]
+name = "spl-token-group-interface"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5597b4cd76f85ce7cd206045b7dc22da8c25516573d42d267c8d1fd128db5129"
+dependencies = [
+ "bytemuck",
+ "num-derive",
+ "num-traits",
+ "solana-decode-error",
+ "solana-instruction",
+ "solana-msg",
+ "solana-program-error",
+ "solana-pubkey",
+ "spl-discriminator",
+ "spl-pod",
+ "thiserror 2.0.16",
+]
+
+[[package]]
+name = "spl-token-metadata-interface"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "304d6e06f0de0c13a621464b1fd5d4b1bebf60d15ca71a44d3839958e0da16ee"
+dependencies = [
+ "borsh 1.5.7",
+ "num-derive",
+ "num-traits",
+ "solana-borsh",
+ "solana-decode-error",
+ "solana-instruction",
+ "solana-msg",
+ "solana-program-error",
+ "solana-pubkey",
+ "spl-discriminator",
+ "spl-pod",
+ "spl-type-length-value",
+ "thiserror 2.0.16",
+]
+
+[[package]]
+name = "spl-transfer-hook-interface"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a7e905b849b6aba63bde8c4badac944ebb6c8e6e14817029cbe1bc16829133bd"
+dependencies = [
+ "arrayref",
+ "bytemuck",
+ "num-derive",
+ "num-traits",
+ "solana-account-info",
+ "solana-cpi",
+ "solana-decode-error",
+ "solana-instruction",
+ "solana-msg",
+ "solana-program-error",
+ "solana-pubkey",
+ "spl-discriminator",
+ "spl-pod",
+ "spl-program-error",
+ "spl-tlv-account-resolution",
+ "spl-type-length-value",
+ "thiserror 2.0.16",
+]
+
+[[package]]
+name = "spl-type-length-value"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d417eb548214fa822d93f84444024b4e57c13ed6719d4dcc68eec24fb481e9f5"
+dependencies = [
+ "bytemuck",
+ "num-derive",
+ "num-traits",
+ "solana-account-info",
+ "solana-decode-error",
+ "solana-msg",
+ "solana-program-error",
+ "spl-discriminator",
+ "spl-pod",
+ "thiserror 2.0.16",
+]
+
+[[package]]
+name = "strsim"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
+
+[[package]]
+name = "subtle"
+version = "2.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
+
+[[package]]
+name = "syn"
+version = "1.0.109"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "syn"
+version = "2.0.106"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "termcolor"
+version = "1.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755"
+dependencies = [
+ "winapi-util",
+]
+
+[[package]]
+name = "thiserror"
+version = "1.0.69"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
+dependencies = [
+ "thiserror-impl 1.0.69",
+]
+
+[[package]]
+name = "thiserror"
+version = "2.0.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3467d614147380f2e4e374161426ff399c91084acd2363eaf549172b3d5e60c0"
+dependencies = [
+ "thiserror-impl 2.0.16",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "1.0.69"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.106",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "2.0.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6c5e1be1c48b9172ee610da68fd9cd2770e7a4056cb3fc98710ee6906f0c7960"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.106",
+]
+
+[[package]]
+name = "tinyvec"
+version = "1.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa"
+dependencies = [
+ "tinyvec_macros",
+]
+
+[[package]]
+name = "tinyvec_macros"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
+
+[[package]]
+name = "token-metadata-parser"
+version = "0.1.0"
+dependencies = [
+ "hex",
+ "rand 0.9.2",
+ "solana-program",
+ "solana-sdk",
+ "spl-pod",
+ "spl-token-2022",
+ "spl-token-metadata-interface",
+]
+
+[[package]]
+name = "toml"
+version = "0.5.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "toml_datetime"
+version = "0.6.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c"
+
+[[package]]
+name = "toml_edit"
+version = "0.22.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a"
+dependencies = [
+ "indexmap",
+ "toml_datetime",
+ "winnow",
+]
+
+[[package]]
+name = "typenum"
+version = "1.18.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f"
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
+
+[[package]]
+name = "universal-hash"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea"
+dependencies = [
+ "crypto-common",
+ "subtle",
+]
+
+[[package]]
+name = "uriparse"
+version = "0.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0200d0fc04d809396c2ad43f3c95da3582a2556eba8d453c1087f4120ee352ff"
+dependencies = [
+ "fnv",
+ "lazy_static",
+]
+
+[[package]]
+name = "vcpkg"
+version = "0.2.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
+
+[[package]]
+name = "version_check"
+version = "0.9.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
+
+[[package]]
+name = "wasi"
+version = "0.9.0+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
+
+[[package]]
+name = "wasi"
+version = "0.11.1+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
+
+[[package]]
+name = "wasi"
+version = "0.14.2+wasi-0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3"
+dependencies = [
+ "wit-bindgen-rt",
+]
+
+[[package]]
+name = "wasm-bindgen"
+version = "0.2.100"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5"
+dependencies = [
+ "cfg-if",
+ "once_cell",
+ "rustversion",
+ "wasm-bindgen-macro",
+]
+
+[[package]]
+name = "wasm-bindgen-backend"
+version = "0.2.100"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6"
+dependencies = [
+ "bumpalo",
+ "log",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.106",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-macro"
+version = "0.2.100"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407"
+dependencies = [
+ "quote",
+ "wasm-bindgen-macro-support",
+]
+
+[[package]]
+name = "wasm-bindgen-macro-support"
+version = "0.2.100"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.106",
+ "wasm-bindgen-backend",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-shared"
+version = "0.2.100"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "web-sys"
+version = "0.3.77"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2"
+dependencies = [
+ "js-sys",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "winapi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+dependencies = [
+ "winapi-i686-pc-windows-gnu",
+ "winapi-x86_64-pc-windows-gnu",
+]
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+
+[[package]]
+name = "winapi-util"
+version = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0978bf7171b3d90bac376700cb56d606feb40f251a475a5d6634613564460b22"
+dependencies = [
+ "windows-sys",
+]
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+
+[[package]]
+name = "windows-link"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a"
+
+[[package]]
+name = "windows-sys"
+version = "0.60.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb"
+dependencies = [
+ "windows-targets 0.53.3",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
+dependencies = [
+ "windows_aarch64_gnullvm 0.52.6",
+ "windows_aarch64_msvc 0.52.6",
+ "windows_i686_gnu 0.52.6",
+ "windows_i686_gnullvm 0.52.6",
+ "windows_i686_msvc 0.52.6",
+ "windows_x86_64_gnu 0.52.6",
+ "windows_x86_64_gnullvm 0.52.6",
+ "windows_x86_64_msvc 0.52.6",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.53.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91"
+dependencies = [
+ "windows-link",
+ "windows_aarch64_gnullvm 0.53.0",
+ "windows_aarch64_msvc 0.53.0",
+ "windows_i686_gnu 0.53.0",
+ "windows_i686_gnullvm 0.53.0",
+ "windows_i686_msvc 0.53.0",
+ "windows_x86_64_gnu 0.53.0",
+ "windows_x86_64_gnullvm 0.53.0",
+ "windows_x86_64_msvc 0.53.0",
+]
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.53.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.53.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.53.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3"
+
+[[package]]
+name = "windows_i686_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
+
+[[package]]
+name = "windows_i686_gnullvm"
+version = "0.53.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.53.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.53.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.53.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.53.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486"
+
+[[package]]
+name = "winnow"
+version = "0.7.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "wit-bindgen-rt"
+version = "0.39.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1"
+dependencies = [
+ "bitflags",
+]
+
+[[package]]
+name = "zerocopy"
+version = "0.8.26"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f"
+dependencies = [
+ "zerocopy-derive",
+]
+
+[[package]]
+name = "zerocopy-derive"
+version = "0.8.26"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.106",
+]
+
+[[package]]
+name = "zeroize"
+version = "1.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde"
+dependencies = [
+ "zeroize_derive",
+]
+
+[[package]]
+name = "zeroize_derive"
+version = "1.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.106",
+]

+ 21 - 0
solana/modules/token_bridge/token_metadata_parser/Cargo.toml

@@ -0,0 +1,21 @@
+[package]
+name = "token-metadata-parser"
+version = "0.1.0"
+edition = "2018"
+
+[lib]
+crate-type = ["cdylib", "lib"]
+name = "token_metadata_parser"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+
+[dev-dependencies]
+spl-token-2022 = "9.0"
+spl-token-metadata-interface = "0.7"
+spl-pod = "0.5"
+solana-program = "2.1"
+solana-sdk = "2.1"
+hex = "0.4"
+rand = "0.9.2"

+ 13 - 0
solana/modules/token_bridge/token_metadata_parser/README.md

@@ -0,0 +1,13 @@
+# What is this
+
+This crate is a 0-dependency parser for the token2022 [metadata pointer and metadata extensions](https://solana.com/developers/courses/token-extensions/token-extensions-metadata).
+
+# Why
+
+In order for the [token bridge program](../program) to support token2022 metadata, it needs to be able to parse the extensions out of the mint account.
+The official parser is implemented in the [spl-token-2022](https://crates.io/crates/spl-token-2022) crate. That crate has a *massive* dependency tree, and is incompatible with the token bridge's dependencies.
+Resolving the dependency issues would require upgrading the token bridge dependencies beyond several major versions, which is risky. Instead, we re-implement the parsing logic from scratch without any external dependencies.
+
+# Why is it excluded from the workspace
+
+In order to verify our parser works, we construct real token2022 mint accounts with the `spl-token-2022` crate. As such, this crate has a *dev* dependency on that crate. If it were included in the workspace, cargo would try to reconcile the dev dependencies with the regular dependencies of the other crates, which would defeat the purpose.

+ 661 - 0
solana/modules/token_bridge/token_metadata_parser/src/lib.rs

@@ -0,0 +1,661 @@
+// when I say 0 dependencies, I mean 0 dependencies
+#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
+pub struct Pubkey(pub [u8; 32]);
+
+impl Pubkey {
+    pub fn new(bytes: [u8; 32]) -> Self {
+        Self(bytes)
+    }
+    pub fn from_slice(s: &[u8]) -> Option<Self> {
+        if s.len() == 32 {
+            let mut a = [0u8; 32];
+            a.copy_from_slice(s);
+            Some(Self(a))
+        } else {
+            None
+        }
+    }
+    pub fn is_zero(&self) -> bool {
+        self.0.iter().all(|&b| b == 0)
+    }
+}
+
+#[derive(Debug, PartialEq)]
+pub enum MintMetadata {
+    None,
+    External(MetadataPointer),
+    Embedded(TokenMetadata),
+}
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct MetadataPointer {
+    pub authority: Option<Pubkey>,
+    /// Where metadata lives. If equals the mint pubkey, metadata is embedded in the mint (TokenMetadata extension).
+    pub metadata_address: Pubkey,
+}
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct TokenMetadata {
+    pub update_authority: Option<Pubkey>,
+    /// Mint this metadata belongs to (usually the mint itself)
+    pub mint: Pubkey,
+    pub name: String,
+    pub symbol: String,
+    pub uri: String,
+    /// Arbitrary additional (key, value) pairs.
+    pub additional_metadata: Vec<(String, String)>,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum ParseError {
+    NotAMintAccount,    // data < 82
+    NotMintAccountType, // account-type byte != Mint(1)
+    NoEmbeddedMetadata, // pointer points to self but no embedded metadata found
+    UnexpectedEnd,
+    UnexpectedLength,
+    LengthMismatch,
+    InvalidUtf8,
+}
+
+const BASE_MINT_LEN: usize = 82;
+const BASE_MINT_LEN_V2: usize = 165;
+const ACCOUNT_TYPE_OFFSET: usize = BASE_MINT_LEN_V2;
+const ACCOUNT_TYPE_MINT: u8 = 1;
+
+// TLV header sizes
+const TLV_TYPE_SIZE: usize = 2; // u16 LE
+const TLV_LEN_SIZE: usize = 2; // u16 LE
+
+// Extension type discriminants we care about:
+const EXT_METADATA_POINTER: u16 = 18;
+const EXT_TOKEN_METADATA: u16 = 19;
+const EXT_UNINITIALIZED: u16 = 0;
+
+/// Given the address and contents of a mint account, this function tells us what kind of metadata the account has.
+/// It supports both spl-token and spl-token-2022 account layouts.
+pub fn parse_token2022_metadata(
+    mint_account: Pubkey,
+    mint_account_data: &[u8],
+) -> Result<MintMetadata, ParseError> {
+    // mint accounts are at least 82 bytes long, so we reject
+    if mint_account_data.len() < BASE_MINT_LEN {
+        return Err(ParseError::NotAMintAccount);
+    }
+
+    // if exactly 82 bytes, it's either an spl token, or a token2022 with no extensions. this is valid
+    if mint_account_data.len() == BASE_MINT_LEN {
+        return Ok(MintMetadata::None); // no extensions
+    }
+
+    // if the mint has extensions, it's at least 166 bytes long. Anything in between is invalid.
+    if mint_account_data.len() <= BASE_MINT_LEN_V2 {
+        return Err(ParseError::NotAMintAccount);
+    }
+
+    // by this point, we know the mint has extensions. the layout here is:
+    // 83..=165 padded with zeros (we verify here)
+    if !mint_account_data[BASE_MINT_LEN..ACCOUNT_TYPE_OFFSET]
+        .iter()
+        .all(|&b| b == 0)
+    {
+        return Err(ParseError::NotAMintAccount);
+    }
+
+    // 166th byte is 1.
+    let acct_type = *mint_account_data
+        .get(ACCOUNT_TYPE_OFFSET)
+        .ok_or(ParseError::UnexpectedEnd)?;
+    if acct_type != ACCOUNT_TYPE_MINT {
+        return Err(ParseError::NotMintAccountType);
+    }
+
+    // the rest is the extensions in TLV format
+    let mut offset = ACCOUNT_TYPE_OFFSET + 1;
+    let mut pointer: Option<MetadataPointer> = None;
+    let mut meta: Option<TokenMetadata> = None;
+
+    while offset + TLV_TYPE_SIZE + TLV_LEN_SIZE <= mint_account_data.len() {
+        let t = u16::from_le_bytes([mint_account_data[offset], mint_account_data[offset + 1]]);
+        let l = u16::from_le_bytes([mint_account_data[offset + 2], mint_account_data[offset + 3]]);
+        offset += TLV_TYPE_SIZE + TLV_LEN_SIZE;
+
+        // Bounds check for value
+        let end = offset
+            .checked_add(l as usize)
+            .ok_or(ParseError::UnexpectedEnd)?;
+        if end > mint_account_data.len() {
+            return Err(ParseError::UnexpectedEnd);
+        }
+        let val = &mint_account_data[offset..end];
+        offset = end;
+
+        if t == EXT_UNINITIALIZED {
+            // NOTE: this can actually never happen, see https://github.com/wormhole-foundation/wormhole/pull/4482#discussion_r2409540742.
+            // We keep this code here as it was audited, and it doesn't actively hurt that much to have it.
+            break; // padding
+        }
+
+        match t {
+            EXT_METADATA_POINTER => {
+                if val.len() != 64 {
+                    return Err(ParseError::UnexpectedLength);
+                }
+                let authority = {
+                    let a = Pubkey::from_slice(&val[0..32]).unwrap();
+                    if a.is_zero() {
+                        None
+                    } else {
+                        Some(a)
+                    }
+                };
+                let metadata_address = Pubkey::from_slice(&val[32..64]).unwrap();
+                pointer = Some(MetadataPointer {
+                    authority,
+                    metadata_address,
+                });
+            }
+            EXT_TOKEN_METADATA => {
+                // value layout:
+                // [0..32)  update_authority (zero = None)
+                // [32..64) mint
+                // then: name: len(u32 LE) + bytes
+                //       symbol: len + bytes
+                //       uri: len + bytes
+                //       kv_count: u32
+                //       kv_count times: key(len+bytes), value(len+bytes)
+                if val.len() < 64 {
+                    return Err(ParseError::UnexpectedLength);
+                }
+                let update_authority = {
+                    let a = Pubkey::from_slice(&val[0..32]).unwrap();
+                    if a.is_zero() {
+                        None
+                    } else {
+                        Some(a)
+                    }
+                };
+                let mint = Pubkey::from_slice(&val[32..64]).unwrap();
+                let mut cur = 64;
+
+                fn read_u32_le(buf: &[u8], cur: &mut usize) -> Result<u32, ParseError> {
+                    if *cur + 4 > buf.len() {
+                        return Err(ParseError::UnexpectedEnd);
+                    }
+                    let n = u32::from_le_bytes([
+                        buf[*cur],
+                        buf[*cur + 1],
+                        buf[*cur + 2],
+                        buf[*cur + 3],
+                    ]);
+                    *cur += 4;
+                    Ok(n)
+                }
+                fn read_string(buf: &[u8], cur: &mut usize) -> Result<String, ParseError> {
+                    let len = read_u32_le(buf, cur)? as usize;
+                    if *cur + len > buf.len() {
+                        return Err(ParseError::UnexpectedEnd);
+                    }
+                    let s = std::str::from_utf8(&buf[*cur..*cur + len])
+                        .map_err(|_| ParseError::InvalidUtf8)?;
+                    *cur += len;
+                    Ok(s.to_owned())
+                }
+
+                let name = read_string(val, &mut cur)?;
+                let symbol = read_string(val, &mut cur)?;
+                let uri = read_string(val, &mut cur)?;
+
+                let kv_count = read_u32_le(val, &mut cur)? as usize;
+                let mut additional_metadata = Vec::with_capacity(kv_count);
+                for _ in 0..kv_count {
+                    let k = read_string(val, &mut cur)?;
+                    let v = read_string(val, &mut cur)?;
+                    additional_metadata.push((k, v));
+                }
+                if cur != val.len() {
+                    return Err(ParseError::LengthMismatch);
+                }
+
+                meta = Some(TokenMetadata {
+                    update_authority,
+                    mint,
+                    name,
+                    symbol,
+                    uri,
+                    additional_metadata,
+                });
+            }
+            _ => {
+                // Unknown extension; skip (we already advanced by its length)
+            }
+        }
+    }
+
+    match pointer {
+        None => Ok(MintMetadata::None),
+        Some(ptr) => {
+            // Both pointer and embedded metadata exist
+            if ptr.metadata_address == mint_account {
+                if let Some(metadata) = meta {
+                    // Pointer points to self, return embedded metadata
+                    Ok(MintMetadata::Embedded(metadata))
+                } else {
+                    Err(ParseError::NoEmbeddedMetadata)
+                }
+            } else {
+                // Pointer points elsewhere, return external pointer
+                // NOTE: metadata may still be Some, but we ignore it (it's valid to have embedded metadata that's ignored)
+                Ok(MintMetadata::External(ptr))
+            }
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use solana_program::{
+        program_option::COption,
+        pubkey::Pubkey as SolanaPubkey,
+    };
+    use spl_pod::optional_keys::OptionalNonZeroPubkey;
+    use spl_token_2022::{
+        extension::{
+            metadata_pointer::MetadataPointer,
+            transfer_fee::TransferFeeConfig,
+            BaseStateWithExtensionsMut,
+            ExtensionType,
+            StateWithExtensionsMut,
+        },
+        state::Mint,
+    };
+    use spl_token_metadata_interface::state::TokenMetadata;
+    use std::str::FromStr;
+
+    fn convert_pubkey(pk: SolanaPubkey) -> Pubkey {
+        Pubkey::new(pk.to_bytes())
+    }
+
+    #[test]
+    fn test_mint_with_metadata_pointer_and_metadata() {
+        let mint_address = SolanaPubkey::new_unique();
+        let authority = SolanaPubkey::new_unique();
+
+        // Create TokenMetadata
+        let token_metadata = TokenMetadata {
+            update_authority: OptionalNonZeroPubkey(authority),
+            mint: mint_address,
+            name: "Test Token".to_string(),
+            symbol: "TEST".to_string(),
+            uri: "https://example.com/token.json".to_string(),
+            additional_metadata: vec![],
+        };
+
+        // Calculate account size needed for both extensions
+        let extension_types = vec![ExtensionType::MetadataPointer];
+        let mut account_size =
+            ExtensionType::try_calculate_account_len::<Mint>(&extension_types).unwrap();
+        // Add space for variable-length TokenMetadata
+        account_size += token_metadata.tlv_size_of().unwrap();
+
+        // Create account data buffer
+        let mut mint_data = vec![0; account_size];
+
+        // Initialize the mint account with extensions
+        let mut state =
+            StateWithExtensionsMut::<Mint>::unpack_uninitialized(&mut mint_data).unwrap();
+
+        // Set up base mint data
+        state.base = Mint {
+            mint_authority: COption::Some(authority),
+            supply: 1000000,
+            decimals: 6,
+            is_initialized: true,
+            freeze_authority: COption::None,
+        };
+
+        // Initialize MetadataPointer extension (pointing to self)
+        let metadata_pointer_extension = state.init_extension::<MetadataPointer>(true).unwrap();
+        metadata_pointer_extension.authority = OptionalNonZeroPubkey(authority);
+        metadata_pointer_extension.metadata_address = OptionalNonZeroPubkey(mint_address);
+
+        // Initialize TokenMetadata as a variable-length extension
+        state
+            .init_variable_len_extension(&token_metadata, true)
+            .unwrap();
+
+        // Pack the base state and initialize account type
+        state.pack_base();
+        state.init_account_type().unwrap();
+
+        // Now test our parser against this properly serialized SPL Token-2022 data
+        let result = parse_token2022_metadata(convert_pubkey(mint_address), &mint_data).unwrap();
+
+        // Since metadata pointer points to self, we should get embedded metadata
+        match &result {
+            MintMetadata::Embedded(token_metadata) => {
+                assert_eq!(
+                    token_metadata.update_authority,
+                    Some(convert_pubkey(authority))
+                );
+                assert_eq!(token_metadata.mint, convert_pubkey(mint_address));
+                assert_eq!(token_metadata.name, "Test Token");
+                assert_eq!(token_metadata.symbol, "TEST");
+                assert_eq!(token_metadata.uri, "https://example.com/token.json");
+                assert_eq!(token_metadata.additional_metadata.len(), 0);
+            }
+            _ => panic!("Expected embedded metadata"),
+        }
+    }
+
+    /// Generate a random ExtensionType that has a known size.
+    /// The only variable-length extension is TokenMetadata, which we skip.
+    fn random_sized_extension() -> ExtensionType {
+        use rand::Rng;
+        use std::convert::TryFrom;
+        loop {
+            let ext_u16: u16 = rand::rng().random_range(0..=27);
+            if let Ok(ext_type) = ExtensionType::try_from(ext_u16) {
+                if ext_type != ExtensionType::TokenMetadata {
+                    return ext_type;
+                }
+            }
+        }
+    }
+
+    // In a previous version of this code, I misintepreted the mint account
+    // extension layout, and thought that mint accounts can be _between_ 82
+    // bytes (no extension) and 165 bytes (token account).
+    //
+    // It turns out that if the mint account has *any* extensions, it will
+    // be padded out with 0s to 165, then a single 1 byte account type
+    // discriminator is inserted, then the extensions follow.
+    //
+    // To verify this, we perform two tests:
+    // 1. generate a random set of extensions and verify the calculated size is either 82 or > 165.
+    // 2. create a mint account with a single small extension (MetadataPointer) and verify its size + the account discriminator.
+    #[test]
+    fn test_fuzz_mint_size() {
+        use rand::Rng;
+
+        let mut rng = rand::rng();
+        for _ in 0..1000 {
+            let len: usize = rng.random_range(0..=3);
+            // now generate `len` random extensions from the ExtensionType enum.
+            // ExtensionType is repr(u16), so we can generate a random u16 and
+            // cast it to ExtensionType, then filter out invalid values.
+            let mut extensions = Vec::new();
+            for _ in 0..len {
+                extensions.push(random_sized_extension());
+            }
+            let account_size =
+                ExtensionType::try_calculate_account_len::<Mint>(&extensions).unwrap();
+            assert!(account_size == 82 || account_size > 165);
+        }
+    }
+
+    #[test]
+    fn test_mint_with_small_extension() {
+        // |------------------+--------------|
+        // | field            | size (bytes) |
+        // |------------------+--------------|
+        // | base             |          165 |
+        // | account type     |            1 |
+        // | extension type   |            2 |
+        // | extension length |            2 |
+        // | metadata pointer |           64 |
+        // |------------------+--------------|
+        // | total            |          234 |
+
+        let mint_address = SolanaPubkey::new_unique();
+        let metadata_address = SolanaPubkey::new_unique();
+        let authority = SolanaPubkey::new_unique();
+
+        // Calculate account size needed for the extension
+        let extension_types = vec![ExtensionType::MetadataPointer];
+        let account_size =
+            ExtensionType::try_calculate_account_len::<Mint>(&extension_types).unwrap();
+
+        assert_eq!(account_size, 234);
+
+        // Create account data buffer
+        let mut mint_data = vec![0; account_size];
+
+        // Initialize the mint account with extensions
+        let mut state =
+            StateWithExtensionsMut::<Mint>::unpack_uninitialized(&mut mint_data).unwrap();
+
+        // Set up base mint data
+        state.base = Mint {
+            mint_authority: COption::Some(authority),
+            supply: 1000000,
+            decimals: 6,
+            is_initialized: true,
+            freeze_authority: COption::None,
+        };
+
+        // Initialize MetadataPointer extension (pointing to self)
+        let metadata_pointer_extension = state.init_extension::<MetadataPointer>(true).unwrap();
+        metadata_pointer_extension.authority = OptionalNonZeroPubkey(authority);
+        metadata_pointer_extension.metadata_address = OptionalNonZeroPubkey(metadata_address);
+
+        // Pack the base state and initialize account type
+        state.pack_base();
+        state.init_account_type().unwrap();
+
+        let result = parse_token2022_metadata(convert_pubkey(mint_address), &mint_data).unwrap();
+
+        // Since metadata pointer points elsewhere, we should get external metadata
+        match &result {
+            MintMetadata::External(addr) => {
+                assert_eq!(addr.metadata_address, convert_pubkey(metadata_address));
+            }
+            _ => panic!("Expected external metadata"),
+        }
+    }
+
+    #[test]
+    fn test_basic_mint_no_extensions() {
+        // Test basic 82-byte mint with no extensions
+        let data = [0u8; 82];
+        let dummy_mint = Pubkey::new([0u8; 32]);
+
+        let result = parse_token2022_metadata(dummy_mint, &data).unwrap();
+        match result {
+            MintMetadata::None => {} // Expected
+            _ => panic!("Expected no metadata"),
+        }
+    }
+
+    #[test]
+    fn test_error_cases() {
+        let dummy_mint = Pubkey::new([0u8; 32]);
+
+        // Test too short data
+        let short_data = vec![0u8; 50];
+        assert_eq!(
+            parse_token2022_metadata(dummy_mint, &short_data),
+            Err(ParseError::NotAMintAccount)
+        );
+
+        // Test wrong account length
+        let wrong_type_data = vec![0u8; 100];
+        assert_eq!(
+            parse_token2022_metadata(dummy_mint, &wrong_type_data),
+            Err(ParseError::NotAMintAccount)
+        );
+    }
+
+    #[test]
+    fn test_mint_with_multiple_extensions() {
+        let mint_address = SolanaPubkey::new_unique();
+        let authority = SolanaPubkey::new_unique();
+
+        // Create TokenMetadata
+        let token_metadata = TokenMetadata {
+            update_authority: OptionalNonZeroPubkey(authority),
+            mint: mint_address,
+            name: "Multi-Extension Token".to_string(),
+            symbol: "MULTI".to_string(),
+            uri: "https://example.com/multi.json".to_string(),
+            additional_metadata: vec![
+                ("category".to_string(), "test".to_string()),
+                ("version".to_string(), "1.0".to_string()),
+            ],
+        };
+
+        // Calculate account size needed for multiple extensions
+        let extension_types = vec![
+            ExtensionType::MetadataPointer,
+            ExtensionType::TransferFeeConfig,
+        ];
+        let mut account_size =
+            ExtensionType::try_calculate_account_len::<Mint>(&extension_types).unwrap();
+        // Add space for variable-length TokenMetadata
+        account_size += token_metadata.tlv_size_of().unwrap();
+
+        // Create account data buffer
+        let mut mint_data = vec![0; account_size];
+
+        // Initialize the mint account with extensions
+        let mut state =
+            StateWithExtensionsMut::<Mint>::unpack_uninitialized(&mut mint_data).unwrap();
+
+        // Set up base mint data
+        state.base = Mint {
+            mint_authority: COption::Some(authority),
+            supply: 5000000,
+            decimals: 9,
+            is_initialized: true,
+            freeze_authority: COption::Some(authority),
+        };
+
+        // Initialize TransferFeeConfig extension (unrelated to metadata)
+        let transfer_fee_extension = state.init_extension::<TransferFeeConfig>(true).unwrap();
+        transfer_fee_extension.transfer_fee_config_authority = OptionalNonZeroPubkey(authority);
+        transfer_fee_extension.withdraw_withheld_authority = OptionalNonZeroPubkey(authority);
+        transfer_fee_extension.withheld_amount = 0.into();
+        transfer_fee_extension
+            .older_transfer_fee
+            .transfer_fee_basis_points = 50.into(); // 0.5%
+        transfer_fee_extension.older_transfer_fee.maximum_fee = 1000000.into(); // 1 token
+        transfer_fee_extension
+            .newer_transfer_fee
+            .transfer_fee_basis_points = 25.into(); // 0.25%
+        transfer_fee_extension.newer_transfer_fee.maximum_fee = 500000.into(); // 0.5 token
+
+        // Initialize MetadataPointer extension (pointing to self)
+        let metadata_pointer_extension = state.init_extension::<MetadataPointer>(true).unwrap();
+        metadata_pointer_extension.authority = OptionalNonZeroPubkey(authority);
+        metadata_pointer_extension.metadata_address = OptionalNonZeroPubkey(mint_address);
+
+        // Initialize TokenMetadata as a variable-length extension
+        state
+            .init_variable_len_extension(&token_metadata, true)
+            .unwrap();
+
+        // Pack the base state and initialize account type
+        state.pack_base();
+        state.init_account_type().unwrap();
+
+        // Now test our parser against this multi-extension data
+        let result = parse_token2022_metadata(convert_pubkey(mint_address), &mint_data).unwrap();
+
+        // Since metadata pointer points to self, we should get embedded metadata (should work despite other extensions)
+        match &result {
+            MintMetadata::Embedded(token_metadata) => {
+                assert_eq!(
+                    token_metadata.update_authority,
+                    Some(convert_pubkey(authority))
+                );
+                assert_eq!(token_metadata.mint, convert_pubkey(mint_address));
+                assert_eq!(token_metadata.name, "Multi-Extension Token");
+                assert_eq!(token_metadata.symbol, "MULTI");
+                assert_eq!(token_metadata.uri, "https://example.com/multi.json");
+                assert_eq!(token_metadata.additional_metadata.len(), 2);
+                assert_eq!(
+                    token_metadata.additional_metadata[0],
+                    ("category".to_string(), "test".to_string())
+                );
+                assert_eq!(
+                    token_metadata.additional_metadata[1],
+                    ("version".to_string(), "1.0".to_string())
+                );
+            }
+            _ => panic!("Expected embedded metadata despite multiple extensions"),
+        }
+    }
+
+    #[test]
+    fn test_real_world_pyusd_mint_data() {
+        // Real-world PYUSD mint account data from mainnet: 2b1kV6DkPAnxd5ixfnxCpjxmKwqjjaYmCZfHsFu24GXo
+        // This mint has both a metadata pointer and embedded TokenMetadata
+        let hex_data = "01000000dd4c486c90f8b6f007c304ef2481f805186be8fd5f52acd1025cb79b9f67ff216c9d1390e1cd000006010100000017853261ef6ab8532a67f053865aad31293fcf07cf120ab5b9a15706548dc02b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010300200017853261ef6ab8532a67f053865aad31293fcf07cf120ab5b9a15706548dc02b0c00200017853261ef6ab8532a67f053865aad31293fcf07cf120ab5b9a15706548dc02b01006c0017853261ef6ab8532a67f053865aad31293fcf07cf120ab5b9a15706548dc02b17853261ef6ab8532a67f053865aad31293fcf07cf120ab5b9a15706548dc02b00000000000000005d02000000000000000000000000000000005d02000000000000000000000000000000000400410017853261ef6ab8532a67f053865aad31293fcf07cf120ab5b9a15706548dc02b0000000000000000000000000000000000000000000000000000000000000000001000810017853261ef6ab8532a67f053865aad31293fcf07cf120ab5b9a15706548dc02b1c37e6433b7304dd82737ae40d9b8bf3c49f5b0e6c49a8d53328b3e506901c5701000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e00400017853261ef6ab8532a67f053865aad31293fcf07cf120ab5b9a15706548dc02b00000000000000000000000000000000000000000000000000000000000000001200400017853261ef6ab8532a67f053865aad31293fcf07cf120ab5b9a15706548dc02b1792483b6c8a2a87b7471d814f9591f9395c840a9ce3d9f4d5ba7d3a4b8a749e1300ae0017853261ef6ab8532a67f053865aad31293fcf07cf120ab5b9a15706548dc02b1792483b6c8a2a87b7471d814f9591f9395c840a9ce3d9f4d5ba7d3a4b8a749e0a00000050617950616c205553440500000050595553444f00000068747470733a2f2f746f6b656e2d6d657461646174612e7061786f732e636f6d2f70797573645f6d657461646174612f70726f642f736f6c616e612f70797573645f6d657461646174612e6a736f6e00000000";
+
+        let raw_data = hex::decode(hex_data).expect("Valid hex string");
+
+        let pyusd_mint = convert_pubkey(
+            SolanaPubkey::from_str("2b1kV6DkPAnxd5ixfnxCpjxmKwqjjaYmCZfHsFu24GXo").unwrap(),
+        );
+
+        // Parse using our custom parser
+        let result = parse_token2022_metadata(pyusd_mint, &raw_data).unwrap();
+
+        let metadata_address = convert_pubkey(
+            SolanaPubkey::from_str("2b1kV6DkPAnxd5ixfnxCpjxmKwqjjaYmCZfHsFu24GXo").unwrap(),
+        );
+
+        match &result {
+            MintMetadata::Embedded(token_metadata) => {
+                // PYUSD has embedded metadata (metadata pointer points to self)
+                assert_eq!(token_metadata.mint, metadata_address);
+                assert_eq!(token_metadata.name, "PayPal USD");
+                assert_eq!(token_metadata.symbol, "PYUSD");
+                assert_eq!(
+                    token_metadata.uri,
+                    "https://token-metadata.paxos.com/pyusd_metadata/prod/solana/pyusd_metadata.json"
+                );
+                assert_eq!(token_metadata.additional_metadata.len(), 0);
+            }
+            _ => panic!("Expected embedded metadata for PYUSD (pointer points to mint)"),
+        }
+    }
+
+    #[test]
+    fn test_real_world_usdc_mint_data() {
+        // Real-world USDC mint account data from mainnet: EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v
+        // This is a regular spl token. We want to make sure we can parse it still.
+        let hex_data = "0100000098fe86e88d9be2ea8bc1cca4878b2988c240f52b8424bfb40ed1a2ddcb5e199b3e8ef2b0d02020000601010000006270aa8a59c59405b45286c86772e6cd126e9b8a5d3a38536d37f7b414e8b667";
+
+        let raw_data = hex::decode(hex_data).expect("Valid hex string");
+        let dummy_mint = Pubkey::new([0u8; 32]); // USDC mint address not needed for this test
+
+        // Parse using our custom parser
+        let result = parse_token2022_metadata(dummy_mint, &raw_data).unwrap();
+
+        // Should return None since this is a basic SPL token with no extensions
+        match result {
+            MintMetadata::None => {} // Expected
+            _ => panic!("Expected no metadata for basic SPL token"),
+        }
+    }
+
+    #[test]
+    fn test_real_world_wsol_token2022_mint_data() {
+        // This is a token2022 token with no extensions: 9pan9bMn5HatX4EJdBwg9VgCa7Uz5HL8N1m5D3NdXejP
+        // We make sure we can parse its mint account the same
+        let hex_data = "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000901000000000000000000000000000000000000000000000000000000000000000000000000";
+
+        let raw_data = hex::decode(hex_data).expect("Valid hex string");
+        let dummy_mint = Pubkey::new([0u8; 32]); // Mint address not needed for this test
+
+        // Parse using our custom parser
+        let result = parse_token2022_metadata(dummy_mint, &raw_data).unwrap();
+
+        // Should return None since this Token-2022 mint has no extensions
+        match result {
+            MintMetadata::None => {} // Expected
+            _ => panic!("Expected no metadata for Token-2022 mint with no extensions"),
+        }
+    }
+}

+ 42 - 3
solana/solitaire/program/src/macros.rs

@@ -110,15 +110,27 @@ macro_rules! solitaire {
 }
 
 #[macro_export]
-macro_rules! pack_type {
-    ($name:ident, $embed:ty, $owner:expr) => {
+macro_rules! pack_type_impl {
+    // We take a "unpacker" as an input, which specifies how to unpack the embedded type.
+    // In most cases, this should be just be
+    // `solana_program::program_pack::Pack`, but in some cases (like token-2022
+    // mints) it may be a custom trait that provides an `unpack` method. This is
+    // because `Pack` does a strict length check on the account, whereas
+    // token-2022 mints with extensions might be longer.
+    //
+    // NOTE: we only use this on the deserialisation side, but we keep the call for serialisation
+    // as solana_program::program_pack::Pack::pack_into_slice. We could generalise that side too, but in
+    // reality, that code is never invoked, because solitaire will persist (and
+    // thus serialise) accounts that are owned by the current program.
+    // `pack_type!` on the other hands is only used for solitaire-ising external accounts.
+    ($name:ident, $embed:ty, $owner:expr, $unpacker:path) => {
         #[repr(transparent)]
         pub struct $name(pub $embed);
 
         impl BorshDeserialize for $name {
             fn deserialize(buf: &mut &[u8]) -> std::io::Result<Self> {
                 let acc = $name(
-                    solana_program::program_pack::Pack::unpack(buf)
+                    <$embed as $unpacker>::unpack(buf)
                         .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?,
                 );
                 // We need to clear the buf to show to Borsh that we've read all data
@@ -158,3 +170,30 @@ macro_rules! pack_type {
         }
     };
 }
+
+#[macro_export]
+macro_rules! pack_type {
+    ($name:ident, $embed:ty, AccountOwner::OneOf($owner:expr)) => {
+        solitaire::pack_type_impl!(
+            $name,
+            $embed,
+            AccountOwner::OneOf($owner),
+            solana_program::program_pack::Pack
+        );
+
+        impl solitaire::processors::seeded::MultiOwned for $name {
+        }
+    };
+    ($name:ident, $embed:ty, $owner:expr) => {
+        solitaire::pack_type_impl!($name, $embed, $owner, solana_program::program_pack::Pack);
+
+        impl solitaire::processors::seeded::SingleOwned for $name {
+        }
+    };
+    ($name:ident, $embed:ty, AccountOwner::OneOf($owner:expr), $unpacker:ident) => {
+        solitaire::pack_type_impl!($name, $embed, AccountOwner::OneOf($owner), $unpacker);
+
+        impl solitaire::processors::seeded::MultiOwned for $name {
+        }
+    };
+}

+ 5 - 0
solana/solitaire/program/src/processors/peel.rs

@@ -217,6 +217,11 @@ impl<
                         return Err(SolitaireError::InvalidOwner(*ctx.info.owner));
                     }
                 }
+                AccountOwner::OneOf(vs) => {
+                    if !vs.contains(ctx.info.owner) {
+                        return Err(SolitaireError::InvalidOwner(*ctx.info.owner));
+                    }
+                }
                 AccountOwner::Any => {}
             };
         }

+ 100 - 1
solana/solitaire/program/src/processors/seeded.rs

@@ -25,16 +25,59 @@ pub trait AccountSize {
 pub enum AccountOwner {
     This,
     Other(Pubkey),
+    OneOf(Vec<Pubkey>),
     Any,
 }
 
+/// The ownership story:
+/// We (solitaire) require every account to have an owner specified for two reasons:
+/// 1. Security checks (i.e. solitaire checks that the account is owned by whoever we expect)
+/// 2. Account creation
+///
+/// Solitaire programs create many accounts, most of which are owned by the program itself.
+/// However, some accounts are owned by external programs. When performing security
+/// checks, the ownership is expressed as a constraint on the account, and this
+/// constraint may allow multiple potential owners.
+/// But when creating an account, we need to know exactly who the owner is.
+///
+/// An example of this is a token account, which, when checking its owner, we
+/// check that it's owned by _either_ the SPL token program, or the
+/// Token2022 program. However, when creating a token account, we need to
+/// know exactly which program is the expected owner.
+///
+/// To this end, we have two traits:
+/// - `SingleOwned` is for accounts that have a single owner
+/// - `MultiOwned` is for accounts that can have multiple potential owners
+///
+/// This stratification allows us then have two separate creation flows:
+/// `Creatable` for accounts with a single owner, and `CreatableWithOwner` for
+/// accounts whose owner is one of a set of owners. This way, assuming that only the
+/// appropriate trait is implemented for a type, the compiler statically
+/// guarantees that the appropriate creation flow is used (.create vs .create_with_owner).
 pub trait Owned {
     fn owner(&self) -> AccountOwner;
+}
 
+/// A trait for accounts that may have a single owner. Most accounts are like this.
+pub trait SingleOwned: Owned {
     fn owner_pubkey(&self, program_id: &Pubkey) -> Result<Pubkey> {
         match self.owner() {
             AccountOwner::This => Ok(*program_id),
             AccountOwner::Other(v) => Ok(v),
+            AccountOwner::OneOf(_) => Err(SolitaireError::AmbiguousOwner),
+            AccountOwner::Any => Err(SolitaireError::AmbiguousOwner),
+        }
+    }
+}
+
+/// A trait for accounts that may have multiple potential owners. This is the
+/// equivalent of an `InterfaceAccount` in anchor.
+pub trait MultiOwned: Owned {
+    fn owners_pubkeys(&self, program_id: &Pubkey) -> Result<Vec<Pubkey>> {
+        match self.owner() {
+            AccountOwner::This => Ok(vec![*program_id]),
+            AccountOwner::Other(v) => Ok(vec![v]),
+            AccountOwner::OneOf(v) => Ok(v),
             AccountOwner::Any => Err(SolitaireError::AmbiguousOwner),
         }
     }
@@ -48,6 +91,16 @@ impl<'a, T: Owned + Default, const IS_INITIALIZED: AccountState> Owned
     }
 }
 
+impl<'a, T: SingleOwned + Default, const IS_INITIALIZED: AccountState> SingleOwned
+    for Data<'a, T, IS_INITIALIZED>
+{
+}
+
+impl<'a, T: MultiOwned + Default, const IS_INITIALIZED: AccountState> MultiOwned
+    for Data<'a, T, IS_INITIALIZED>
+{
+}
+
 pub trait Seeded<I> {
     fn seeds(accs: I) -> Vec<Vec<u8>>;
 
@@ -105,6 +158,17 @@ pub trait Creatable<'a, I> {
     ) -> Result<()>;
 }
 
+pub trait CreatableWithOwner<'a, I> {
+    fn create_with_owner(
+        &'a self,
+        owner: &Pubkey,
+        accs: I,
+        ctx: &'a ExecutionContext,
+        payer: &'a Pubkey,
+        lamports: CreationLamports,
+    ) -> Result<()>;
+}
+
 impl<T: BorshSerialize + Owned + Default, const IS_INITIALIZED: AccountState> AccountSize
     for Data<'_, T, IS_INITIALIZED>
 {
@@ -113,7 +177,9 @@ impl<T: BorshSerialize + Owned + Default, const IS_INITIALIZED: AccountState> Ac
     }
 }
 
-impl<'a, 'b: 'a, K, T: AccountSize + Seeded<K> + Keyed<'a, 'b> + Owned> Creatable<'a, K> for T {
+impl<'a, 'b: 'a, K, T: AccountSize + Seeded<K> + Keyed<'a, 'b> + SingleOwned> Creatable<'a, K>
+    for T
+{
     fn create(
         &'a self,
         accs: K,
@@ -139,6 +205,39 @@ impl<'a, 'b: 'a, K, T: AccountSize + Seeded<K> + Keyed<'a, 'b> + Owned> Creatabl
     }
 }
 
+impl<'a, 'b: 'a, K, T: AccountSize + Seeded<K> + Keyed<'a, 'b> + MultiOwned>
+    CreatableWithOwner<'a, K> for T
+{
+    fn create_with_owner(
+        &'a self,
+        owner: &Pubkey,
+        accs: K,
+        ctx: &'a ExecutionContext<'_, '_>,
+        payer: &'a Pubkey,
+        lamports: CreationLamports,
+    ) -> Result<()> {
+        if !self.owners_pubkeys(ctx.program_id)?.contains(owner) {
+            return Err(SolitaireError::InvalidOwner(*owner));
+        }
+
+        let seeds = T::bumped_seeds(accs, ctx.program_id);
+        let size = self.size();
+
+        let s: Vec<&[u8]> = seeds.iter().map(|item| item.as_slice()).collect();
+        let seed_slice = s.as_slice();
+
+        create_account(
+            ctx,
+            self.info(),
+            payer,
+            lamports,
+            size,
+            owner,
+            SignedWithSeeds(&[seed_slice]),
+        )
+    }
+}
+
 impl<'a, const SEED: &'static str, T> Seeded<Option<()>> for Derive<T, SEED> {
     fn seeds(_accs: Option<()>) -> Vec<Vec<u8>> {
         vec![SEED.as_bytes().to_vec()]

+ 4 - 0
whitepapers/0003_token_bridge.md

@@ -249,6 +249,10 @@ Since there is no way for a token bridge endpoint to know which other chain alre
 native asset on its chain, there may be transfers initiated for assets that don't have wrapped assets set up yet on the
 target chain. However, the transfer will become executable once the wrapped asset is set up (which can be done any time).
 
+The name and symbol fields of the Transfer payload are not guaranteed to be valid UTF8 strings.
+Implementations might truncate longer strings at the 32 byte mark, which may result in invalid UTF8 bytes at the end.
+Thus, any client wishing to present these as strings must validate them first, potentially dropping the garbage at the end.
+
 <!-- Local Variables: -->
 <!-- fill-column: 120 -->
 <!-- End: -->