From faf659a4724357bec3f5c29c6c1c7e19d408a4d4 Mon Sep 17 00:00:00 2001 From: fraggdiller Date: Thu, 27 Jun 2024 01:36:02 +0300 Subject: [PATCH 01/48] Update solana dependencies from 1.18.12 to 2.0.1 version --- Cargo.toml | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 738f57f..d28e489 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,12 +24,13 @@ semver = "1.0.23" serde = "1.0.198" serde-enum-str = "0.4.0" serde_json = "1.0.116" -solana-account-decoder = "1.18.12" -solana-client = "1.18.12" -solana-program = "1.18.12" -solana-rpc-client-api = "1.18.12" -solana-sdk = "1.18.11" -solana-transaction-status = "1.18.12" +solana-account-decoder = "2.0.1" +solana-client = "2.0.1" +solana-program = "2.0.1" +solana-rpc-client-api = "2.0.1" +solana-sdk = "2.0.1" +solana-transaction-status = "2.0.1" +spl-token-2022 = "4.0.0" thiserror = "1.0.58" tokio = { version = "1.37.0", features = ["macros", "rt-multi-thread", "net"] } tokio-stream = "0.1.15" From 99d42f4291bce6df4419963c191f29e7ef5d8cfe Mon Sep 17 00:00:00 2001 From: n98365184 <165314898+n98365184@users.noreply.github.com> Date: Sun, 7 Jul 2024 16:05:31 +0700 Subject: [PATCH 02/48] fix(types): mismatch The current response uses "min" instead of "none". --- src/types/types.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/types/types.rs b/src/types/types.rs index a6aa909..3bd1a0f 100644 --- a/src/types/types.rs +++ b/src/types/types.rs @@ -795,7 +795,7 @@ pub struct GetPriorityFeeEstimateRequest { #[derive(Serialize, Deserialize, Debug, Default)] pub struct MicroLamportPriorityFeeLevels { - pub none: f64, + pub min: f64, pub low: f64, pub medium: f64, pub high: f64, From c78d8b7fd3e539bf639162219a5cca1a7550053c Mon Sep 17 00:00:00 2001 From: Evan <0xIchigo@protonmail.com> Date: Fri, 26 Jul 2024 01:23:46 -0400 Subject: [PATCH 03/48] Fix Dep Issues --- Cargo.toml | 13 ++++++------- tests/rpc/test_get_priority_fee_estimate.rs | 2 +- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index d28e489..742811f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,13 +24,12 @@ semver = "1.0.23" serde = "1.0.198" serde-enum-str = "0.4.0" serde_json = "1.0.116" -solana-account-decoder = "2.0.1" -solana-client = "2.0.1" -solana-program = "2.0.1" -solana-rpc-client-api = "2.0.1" -solana-sdk = "2.0.1" -solana-transaction-status = "2.0.1" -spl-token-2022 = "4.0.0" +solana-account-decoder = "=1.18.12" +solana-client = "=1.18.12" +solana-program = "=1.18.12" +solana-rpc-client-api = "=1.18.12" +solana-sdk = "=1.18.12" +solana-transaction-status = "=1.18.12" thiserror = "1.0.58" tokio = { version = "1.37.0", features = ["macros", "rt-multi-thread", "net"] } tokio-stream = "0.1.15" diff --git a/tests/rpc/test_get_priority_fee_estimate.rs b/tests/rpc/test_get_priority_fee_estimate.rs index 5e6e391..c34d6d5 100644 --- a/tests/rpc/test_get_priority_fee_estimate.rs +++ b/tests/rpc/test_get_priority_fee_estimate.rs @@ -19,7 +19,7 @@ async fn test_get_nft_editions_success() { result: GetPriorityFeeEstimateResponse { priority_fee_estimate: Some(100.0), priority_fee_levels: Some(MicroLamportPriorityFeeLevels { - none: 0.0, + min: 0.0, low: 10.0, medium: 100.0, high: 500.0, From f6196e78221dafa1fc956a6d6db6aa1a5cc5b07f Mon Sep 17 00:00:00 2001 From: Evan <0xIchigo@protonmail.com> Date: Fri, 26 Jul 2024 01:48:08 -0400 Subject: [PATCH 04/48] Update get_compute_units --- src/optimized_transaction.rs | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/src/optimized_transaction.rs b/src/optimized_transaction.rs index 53f3fd1..290eccc 100644 --- a/src/optimized_transaction.rs +++ b/src/optimized_transaction.rs @@ -40,7 +40,7 @@ impl Helius { instructions: Vec, payer: Pubkey, lookup_tables: Vec, - signers: &[&dyn Signer], + signers: Option<&[&dyn Signer]>, ) -> Result> { // Set the compute budget limit let test_instructions: Vec = vec![ComputeBudgetInstruction::set_compute_unit_limit(1_400_000)] @@ -56,9 +56,16 @@ impl Helius { v0::Message::try_compile(&payer, &test_instructions, &lookup_tables, recent_blockhash)?; let versioned_message: VersionedMessage = VersionedMessage::V0(v0_message); - // Create a signed VersionedTransaction - let transaction: VersionedTransaction = VersionedTransaction::try_new(versioned_message, signers) - .map_err(|e| HeliusError::InvalidInput(format!("Signing error: {:?}", e)))?; + // Create a VersionedTransaction (signed or unsigned) + let transaction: VersionedTransaction = if let Some(signers) = signers { + VersionedTransaction::try_new(versioned_message, signers) + .map_err(|e| HeliusError::InvalidInput(format!("Signing error: {:?}", e)))? + } else { + VersionedTransaction { + signatures: vec![], + message: versioned_message, + } + }; // Simulate the transaction let config: RpcSimulateTransactionConfig = RpcSimulateTransactionConfig { @@ -160,7 +167,7 @@ impl Helius { v0::Message::try_compile(&payer_pubkey, &config.instructions, lookup_tables, recent_blockhash)?; let versioned_message: VersionedMessage = VersionedMessage::V0(v0_message); - let all_signers = if let Some(fee_payer) = config.fee_payer { + let all_signers: Vec<&dyn Signer> = if let Some(fee_payer) = config.fee_payer { let mut all_signers: Vec<&dyn Signer> = config.signers.clone(); if !all_signers.iter().any(|signer| signer.pubkey() == fee_payer.pubkey()) { all_signers.push(fee_payer); @@ -238,7 +245,7 @@ impl Helius { updated_instructions, payer_pubkey, config.lookup_tables.clone().unwrap_or_default(), - &config.signers, + Some(&config.signers), ) .await?; From 32437dc21f2a9b6d496e395abf64f762b0bdae76 Mon Sep 17 00:00:00 2001 From: Evan <0xIchigo@protonmail.com> Date: Fri, 26 Jul 2024 01:51:21 -0400 Subject: [PATCH 05/48] Make sig_verify Optional --- src/optimized_transaction.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/optimized_transaction.rs b/src/optimized_transaction.rs index 290eccc..1757a26 100644 --- a/src/optimized_transaction.rs +++ b/src/optimized_transaction.rs @@ -69,7 +69,7 @@ impl Helius { // Simulate the transaction let config: RpcSimulateTransactionConfig = RpcSimulateTransactionConfig { - sig_verify: true, + sig_verify: signers.is_some(), ..Default::default() }; let result: Response = self From 9d6c861791a359e2c950b27f6b6b1da253bb05ba Mon Sep 17 00:00:00 2001 From: Anam Ansari Date: Thu, 29 Aug 2024 00:43:25 +0530 Subject: [PATCH 06/48] fix: support for staked endpoint rpc --- Cargo.toml | 12 ++++++------ src/types/enums.rs | 3 +++ src/types/types.rs | 11 ++++++++--- tests/test_client.rs | 4 ++-- 4 files changed, 19 insertions(+), 11 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 742811f..70994f4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,12 +24,12 @@ semver = "1.0.23" serde = "1.0.198" serde-enum-str = "0.4.0" serde_json = "1.0.116" -solana-account-decoder = "=1.18.12" -solana-client = "=1.18.12" -solana-program = "=1.18.12" -solana-rpc-client-api = "=1.18.12" -solana-sdk = "=1.18.12" -solana-transaction-status = "=1.18.12" +solana-account-decoder = "=2.0.7" +solana-client = "=2.0.7" +solana-program = "=2.0.7" +solana-rpc-client-api = "=2.0.7" +solana-sdk = "=2.0.7" +solana-transaction-status = "=2.0.7" thiserror = "1.0.58" tokio = { version = "1.37.0", features = ["macros", "rt-multi-thread", "net"] } tokio-stream = "0.1.15" diff --git a/src/types/enums.rs b/src/types/enums.rs index 8600745..88365ce 100644 --- a/src/types/enums.rs +++ b/src/types/enums.rs @@ -149,6 +149,9 @@ impl MintApiAuthority { Cluster::MainnetBeta => Ok(MintApiAuthority::Mainnet( "HnT5KVAywGgQDhmh6Usk4bxRg4RwKxCK4jmECyaDth5R", )), + Cluster::StakedMainnetBeta => Ok(MintApiAuthority::Mainnet( + "HnT5KVAywGgQDhmh6Usk4bxRg4RwKxCK4jmECyaDth5R", + )) } } } diff --git a/src/types/types.rs b/src/types/types.rs index 3bd1a0f..c4eddc6 100644 --- a/src/types/types.rs +++ b/src/types/types.rs @@ -16,13 +16,14 @@ use solana_sdk::{address_lookup_table::AddressLookupTableAccount, instruction::I pub enum Cluster { Devnet, MainnetBeta, + StakedMainnetBeta } /// Stores the API and RPC endpoint URLs for a specific Helius cluster #[derive(Debug, Clone)] pub struct HeliusEndpoints { pub api: String, - pub rpc: String, + pub rpc: String } impl HeliusEndpoints { @@ -30,12 +31,16 @@ impl HeliusEndpoints { match cluster { Cluster::Devnet => HeliusEndpoints { api: "https://api-devnet.helius-rpc.com/".to_string(), - rpc: "https://devnet.helius-rpc.com/".to_string(), + rpc: "https://devnet.helius-rpc.com/".to_string() }, Cluster::MainnetBeta => HeliusEndpoints { api: "https://api-mainnet.helius-rpc.com/".to_string(), - rpc: "https://mainnet.helius-rpc.com/".to_string(), + rpc: "https://mainnet.helius-rpc.com/".to_string() }, + Cluster::StakedMainnetBeta => HeliusEndpoints { + api: "https://api-mainnet.helius-rpc.com/".to_string(), + rpc: "https://staked.helius-rpc.com/".to_string() + } } } } diff --git a/tests/test_client.rs b/tests/test_client.rs index bc8b2e9..e7e9104 100644 --- a/tests/test_client.rs +++ b/tests/test_client.rs @@ -5,7 +5,7 @@ use helius::types::Cluster; #[test] fn test_creating_new_client_success() { let api_key: &str = "valid-api-key"; - let cluster: Cluster = Cluster::Devnet; + let cluster: Cluster = Cluster::StakedMainnetBeta; let result: Result = Helius::new(api_key, cluster); assert!(result.is_ok()); @@ -17,7 +17,7 @@ fn test_creating_new_client_success() { #[test] fn test_creating_new_async_client_success() { let api_key: &str = "valid-api-key"; - let cluster: Cluster = Cluster::Devnet; + let cluster: Cluster = Cluster::StakedMainnetBeta; let result: Result = Helius::new_with_async_solana(api_key, cluster); assert!(result.is_ok()); From 1897624513c610b467e420637140905426c362db Mon Sep 17 00:00:00 2001 From: Anam Ansari Date: Fri, 30 Aug 2024 13:07:20 +0530 Subject: [PATCH 07/48] chore: client test for staked connection --- src/types/enums.rs | 2 +- src/types/types.rs | 12 ++++++------ tests/test_client.rs | 4 ++-- tests/test_client_staked.rs | 28 ++++++++++++++++++++++++++++ tests/test_factory.rs | 10 ++++++++++ 5 files changed, 47 insertions(+), 9 deletions(-) create mode 100644 tests/test_client_staked.rs diff --git a/src/types/enums.rs b/src/types/enums.rs index 88365ce..bed9b46 100644 --- a/src/types/enums.rs +++ b/src/types/enums.rs @@ -151,7 +151,7 @@ impl MintApiAuthority { )), Cluster::StakedMainnetBeta => Ok(MintApiAuthority::Mainnet( "HnT5KVAywGgQDhmh6Usk4bxRg4RwKxCK4jmECyaDth5R", - )) + )), } } } diff --git a/src/types/types.rs b/src/types/types.rs index c4eddc6..002b4fa 100644 --- a/src/types/types.rs +++ b/src/types/types.rs @@ -16,14 +16,14 @@ use solana_sdk::{address_lookup_table::AddressLookupTableAccount, instruction::I pub enum Cluster { Devnet, MainnetBeta, - StakedMainnetBeta + StakedMainnetBeta, } /// Stores the API and RPC endpoint URLs for a specific Helius cluster #[derive(Debug, Clone)] pub struct HeliusEndpoints { pub api: String, - pub rpc: String + pub rpc: String, } impl HeliusEndpoints { @@ -31,16 +31,16 @@ impl HeliusEndpoints { match cluster { Cluster::Devnet => HeliusEndpoints { api: "https://api-devnet.helius-rpc.com/".to_string(), - rpc: "https://devnet.helius-rpc.com/".to_string() + rpc: "https://devnet.helius-rpc.com/".to_string(), }, Cluster::MainnetBeta => HeliusEndpoints { api: "https://api-mainnet.helius-rpc.com/".to_string(), - rpc: "https://mainnet.helius-rpc.com/".to_string() + rpc: "https://mainnet.helius-rpc.com/".to_string(), }, Cluster::StakedMainnetBeta => HeliusEndpoints { api: "https://api-mainnet.helius-rpc.com/".to_string(), - rpc: "https://staked.helius-rpc.com/".to_string() - } + rpc: "https://staked.helius-rpc.com/".to_string(), + }, } } } diff --git a/tests/test_client.rs b/tests/test_client.rs index e7e9104..bc8b2e9 100644 --- a/tests/test_client.rs +++ b/tests/test_client.rs @@ -5,7 +5,7 @@ use helius::types::Cluster; #[test] fn test_creating_new_client_success() { let api_key: &str = "valid-api-key"; - let cluster: Cluster = Cluster::StakedMainnetBeta; + let cluster: Cluster = Cluster::Devnet; let result: Result = Helius::new(api_key, cluster); assert!(result.is_ok()); @@ -17,7 +17,7 @@ fn test_creating_new_client_success() { #[test] fn test_creating_new_async_client_success() { let api_key: &str = "valid-api-key"; - let cluster: Cluster = Cluster::StakedMainnetBeta; + let cluster: Cluster = Cluster::Devnet; let result: Result = Helius::new_with_async_solana(api_key, cluster); assert!(result.is_ok()); diff --git a/tests/test_client_staked.rs b/tests/test_client_staked.rs new file mode 100644 index 0000000..f529799 --- /dev/null +++ b/tests/test_client_staked.rs @@ -0,0 +1,28 @@ +use helius::client::Helius; +use helius::error::Result; +use helius::types::Cluster; + +#[test] +fn test_creating_new_client_staked_success() { + let api_key: &str = "valid-api-key"; + let cluster: Cluster = Cluster::StakedMainnetBeta; + + let result: Result = Helius::new(api_key, cluster); + assert!(result.is_ok()); + + let helius: Helius = result.unwrap(); + assert_eq!(helius.config.api_key, api_key); +} + +#[test] +fn test_creating_new_async_client_staked_success() { + let api_key: &str = "valid-api-key"; + let cluster: Cluster = Cluster::StakedMainnetBeta; + + let result: Result = Helius::new_with_async_solana(api_key, cluster); + assert!(result.is_ok()); + + let helius: Helius = result.unwrap(); + assert_eq!(helius.config.api_key, api_key); + assert!(helius.async_rpc_client.is_some()); +} diff --git a/tests/test_factory.rs b/tests/test_factory.rs index 92c6787..55826eb 100644 --- a/tests/test_factory.rs +++ b/tests/test_factory.rs @@ -21,6 +21,16 @@ fn test_factory_create_mainnet_instance() { assert_eq!(helius.config.endpoints.rpc, "https://mainnet.helius-rpc.com/"); } +#[test] +fn test_factory_create_staked_mainnet_instance() { + let factory: HeliusFactory = HeliusFactory::new("valid_api_key"); + let helius: Helius = factory.create(Cluster::StakedMainnetBeta).unwrap(); + + assert_eq!(helius.config.api_key, "valid_api_key"); + assert_eq!(helius.config.endpoints.api, "https://api-mainnet.helius-rpc.com/"); + assert_eq!(helius.config.endpoints.rpc, "https://staked.helius-rpc.com/"); +} + #[test] fn test_factory_create_with_reqwest() { let mut factory = HeliusFactory::new("valid_api_key"); From 2aabe5f8364f6dfbd6038c0a816f3de2480433c3 Mon Sep 17 00:00:00 2001 From: Evan <0xIchigo@protonmail.com> Date: Sun, 1 Sep 2024 02:16:17 -0400 Subject: [PATCH 08/48] Add Smart Transaction With Seeds Types --- src/types/types.rs | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/src/types/types.rs b/src/types/types.rs index 002b4fa..e63af21 100644 --- a/src/types/types.rs +++ b/src/types/types.rs @@ -989,3 +989,32 @@ pub struct BasicRequest { pub method: String, pub params: Vec>, } + +#[derive(Clone)] +pub struct CreateSmartTransactionSeedConfig { + pub instructions: Vec, + pub signer_seeds: Vec<[u8; 32]>, + pub fee_payer_seed: Option<[u8; 32]>, + pub lookup_tables: Option>, +} + +impl CreateSmartTransactionSeedConfig { + pub fn new(instructions: Vec, signer_seeds: Vec<[u8; 32]>) -> Self { + Self { + instructions, + signer_seeds, + fee_payer_seed: None, + lookup_tables: None, + } + } + + pub fn with_fee_payer_seed(mut self, seed: [u8; 32]) -> Self { + self.fee_payer_seed = Some(seed); + self + } + + pub fn with_lookup_tables(mut self, lookup_tables: Vec) -> Self { + self.lookup_tables = Some(lookup_tables); + self + } +} From 9fa2b031014e0517b5156236a88908766fa19b73 Mon Sep 17 00:00:00 2001 From: Evan <0xIchigo@protonmail.com> Date: Sun, 1 Sep 2024 02:16:37 -0400 Subject: [PATCH 09/48] Implement send_smart_transaction_with_seeds --- src/optimized_transaction.rs | 75 +++++++++++++++++++++++++++++++++++- 1 file changed, 73 insertions(+), 2 deletions(-) diff --git a/src/optimized_transaction.rs b/src/optimized_transaction.rs index 1757a26..f5d9899 100644 --- a/src/optimized_transaction.rs +++ b/src/optimized_transaction.rs @@ -1,7 +1,7 @@ use crate::error::{HeliusError, Result}; use crate::types::{ - CreateSmartTransactionConfig, GetPriorityFeeEstimateOptions, GetPriorityFeeEstimateRequest, - GetPriorityFeeEstimateResponse, SmartTransaction, SmartTransactionConfig, + CreateSmartTransactionConfig, CreateSmartTransactionSeedConfig, GetPriorityFeeEstimateOptions, + GetPriorityFeeEstimateRequest, GetPriorityFeeEstimateResponse, SmartTransaction, SmartTransactionConfig, }; use crate::Helius; @@ -9,6 +9,7 @@ use bincode::{serialize, ErrorKind}; use reqwest::StatusCode; use solana_client::rpc_config::{RpcSendTransactionConfig, RpcSimulateTransactionConfig}; use solana_client::rpc_response::{Response, RpcSimulateTransactionResult}; +use solana_sdk::signature::{keypair_from_seed, Keypair}; use solana_sdk::{ address_lookup_table::AddressLookupTableAccount, bs58::encode, @@ -378,4 +379,74 @@ impl Helius { text: "Transaction failed to confirm in 60s".to_string(), }) } + + /// Sends a smart transaction using seed bytes + /// + /// This method allows for sending smart transactions in asynchronous contexts + /// where the Signer trait's lack of Send + Sync would otherwise cause issues. + /// It creates Keypairs from the provided seed bytes and uses them to sign the transaction. + /// + /// # Arguments + /// + /// * `create_config` - A `CreateSmartTransactionSeedConfig` containing: + /// - `instructions`: The instructions to be executed in the transaction. + /// - `signer_seeds`: Seed bytes for generating signer keypairs. + /// - `fee_payer_seed`: Optional seed bytes for generating the fee payer keypair. + /// - `lookup_tables`: Optional address lookup tables for the transaction. + /// * `send_options` - Optional `RpcSendTransactionConfig` for sending the transaction. + /// + /// # Returns + /// + /// A `Result` containing the transaction signature if successful, or an error if not. + /// + /// # Errors + /// + /// This function will return an error if keypair creation from seeds fails or the underlying `send_smart_transaction` call fails + /// + /// # Notes + /// + /// If no `fee_payer_seed` is provided, the first signer (i.e., derived from the first seed in `signer_seeds`) will be used as the fee payer + pub async fn send_smart_transaction_with_seeds( + &self, + create_config: CreateSmartTransactionSeedConfig, + send_options: Option, + ) -> Result { + if create_config.signer_seeds.is_empty() { + return Err(HeliusError::InvalidInput("At least one signer seed must be provided".to_string())); + } + + let mut signers: Vec = create_config + .signer_seeds + .into_iter() + .map(|seed| keypair_from_seed(&seed).expect("Failed to create keypair from seed")) + .collect(); + + let fee_payer: Option = create_config + .fee_payer_seed + .map(|seed| keypair_from_seed(&seed).expect("Failed to create fee payer keypair from seed")); + + // Determine the fee payer + let fee_payer_index: usize = if fee_payer.is_some() { + signers.push(fee_payer.unwrap()); + signers.len() - 1 // Index of the last signer (fee payer) + } else { + 0 // Index of the first signer + }; + + let signer_refs: Vec<&dyn Signer> = signers.iter().map(|keypair| keypair as &dyn Signer).collect(); + + let create_smart_transaction_config: CreateSmartTransactionConfig<'_> = CreateSmartTransactionConfig { + instructions: create_config.instructions, + signers: signer_refs, + lookup_tables: create_config.lookup_tables, + fee_payer: Some(&signers[fee_payer_index] as &dyn Signer), + }; + + let smart_transaction_config: SmartTransactionConfig<'_> = SmartTransactionConfig { + create_config: create_smart_transaction_config, + send_options: send_options.unwrap_or_default(), + }; + + self.send_smart_transaction(smart_transaction_config).await + } } From 21a0b3acf87d07854852a7506483b31f0fe26b55 Mon Sep 17 00:00:00 2001 From: Evan <0xIchigo@protonmail.com> Date: Sun, 1 Sep 2024 02:16:57 -0400 Subject: [PATCH 10/48] Update README.md with send_smart_transaction_with_seeds --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index a8d40d7..16d5b83 100644 --- a/README.md +++ b/README.md @@ -122,6 +122,7 @@ Our SDK is designed to provide a seamless developer experience when building on - [`get_compute_units`](https://github.com/helius-labs/helius-rust-sdk/blob/a79a751e1a064125010bdb359068a366d635d005/src/optimized_transaction.rs#L29-L75) - Simulates a transaction to get the total compute units consumed - [`poll_transaction_confirmation`](https://github.com/helius-labs/helius-rust-sdk/blob/a79a751e1a064125010bdb359068a366d635d005/src/optimized_transaction.rs#L77-L112) - Polls a transaction to check whether it has been confirmed in 5 second intervals with a 15 second timeout - [`send_smart_transaction`](https://github.com/helius-labs/helius-rust-sdk/blob/705d66fb7d4004fc32c2a5f0d6ca4a1f2a7b175d/src/optimized_transaction.rs#L314-L374) - Builds and sends an optimized transaction, and handles its confirmation status +- [`send_smart_transaction_with_seeds`]() - Sends a smart transaction using seed bytes ### Jito Smart Transactions and Helper Methods - [`add_tip_instruction`](https://github.com/helius-labs/helius-rust-sdk/blob/02b351a5ee3fe16a36078b40f92dc72d0ad077ed/src/jito.rs#L66-L83) - Adds a tip instruction to the instructions provided From dbe31327d1df47c26ddbf134fe5f221a1c0b1c20 Mon Sep 17 00:00:00 2001 From: Evan <0xIchigo@protonmail.com> Date: Sun, 1 Sep 2024 02:19:29 -0400 Subject: [PATCH 11/48] Update Doc Comment --- src/optimized_transaction.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/optimized_transaction.rs b/src/optimized_transaction.rs index f5d9899..76417ab 100644 --- a/src/optimized_transaction.rs +++ b/src/optimized_transaction.rs @@ -401,7 +401,8 @@ impl Helius { /// /// # Errors /// - /// This function will return an error if keypair creation from seeds fails or the underlying `send_smart_transaction` call fails + /// This function will return an error if keypair creation from seeds fails, the underlying `send_smart_transaction` call fails, + /// or no signer seeds are provided /// /// # Notes /// From b511e618716bf6247d66b1c5ee432fd5ebbe680f Mon Sep 17 00:00:00 2001 From: Evan <0xIchigo@protonmail.com> Date: Sun, 1 Sep 2024 02:26:05 -0400 Subject: [PATCH 12/48] Fix Formatting --- src/optimized_transaction.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/optimized_transaction.rs b/src/optimized_transaction.rs index 76417ab..6c9be7e 100644 --- a/src/optimized_transaction.rs +++ b/src/optimized_transaction.rs @@ -403,7 +403,7 @@ impl Helius { /// /// This function will return an error if keypair creation from seeds fails, the underlying `send_smart_transaction` call fails, /// or no signer seeds are provided - /// + /// /// # Notes /// /// If no `fee_payer_seed` is provided, the first signer (i.e., derived from the first seed in `signer_seeds`) will be used as the fee payer @@ -413,9 +413,11 @@ impl Helius { send_options: Option, ) -> Result { if create_config.signer_seeds.is_empty() { - return Err(HeliusError::InvalidInput("At least one signer seed must be provided".to_string())); + return Err(HeliusError::InvalidInput( + "At least one signer seed must be provided".to_string(), + )); } - + let mut signers: Vec = create_config .signer_seeds .into_iter() From 61615e6c5e1b137449a184721e25d6cef6271b78 Mon Sep 17 00:00:00 2001 From: Evan <0xIchigo@protonmail.com> Date: Sun, 1 Sep 2024 02:56:43 -0400 Subject: [PATCH 13/48] Make Clippy Happy --- src/optimized_transaction.rs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/optimized_transaction.rs b/src/optimized_transaction.rs index 6c9be7e..9b867ec 100644 --- a/src/optimized_transaction.rs +++ b/src/optimized_transaction.rs @@ -424,13 +424,11 @@ impl Helius { .map(|seed| keypair_from_seed(&seed).expect("Failed to create keypair from seed")) .collect(); - let fee_payer: Option = create_config - .fee_payer_seed - .map(|seed| keypair_from_seed(&seed).expect("Failed to create fee payer keypair from seed")); - // Determine the fee payer - let fee_payer_index: usize = if fee_payer.is_some() { - signers.push(fee_payer.unwrap()); + let fee_payer_index: usize = if let Some(fee_payer_seed) = create_config.fee_payer_seed { + let fee_payer: Keypair = + keypair_from_seed(&fee_payer_seed).expect("Failed to create fee payer keypair from seed"); + signers.push(fee_payer); signers.len() - 1 // Index of the last signer (fee payer) } else { 0 // Index of the first signer From e9e3a82543fedc5e549b11836428c55f822fe328 Mon Sep 17 00:00:00 2001 From: Evan <0xIchigo@protonmail.com> Date: Sun, 1 Sep 2024 12:48:01 -0400 Subject: [PATCH 14/48] Add Features Section and TLS Stuff --- Cargo.toml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 70994f4..99ebd30 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,7 +19,7 @@ futures = "0.3.30" futures-util = "0.3.30" phf = { version = "0.11.2", features = ["macros"] } rand = "0.8.5" -reqwest = { version = "0.12.3", features = ["json"] } +reqwest = { version = "0.12.3", features = ["json"], default-features = false } semver = "1.0.23" serde = "1.0.198" serde-enum-str = "0.4.0" @@ -38,3 +38,8 @@ url = "2.5.0" [dev-dependencies] mockito = "1.4.0" + +[features] +default = ["native-tls"] +native-tls = ["reqwest/native-tls"] +rustls = ["reqwest/rustls-tls"] \ No newline at end of file From 210d0de73335d2047b71db0bc8897c5d71887b3e Mon Sep 17 00:00:00 2001 From: Evan <0xIchigo@protonmail.com> Date: Sun, 1 Sep 2024 12:48:45 -0400 Subject: [PATCH 15/48] Implement TLS Support via Client::builder() --- src/client.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/client.rs b/src/client.rs index 51a63b6..8cb7649 100644 --- a/src/client.rs +++ b/src/client.rs @@ -46,8 +46,9 @@ impl Helius { /// ``` pub fn new(api_key: &str, cluster: Cluster) -> Result { let config: Arc = Arc::new(Config::new(api_key, cluster)?); - let client: Client = Client::new(); + let client: Client = Client::builder().build().map_err(HeliusError::ReqwestError)?; let rpc_client: Arc = Arc::new(RpcClient::new(Arc::new(client.clone()), config.clone())?); + Ok(Helius { config, client, @@ -75,7 +76,7 @@ impl Helius { /// ``` pub fn new_with_async_solana(api_key: &str, cluster: Cluster) -> Result { let config: Arc = Arc::new(Config::new(api_key, cluster)?); - let client: Client = Client::new(); + let client: Client = Client::builder().build().map_err(HeliusError::ReqwestError)?; let url: String = format!("{}/?api-key={}", config.endpoints.rpc, config.api_key); let async_solana_client: Arc = Arc::new(AsyncSolanaRpcClient::new(url)); @@ -98,10 +99,11 @@ impl Helius { /// An instance of `Helius` if successful. A `HeliusError` is returned if an error occurs during configuration or initialization of the HTTP, RPC, or WS client pub async fn new_with_ws(api_key: &str, cluster: Cluster) -> Result { let config: Arc = Arc::new(Config::new(api_key, cluster)?); - let client: Client = Client::new(); + let client: Client = Client::builder().build().map_err(HeliusError::ReqwestError)?; let rpc_client: Arc = Arc::new(RpcClient::new(Arc::new(client.clone()), config.clone())?); let wss: String = format!("{}{}", ENHANCED_WEBSOCKET_URL, api_key); let ws_client: Arc = Arc::new(EnhancedWebsocket::new(&wss).await?); + Ok(Helius { config, client, From 2d1bf42a6b20379cdf1db5698b1f7952164216bc Mon Sep 17 00:00:00 2001 From: Evan <0xIchigo@protonmail.com> Date: Sun, 1 Sep 2024 12:48:59 -0400 Subject: [PATCH 16/48] Add TLS Errors --- src/error.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/error.rs b/src/error.rs index 0b02d2b..468f36b 100644 --- a/src/error.rs +++ b/src/error.rs @@ -123,6 +123,9 @@ pub enum HeliusError { #[error("Url parse error")] UrlParseError(#[from] url::ParseError), + + #[error("TLS error: {0}")] + TlsError(String), } impl HeliusError { @@ -156,5 +159,15 @@ impl From for HeliusError { } } +impl From for HeliusError { + fn from(err: reqwest::Error) -> Self { + if err.is_builder() { + HeliusError::TlsError(err.to_string()) + } else { + HeliusError::ReqwestError(err) + } + } +} + /// A handy type alias for handling results across the Helius SDK pub type Result = std::result::Result; From 3a530c7ee6f30a47e79d9ef1724e6fd208b74528 Mon Sep 17 00:00:00 2001 From: Evan <0xIchigo@protonmail.com> Date: Sun, 1 Sep 2024 12:49:15 -0400 Subject: [PATCH 17/48] Update README.md With TLS Options --- README.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/README.md b/README.md index a8d40d7..d25942d 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,21 @@ where `x.y.z` is your desired version. Alternatively, use `cargo add helius` to Remember to run `cargo update` regularly to fetch the latest version of the SDK. +### TLS Options +By default, this SDK uses the native TLS implementation. It is used as follows: +```toml +[dependencies] +helius = "x.y.z" +``` + +To use `rustls` instead of the native TLS implementation, add the following to your `Cargo.toml`: +```toml +[dependencies] +helius = { version = "x.y.z", default-features = false, features = ["rustls"] } +``` + +Using `rustls` may be preferred in environments where OpenSSL is not available or when a pure Rust TLS implementation is desired. However, it may not support all the same features as the native TLS implementation + ## Usage ### `Helius` The SDK provides a [`Helius`](https://github.com/helius-labs/helius-rust-sdk/blob/dev/src/client.rs) instance that can be configured with an API key and a given Solana cluster. Developers can generate a new API key on the [Helius Developer Dashboard](https://dev.helius.xyz/dashboard/app). This instance acts as the main entry point for interacting with the SDK by providing methods to access different Solana and RPC client functionalities. The following code is an example of how to use the SDK to fetch info on [Mad Lad #8420](https://explorer.solana.com/address/F9Lw3ki3hJ7PF9HQXsBzoY8GyE6sPoEZZdXJBsTTD2rk?network=mainnet): From 85bf998484d0d99c8fb3a37f8d855339f7992134 Mon Sep 17 00:00:00 2001 From: Evan <0xIchigo@protonmail.com> Date: Sun, 1 Sep 2024 12:53:03 -0400 Subject: [PATCH 18/48] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index d25942d..c6ba36f 100644 --- a/README.md +++ b/README.md @@ -17,13 +17,13 @@ where `x.y.z` is your desired version. Alternatively, use `cargo add helius` to Remember to run `cargo update` regularly to fetch the latest version of the SDK. ### TLS Options -By default, this SDK uses the native TLS implementation. It is used as follows: +The Helius Rust SDK uses the native TLS implementation by default via: ```toml [dependencies] helius = "x.y.z" ``` -To use `rustls` instead of the native TLS implementation, add the following to your `Cargo.toml`: +However, the SDK also supports `rustls`. Add the following to your `Cargo.toml` to use `rustls` instead of the native TLS implementation: ```toml [dependencies] helius = { version = "x.y.z", default-features = false, features = ["rustls"] } From dbbb548f0f8513862dc70e0a2abfd312277d169b Mon Sep 17 00:00:00 2001 From: Evan <0xIchigo@protonmail.com> Date: Sun, 1 Sep 2024 13:07:48 -0400 Subject: [PATCH 19/48] Fix Build Issues --- Cargo.toml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 99ebd30..9a2f72d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,17 +19,17 @@ futures = "0.3.30" futures-util = "0.3.30" phf = { version = "0.11.2", features = ["macros"] } rand = "0.8.5" -reqwest = { version = "0.12.3", features = ["json"], default-features = false } +reqwest = { version = "0.11", features = ["json"], default-features = false } semver = "1.0.23" serde = "1.0.198" serde-enum-str = "0.4.0" serde_json = "1.0.116" -solana-account-decoder = "=2.0.7" -solana-client = "=2.0.7" -solana-program = "=2.0.7" -solana-rpc-client-api = "=2.0.7" -solana-sdk = "=2.0.7" -solana-transaction-status = "=2.0.7" +solana-account-decoder = "=1.17.0" +solana-client = "=1.17.0" +solana-program = "=1.17.0" +solana-rpc-client-api = "=1.17.0" +solana-sdk = "=1.17.0" +solana-transaction-status = "=1.17.0" thiserror = "1.0.58" tokio = { version = "1.37.0", features = ["macros", "rt-multi-thread", "net"] } tokio-stream = "0.1.15" From e96e170871f00e0aa711ed1d8d0bdcf4bf19320e Mon Sep 17 00:00:00 2001 From: Anam Ansari Date: Tue, 17 Sep 2024 16:55:07 +0100 Subject: [PATCH 20/48] fix: changed api mainnet endpoint --- src/types/types.rs | 4 ++-- tests/test_factory.rs | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/types/types.rs b/src/types/types.rs index e63af21..062ef13 100644 --- a/src/types/types.rs +++ b/src/types/types.rs @@ -34,11 +34,11 @@ impl HeliusEndpoints { rpc: "https://devnet.helius-rpc.com/".to_string(), }, Cluster::MainnetBeta => HeliusEndpoints { - api: "https://api-mainnet.helius-rpc.com/".to_string(), + api: "https://api.helius-rpc.com/".to_string(), rpc: "https://mainnet.helius-rpc.com/".to_string(), }, Cluster::StakedMainnetBeta => HeliusEndpoints { - api: "https://api-mainnet.helius-rpc.com/".to_string(), + api: "https://api.helius-rpc.com/".to_string(), rpc: "https://staked.helius-rpc.com/".to_string(), }, } diff --git a/tests/test_factory.rs b/tests/test_factory.rs index 55826eb..62a3ec0 100644 --- a/tests/test_factory.rs +++ b/tests/test_factory.rs @@ -17,7 +17,7 @@ fn test_factory_create_mainnet_instance() { let helius: Helius = factory.create(Cluster::MainnetBeta).unwrap(); assert_eq!(helius.config.api_key, "valid_api_key"); - assert_eq!(helius.config.endpoints.api, "https://api-mainnet.helius-rpc.com/"); + assert_eq!(helius.config.endpoints.api, "https://api.helius-rpc.com/"); assert_eq!(helius.config.endpoints.rpc, "https://mainnet.helius-rpc.com/"); } @@ -27,7 +27,7 @@ fn test_factory_create_staked_mainnet_instance() { let helius: Helius = factory.create(Cluster::StakedMainnetBeta).unwrap(); assert_eq!(helius.config.api_key, "valid_api_key"); - assert_eq!(helius.config.endpoints.api, "https://api-mainnet.helius-rpc.com/"); + assert_eq!(helius.config.endpoints.api, "https://api.helius-rpc.com/"); assert_eq!(helius.config.endpoints.rpc, "https://staked.helius-rpc.com/"); } @@ -40,6 +40,6 @@ fn test_factory_create_with_reqwest() { .unwrap(); assert_eq!(helius.config.api_key, "valid_api_key"); - assert_eq!(helius.config.endpoints.api, "https://api-mainnet.helius-rpc.com/"); + assert_eq!(helius.config.endpoints.api, "https://api.helius-rpc.com/"); assert_eq!(helius.config.endpoints.rpc, "https://mainnet.helius-rpc.com/"); } From 4e295e5b94133e987d42ba2834c7fcef00e8d735 Mon Sep 17 00:00:00 2001 From: 0xIchigo <0xIchigo@protonmail.com> Date: Wed, 18 Sep 2024 00:40:11 +0800 Subject: [PATCH 21/48] Update Build Artifact to v4 --- .github/workflows/rust.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index f8ffb22..716369f 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -46,7 +46,7 @@ jobs: path: target \ key: ${{ runner.os }}-target-${{ hashFiles('**/Cargo.lock') }} \ restore-keys: ${{ runner.os }}-target- || echo 'Cache target step failed or timed out.'" - + - name: Set up Rust uses: actions-rs/toolchain@v1 with: @@ -80,7 +80,7 @@ jobs: run: cargo build --verbose - name: Save build artifacts - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 with: name: build path: target From 37e5a2508303b06e880ead70f3d0318d4c09d359 Mon Sep 17 00:00:00 2001 From: Dmytro Medynskyi Date: Tue, 8 Oct 2024 18:34:36 +0200 Subject: [PATCH 22/48] feat: Add revoke_collection_authority --- Cargo.toml | 1 + src/client.rs | 2 + src/config.rs | 6 +- src/mint_api.rs | 30 ++++++- src/types/enums.rs | 26 +++--- src/utils/collection_authority.rs | 50 +++++++++++ src/utils/mod.rs | 2 + tests/test_mint_api.rs | 107 ++++++++++++++--------- tests/utils/test_collection_authority.rs | 68 ++++++++++++++ 9 files changed, 240 insertions(+), 52 deletions(-) create mode 100644 src/utils/collection_authority.rs create mode 100644 tests/utils/test_collection_authority.rs diff --git a/Cargo.toml b/Cargo.toml index 9a2f72d..cb78b7f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,7 @@ bincode = "1.3.3" chrono = { version = "0.4.11", features = ["serde"] } futures = "0.3.30" futures-util = "0.3.30" +mpl-token-metadata = "4.1.2" phf = { version = "0.11.2", features = ["macros"] } rand = "0.8.5" reqwest = { version = "0.11", features = ["json"], default-features = false } diff --git a/src/client.rs b/src/client.rs index 8cb7649..012c3ec 100644 --- a/src/client.rs +++ b/src/client.rs @@ -146,6 +146,8 @@ impl Helius { pub fn ws(&self) -> Option> { self.ws_client.clone() } + + pub fn config(&self) -> Arc { self.config.clone() } } /// A wrapper around the asynchronous Solana RPC client that provides thread-safe access diff --git a/src/config.rs b/src/config.rs index c5df168..3630d66 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,5 +1,5 @@ use crate::error::{HeliusError, Result}; -use crate::types::{Cluster, HeliusEndpoints}; +use crate::types::{Cluster, HeliusEndpoints, MintApiAuthority}; /// Configuration settings for the Helius client /// @@ -40,4 +40,8 @@ impl Config { endpoints, }) } + + pub fn mint_api_authority(&self) -> MintApiAuthority { + MintApiAuthority::from_cluster(&self.cluster) + } } diff --git a/src/mint_api.rs b/src/mint_api.rs index 527ff25..60a7c43 100644 --- a/src/mint_api.rs +++ b/src/mint_api.rs @@ -1,6 +1,9 @@ +use solana_program::pubkey::Pubkey; +use solana_sdk::signature::{Keypair, Signature}; use crate::error::Result; -use crate::types::{MintCompressedNftRequest, MintResponse}; +use crate::types::{MintCompressedNftRequest, MintResponse, SmartTransactionConfig}; use crate::Helius; +use crate::utils::collection_authority::revoke_collection_authority_instruction; impl Helius { /// The easiest way to mint a compressed NFT (cNFT) @@ -13,4 +16,29 @@ impl Helius { pub async fn mint_compressed_nft(&self, request: MintCompressedNftRequest) -> Result { self.rpc_client.post_rpc_request("mintCompressedNft", request).await } + + /// Revokes a delegated collection authority for a given collection mint. + /// + /// # Arguments + /// * `collection_mint` - The public key of the collection mint. + /// * `delegated_collection_authority` - Optional public key of the delegated authority to revoke. If `None`, the default mint API authority is used. + /// * `revoke_authority_keypair` - The keypair of the authority revoking the delegated authority. + /// * `payer_keypair` - Optional keypair to pay for the transaction fees. If `None`, `revoke_authority_keypair` is used as the payer. + /// + /// # Returns + /// A `Result` containing the transaction `Signature` if successful. + pub async fn revoke_collection_authority( + &self, + collection_mint: Pubkey, + delegated_collection_authority: Option, + revoke_authority_keypair: &Keypair, + payer_keypair: Option<&Keypair>, + ) -> Result { + let collection_authority = delegated_collection_authority + .unwrap_or(self.config().mint_api_authority().into()); + let revoke_instruction = revoke_collection_authority_instruction(collection_mint, collection_authority, revoke_authority_keypair); + let payer_keypair = payer_keypair.unwrap_or(revoke_authority_keypair); + let transaction_config = SmartTransactionConfig::new(vec![revoke_instruction], vec![payer_keypair]); + self.send_smart_transaction(transaction_config).await + } } diff --git a/src/types/enums.rs b/src/types/enums.rs index bed9b46..4ad62b7 100644 --- a/src/types/enums.rs +++ b/src/types/enums.rs @@ -1,6 +1,8 @@ use serde::{Deserialize, Serialize}; use serde_enum_str::{Deserialize_enum_str, Serialize_enum_str}; +use solana_program::pubkey::Pubkey; use solana_sdk::transaction::{Transaction, VersionedTransaction}; +use std::str::FromStr; use super::*; @@ -138,20 +140,24 @@ pub enum TokenType { #[derive(Debug, Clone, Copy, PartialEq)] pub enum MintApiAuthority { - Mainnet(&'static str), - Devnet(&'static str), + Mainnet(Pubkey), + Devnet(Pubkey), } impl MintApiAuthority { - pub fn from_cluster(cluster: Cluster) -> Result { + pub fn from_cluster(cluster: &Cluster) -> Self { match cluster { - Cluster::Devnet => Ok(MintApiAuthority::Devnet("2LbAtCJSaHqTnP9M5QSjvAMXk79RNLusFspFN5Ew67TC")), - Cluster::MainnetBeta => Ok(MintApiAuthority::Mainnet( - "HnT5KVAywGgQDhmh6Usk4bxRg4RwKxCK4jmECyaDth5R", - )), - Cluster::StakedMainnetBeta => Ok(MintApiAuthority::Mainnet( - "HnT5KVAywGgQDhmh6Usk4bxRg4RwKxCK4jmECyaDth5R", - )), + Cluster::MainnetBeta | Cluster::StakedMainnetBeta => MintApiAuthority::Mainnet(Pubkey::from_str("HnT5KVAywGgQDhmh6Usk4bxRg4RwKxCK4jmECyaDth5R").unwrap()), + Cluster::Devnet => MintApiAuthority::Devnet(Pubkey::from_str("2LbAtCJSaHqTnP9M5QSjvAMXk79RNLusFspFN5Ew67TC").unwrap()), + } + } +} + +impl Into for MintApiAuthority { + fn into(self) -> Pubkey { + match self { + MintApiAuthority::Mainnet(s) => s, + MintApiAuthority::Devnet(s) => s, } } } diff --git a/src/utils/collection_authority.rs b/src/utils/collection_authority.rs new file mode 100644 index 0000000..bbcddd7 --- /dev/null +++ b/src/utils/collection_authority.rs @@ -0,0 +1,50 @@ +use solana_program::pubkey::Pubkey; +use mpl_token_metadata::ID; +use solana_sdk::signature::{Keypair, Signer}; +use mpl_token_metadata::instructions::RevokeCollectionAuthority; +use solana_program::instruction::Instruction; + +pub fn get_collection_authority_record( + collection_mint: &Pubkey, + collection_authority: &Pubkey +) -> Pubkey { + Pubkey::find_program_address( + &[ + "metadata".as_bytes(), + ID.as_ref(), + &collection_mint.to_bytes(), + "collection_authority".as_bytes(), + &collection_authority.to_bytes()], + &ID, + ) + .0 +} +pub fn get_collection_metadata_account(collection_mint: &Pubkey) -> Pubkey { + Pubkey::find_program_address( + &[ + "metadata".as_bytes(), + ID.as_ref(), + &collection_mint.to_bytes() + ], + &ID, + ) + .0 +} + +pub fn revoke_collection_authority_instruction( + collection_mint: Pubkey, + collection_authority: Pubkey, + revoke_authority_keypair: &Keypair +) -> Instruction { + let collection_metadata = get_collection_metadata_account(&collection_mint); + let collection_authority_record= get_collection_authority_record(&collection_mint, &collection_authority); + let revoke_instruction = RevokeCollectionAuthority { + collection_authority_record, + delegate_authority: collection_authority, + revoke_authority: revoke_authority_keypair.pubkey(), + metadata: collection_metadata, + mint: collection_mint, + }; + revoke_instruction.instruction() +} + diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 006f589..a91bc6c 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -5,3 +5,5 @@ pub use self::make_keypairs::make_keypairs; mod deserialize_str_to_number; mod is_valid_solana_address; mod make_keypairs; + +pub mod collection_authority; \ No newline at end of file diff --git a/tests/test_mint_api.rs b/tests/test_mint_api.rs index 8b7d422..8e2fc7a 100644 --- a/tests/test_mint_api.rs +++ b/tests/test_mint_api.rs @@ -160,44 +160,71 @@ async fn test_get_asset_proof_failure() { assert!(result.is_err(), "Expected an error but got success"); } -#[tokio::test] -async fn test_mint_api_authority_from_cluster_success() { - let devnet_cluster: Cluster = Cluster::Devnet; - let mainnet_cluster: Cluster = Cluster::MainnetBeta; - - let devnet_authority: std::result::Result = MintApiAuthority::from_cluster(devnet_cluster); - let mainnet_authority: std::result::Result = - MintApiAuthority::from_cluster(mainnet_cluster); - - assert_eq!( - devnet_authority.unwrap(), - MintApiAuthority::Devnet("2LbAtCJSaHqTnP9M5QSjvAMXk79RNLusFspFN5Ew67TC"), - "Devnet authority did not match expected value" - ); - assert_eq!( - mainnet_authority.unwrap(), - MintApiAuthority::Mainnet("HnT5KVAywGgQDhmh6Usk4bxRg4RwKxCK4jmECyaDth5R"), - "Mainnet authority did not match expected value" - ); -} -#[tokio::test] -async fn test_mint_api_authority_from_cluster_failure() { - let devnet_cluster: Cluster = Cluster::Devnet; - let mainnet_cluster: Cluster = Cluster::MainnetBeta; - - let devnet_authority: std::result::Result = MintApiAuthority::from_cluster(devnet_cluster); - let mainnet_authority: std::result::Result = - MintApiAuthority::from_cluster(mainnet_cluster); - - assert_ne!( - devnet_authority.unwrap(), - MintApiAuthority::Devnet("Blade"), - "Devnet authority did not match expected value" - ); - assert_ne!( - mainnet_authority.unwrap(), - MintApiAuthority::Mainnet("Deacon Frost"), - "Mainnet authority did not match expected value" - ); -} +#[cfg(test)] +mod tests { + use super::*; + use solana_program::pubkey::Pubkey; + use std::str::FromStr; + + #[test] + fn test_into_pubkey() { + let pubkey_str = "HnT5KVAywGgQDhmh6Usk4bxRg4RwKxCK4jmECyaDth5R"; + let expected_pubkey = Pubkey::from_str(pubkey_str).unwrap(); + + let mint_authority = MintApiAuthority::Mainnet(expected_pubkey); + let converted_pubkey: Pubkey = mint_authority.into(); + + assert_eq!(converted_pubkey, expected_pubkey); + + let pubkey_str = "2LbAtCJSaHqTnP9M5QSjvAMXk79RNLusFspFN5Ew67TC"; + let expected_pubkey = Pubkey::from_str(pubkey_str).unwrap(); + + let mint_authority = MintApiAuthority::Devnet(expected_pubkey); + let converted_pubkey: Pubkey = mint_authority.into(); + + assert_eq!(converted_pubkey, expected_pubkey); + } + + #[test] + fn test_from_cluster() { + let cluster = Cluster::Devnet; + let mint_api_authority = MintApiAuthority::from_cluster(&cluster); + + let expected_pubkey = + Pubkey::from_str("2LbAtCJSaHqTnP9M5QSjvAMXk79RNLusFspFN5Ew67TC").unwrap(); + + match mint_api_authority { + MintApiAuthority::Devnet(pubkey) => { + assert_eq!(pubkey, expected_pubkey); + } + _ => panic!("Expected MintApiAuthority::Devnet variant"), + } + + let cluster = Cluster::MainnetBeta; + let mint_api_authority = MintApiAuthority::from_cluster(&cluster); + + let expected_pubkey = + Pubkey::from_str("HnT5KVAywGgQDhmh6Usk4bxRg4RwKxCK4jmECyaDth5R").unwrap(); + + match mint_api_authority { + MintApiAuthority::Mainnet(pubkey) => { + assert_eq!(pubkey, expected_pubkey); + } + _ => panic!("Expected MintApiAuthority::Mainnet variant"), + } + + let cluster = Cluster::StakedMainnetBeta; + let mint_api_authority = MintApiAuthority::from_cluster(&cluster); + + let expected_pubkey = + Pubkey::from_str("HnT5KVAywGgQDhmh6Usk4bxRg4RwKxCK4jmECyaDth5R").unwrap(); + + match mint_api_authority { + MintApiAuthority::Mainnet(pubkey) => { + assert_eq!(pubkey, expected_pubkey); + } + _ => panic!("Expected MintApiAuthority::Mainnet variant"), + } + } +} \ No newline at end of file diff --git a/tests/utils/test_collection_authority.rs b/tests/utils/test_collection_authority.rs new file mode 100644 index 0000000..ffb4741 --- /dev/null +++ b/tests/utils/test_collection_authority.rs @@ -0,0 +1,68 @@ +#[cfg(test)] +mod tests { + use helius::utils::collection_authority::*; + use mpl_token_metadata::ID; + use solana_program::{instruction::AccountMeta, pubkey::Pubkey}; + use solana_sdk::signature::{Keypair, Signer}; + + #[test] + fn test_get_collection_authority_record() { + let collection_mint = Pubkey::new_unique(); + let collection_authority = Pubkey::new_unique(); + + let result = get_collection_authority_record(&collection_mint, &collection_authority); + + let (expected_pubkey, _bump_seed) = Pubkey::find_program_address( + &[ + b"metadata", + ID.as_ref(), + &collection_mint.to_bytes(), + b"collection_authority", + &collection_authority.to_bytes(), + ], + &ID, + ); + + assert_eq!(result, expected_pubkey); + } + + #[test] + fn test_get_collection_metadata_account() { + let collection_mint = Pubkey::new_unique(); + + let result = get_collection_metadata_account(&collection_mint); + + let (expected_pubkey, _bump_seed) = Pubkey::find_program_address( + &[b"metadata", ID.as_ref(), &collection_mint.to_bytes()], + &ID, + ); + + assert_eq!(result, expected_pubkey); + } + + #[test] + fn test_get_revoke_collection_authority_instruction() { + let collection_mint = Pubkey::new_unique(); + let collection_authority = Pubkey::new_unique(); + let revoke_authority_keypair = Keypair::new(); + + let instruction = revoke_collection_authority_instruction( + collection_mint, + collection_authority, + &revoke_authority_keypair, + ); + + assert_eq!(instruction.program_id, ID); + + let expected_accounts = vec![ + AccountMeta::new(get_collection_authority_record(&collection_mint, &collection_authority), false), + AccountMeta::new(collection_authority, false), + AccountMeta::new(revoke_authority_keypair.pubkey(), true), + AccountMeta::new_readonly(get_collection_metadata_account(&collection_mint), false), + AccountMeta::new_readonly(collection_mint, false), + ]; + + assert_eq!(instruction.accounts, expected_accounts); + + } +} \ No newline at end of file From a2ab14d2bce2848173db9bfd418a0f49c4e4bd16 Mon Sep 17 00:00:00 2001 From: Anam Ansari Date: Sat, 12 Oct 2024 01:32:32 +0100 Subject: [PATCH 23/48] fix: replace getSignatureStatus --- src/optimized_transaction.rs | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/src/optimized_transaction.rs b/src/optimized_transaction.rs index 9b867ec..1bb9214 100644 --- a/src/optimized_transaction.rs +++ b/src/optimized_transaction.rs @@ -104,16 +104,25 @@ impl Helius { }); } - match self + let status = self .connection() - .get_signature_status_with_commitment(&txt_sig, commitment_config) - { - Ok(Some(Ok(()))) => return Ok(txt_sig), - Ok(Some(Err(err))) => return Err(HeliusError::TransactionError(err)), - Ok(None) => { + .get_signature_statuses(&[txt_sig])?; + + match status.value[0].clone() { + Some(status) => { + if status.err.is_none() + && (status.confirmation_status == Some(TransactionConfirmationStatus::Confirmed) + || status.confirmation_status == Some(TransactionConfirmationStatus::Finalized)) + { + return Ok(txt_sig); + } + if status.err.is_some() { + return Err(HeliusError::TransactionError(status.err.unwrap())); + } + } + None => { sleep(interval).await; } - Err(err) => return Err(HeliusError::ClientError(err)), } } } From a0c7fb10fb3ec651f9118534f69ef9048ec9e098 Mon Sep 17 00:00:00 2001 From: Anam Ansari Date: Sat, 12 Oct 2024 23:21:04 +0100 Subject: [PATCH 24/48] chore: import statement --- src/optimized_transaction.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/optimized_transaction.rs b/src/optimized_transaction.rs index 1bb9214..5d9eb8f 100644 --- a/src/optimized_transaction.rs +++ b/src/optimized_transaction.rs @@ -22,6 +22,7 @@ use solana_sdk::{ signature::{Signature, Signer}, transaction::{Transaction, VersionedTransaction}, }; +use solana_transaction_status::TransactionConfirmationStatus; use std::time::{Duration, Instant}; use tokio::time::sleep; @@ -104,9 +105,7 @@ impl Helius { }); } - let status = self - .connection() - .get_signature_statuses(&[txt_sig])?; + let status = self.connection().get_signature_statuses(&[txt_sig])?; match status.value[0].clone() { Some(status) => { From 9acf3baf1874aa0b4e1d5164ecbc769e2df28d03 Mon Sep 17 00:00:00 2001 From: Dmytro Medynskyi Date: Tue, 15 Oct 2024 18:52:46 +0200 Subject: [PATCH 25/48] add revoke_collection_authority example usage --- Cargo.toml | 1 + examples/revoke_collection_authority.rs | 155 ++++++++++++++++++++++++ src/client.rs | 1 - src/config.rs | 1 + src/mint_api.rs | 20 ++- tests/tests.rs | 1 + 6 files changed, 174 insertions(+), 5 deletions(-) create mode 100644 examples/revoke_collection_authority.rs diff --git a/Cargo.toml b/Cargo.toml index cb78b7f..f5c2db1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,6 +36,7 @@ tokio = { version = "1.37.0", features = ["macros", "rt-multi-thread", "net"] } tokio-stream = "0.1.15" tokio-tungstenite = { version = "0.21.0", features = ["native-tls", "handshake"] } url = "2.5.0" +spl-token = { version = "3.5.0", features = ["no-entrypoint"] } [dev-dependencies] mockito = "1.4.0" diff --git a/examples/revoke_collection_authority.rs b/examples/revoke_collection_authority.rs new file mode 100644 index 0000000..4226395 --- /dev/null +++ b/examples/revoke_collection_authority.rs @@ -0,0 +1,155 @@ +use helius::error::Result; +use helius::types::Cluster; +use helius::utils::collection_authority::{get_collection_authority_record, get_collection_metadata_account}; +use helius::Helius; +use mpl_token_metadata::instructions::CreateMetadataAccountV3InstructionArgs; +use mpl_token_metadata::instructions::{ApproveCollectionAuthority, CreateMetadataAccountV3}; +use mpl_token_metadata::types::DataV2; +use solana_program::system_instruction::create_account; +use solana_program::system_program; +use solana_sdk::{ + signature::{Keypair, Signer}, + transaction::Transaction, +}; +use spl_token::solana_program::program_pack::Pack; +use spl_token::{instruction::initialize_mint, state::Mint}; + +#[tokio::main] +async fn main() -> Result<()> { + let payer = Keypair::from_base58_string(""); + let api_key = ""; + let cluster = Cluster::MainnetBeta; + let helius = Helius::new_with_async_solana(api_key, cluster)?; + // Get the async Solana RPC client from Helius + let rpc_client = helius.async_connection()?; + + // Create a new SPL Token mint (collection mint) + let collection_mint_keypair = Keypair::new(); + // Calculate rent-exempt amount for the mint account + let rent = rpc_client + .get_minimum_balance_for_rent_exemption(Mint::LEN) + .await + .expect("Failed to get rent exemption amount"); + // Create mint account instruction + let create_mint_account_ix = create_account( + &payer.pubkey(), + &collection_mint_keypair.pubkey(), + rent, + Mint::LEN as u64, + &spl_token::id(), + ); + let collection_authority_keypair = Keypair::new(); + // Initialize the mint instruction + let initialize_mint_ix = initialize_mint( + &spl_token::id(), + &collection_mint_keypair.pubkey(), + &collection_authority_keypair.pubkey(), + None, + 9, + ) + .expect("Failed to create initialize mint instruction"); + let recent_blockhash = rpc_client.get_latest_blockhash().await?; + let transaction = Transaction::new_signed_with_payer( + &[create_mint_account_ix, initialize_mint_ix], + Some(&payer.pubkey()), + &[&payer, &collection_mint_keypair], + recent_blockhash, + ); + rpc_client + .send_and_confirm_transaction(&transaction) + .await + .expect("Failed to create and initialize mint"); + println!("Collection mint created: {}", collection_mint_keypair.pubkey()); + + // Create Metadata account for the collection mint + let metadata_pubkey = get_collection_metadata_account(&collection_mint_keypair.pubkey()); + let create_metadata_accounts_ix = CreateMetadataAccountV3 { + metadata: metadata_pubkey, + mint: collection_mint_keypair.pubkey(), + mint_authority: collection_authority_keypair.pubkey(), + payer: payer.pubkey(), + update_authority: (collection_authority_keypair.pubkey(), true), + system_program: system_program::ID, + rent: None, + } + .instruction(CreateMetadataAccountV3InstructionArgs { + data: DataV2 { + name: "".to_string(), + symbol: "".to_string(), + uri: "".to_string(), + seller_fee_basis_points: 0, + creators: None, + collection: None, + uses: None, + }, + is_mutable: true, + collection_details: None, + }); + let recent_blockhash = rpc_client.get_latest_blockhash().await?; + // Send the transaction to create the metadata account + let transaction = Transaction::new_signed_with_payer( + &[create_metadata_accounts_ix], + Some(&payer.pubkey()), + &[&payer, &collection_authority_keypair], + recent_blockhash, + ); + rpc_client.send_and_confirm_transaction(&transaction).await?; + println!( + "Metadata account created for collection mint {:?}", + collection_mint_keypair.pubkey().to_string() + ); + + let delegated_authority_keypair = Keypair::new(); + let collection_authority_record = + get_collection_authority_record(&collection_mint_keypair.pubkey(), &delegated_authority_keypair.pubkey()); + // Delegate collection authority to delegated_authority_keypair + let approve_collection_authority_ix = ApproveCollectionAuthority { + collection_authority_record, + new_collection_authority: delegated_authority_keypair.pubkey(), + update_authority: collection_authority_keypair.pubkey(), + payer: payer.pubkey(), + metadata: metadata_pubkey, + mint: collection_mint_keypair.pubkey(), + system_program: system_program::ID, + rent: None, + } + .instruction(); + let mut transaction = Transaction::new_with_payer(&[approve_collection_authority_ix], Some(&payer.pubkey())); + let recent_blockhash = rpc_client + .get_latest_blockhash() + .await + .expect("Failed to get blockhash"); + transaction.sign(&[&payer, &collection_authority_keypair], recent_blockhash); + rpc_client + .send_and_confirm_transaction(&transaction) + .await + .expect("Failed to delegate collection authority"); + println!( + "Delegated collection authority to {}", + delegated_authority_keypair.pubkey() + ); + + // The record for delegated collection authority should exist in blockchain + let account = rpc_client.get_account(&collection_authority_record).await; + assert!(account.is_ok(), "Collection authority record account should exist"); + + // Revoke the collection authority using your method + let result = helius + .revoke_collection_authority( + collection_mint_keypair.pubkey(), + Some(delegated_authority_keypair.pubkey()), + &collection_authority_keypair, + Some(&payer), + ) + .await; + assert!(result.is_ok(), "Failed to revoke collection authority"); + println!("Revoke transaction signature: {}", result?); + + // Fetch the collection authority record account + let account = rpc_client.get_account(&collection_authority_record).await; + // The account should not exist if the authority was revoked + assert!(account.is_err(), "Collection authority record account should be closed"); + + println!("Delegated collection authority successfully revoked"); + Ok(()) +} diff --git a/src/client.rs b/src/client.rs index 012c3ec..aa8f1d1 100644 --- a/src/client.rs +++ b/src/client.rs @@ -79,7 +79,6 @@ impl Helius { let client: Client = Client::builder().build().map_err(HeliusError::ReqwestError)?; let url: String = format!("{}/?api-key={}", config.endpoints.rpc, config.api_key); let async_solana_client: Arc = Arc::new(AsyncSolanaRpcClient::new(url)); - Ok(Helius { config: config.clone(), client: client.clone(), diff --git a/src/config.rs b/src/config.rs index 3630d66..c6fa3aa 100644 --- a/src/config.rs +++ b/src/config.rs @@ -13,6 +13,7 @@ pub struct Config { /// The endpoints associated with the specified `cluster`. Note these endpoints are automatically determined based on the cluster to ensure requests /// are made to the correct cluster pub endpoints: HeliusEndpoints, + } impl Config { diff --git a/src/mint_api.rs b/src/mint_api.rs index 60a7c43..8e4b5b3 100644 --- a/src/mint_api.rs +++ b/src/mint_api.rs @@ -1,7 +1,9 @@ use solana_program::pubkey::Pubkey; use solana_sdk::signature::{Keypair, Signature}; +use solana_sdk::signer::Signer; +use solana_sdk::transaction::Transaction; use crate::error::Result; -use crate::types::{MintCompressedNftRequest, MintResponse, SmartTransactionConfig}; +use crate::types::{MintCompressedNftRequest, MintResponse}; use crate::Helius; use crate::utils::collection_authority::revoke_collection_authority_instruction; @@ -32,13 +34,23 @@ impl Helius { collection_mint: Pubkey, delegated_collection_authority: Option, revoke_authority_keypair: &Keypair, - payer_keypair: Option<&Keypair>, + payer_keypair: Option<&Keypair> ) -> Result { let collection_authority = delegated_collection_authority .unwrap_or(self.config().mint_api_authority().into()); let revoke_instruction = revoke_collection_authority_instruction(collection_mint, collection_authority, revoke_authority_keypair); let payer_keypair = payer_keypair.unwrap_or(revoke_authority_keypair); - let transaction_config = SmartTransactionConfig::new(vec![revoke_instruction], vec![payer_keypair]); - self.send_smart_transaction(transaction_config).await + let recent_blockhash = self.async_connection()? + .get_latest_blockhash() + .await?; + self.async_connection()? + .send_and_confirm_transaction( + &Transaction::new_signed_with_payer( + &vec![revoke_instruction], + Some(&payer_keypair.pubkey()), + &vec![&payer_keypair, &revoke_authority_keypair], + recent_blockhash)) + .await + .map_err(|e|e.into()) } } diff --git a/tests/tests.rs b/tests/tests.rs index fba8dbc..3fb5319 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -2,6 +2,7 @@ mod utils { mod test_deserialize_str_to_number; mod test_is_valid_solana_address; mod test_make_keypairs; + mod test_collection_authority; } mod rpc { From 4a22087fe39a69d472363e2a2606ca769a7680f2 Mon Sep 17 00:00:00 2001 From: Dmytro Medynskyi Date: Tue, 15 Oct 2024 19:14:44 +0200 Subject: [PATCH 26/48] apply cargo fmt --- src/client.rs | 4 ++- src/config.rs | 1 - src/mint_api.rs | 34 +++++++++++------------- src/types/enums.rs | 8 ++++-- src/utils/collection_authority.rs | 31 +++++++-------------- src/utils/mod.rs | 2 +- tests/test_mint_api.rs | 12 +++------ tests/tests.rs | 2 +- tests/utils/test_collection_authority.rs | 21 +++++++-------- 9 files changed, 50 insertions(+), 65 deletions(-) diff --git a/src/client.rs b/src/client.rs index aa8f1d1..da293ec 100644 --- a/src/client.rs +++ b/src/client.rs @@ -146,7 +146,9 @@ impl Helius { self.ws_client.clone() } - pub fn config(&self) -> Arc { self.config.clone() } + pub fn config(&self) -> Arc { + self.config.clone() + } } /// A wrapper around the asynchronous Solana RPC client that provides thread-safe access diff --git a/src/config.rs b/src/config.rs index c6fa3aa..3630d66 100644 --- a/src/config.rs +++ b/src/config.rs @@ -13,7 +13,6 @@ pub struct Config { /// The endpoints associated with the specified `cluster`. Note these endpoints are automatically determined based on the cluster to ensure requests /// are made to the correct cluster pub endpoints: HeliusEndpoints, - } impl Config { diff --git a/src/mint_api.rs b/src/mint_api.rs index 8e4b5b3..11c3df1 100644 --- a/src/mint_api.rs +++ b/src/mint_api.rs @@ -1,11 +1,11 @@ +use crate::error::Result; +use crate::types::{MintCompressedNftRequest, MintResponse}; +use crate::utils::collection_authority::revoke_collection_authority_instruction; +use crate::Helius; use solana_program::pubkey::Pubkey; use solana_sdk::signature::{Keypair, Signature}; use solana_sdk::signer::Signer; use solana_sdk::transaction::Transaction; -use crate::error::Result; -use crate::types::{MintCompressedNftRequest, MintResponse}; -use crate::Helius; -use crate::utils::collection_authority::revoke_collection_authority_instruction; impl Helius { /// The easiest way to mint a compressed NFT (cNFT) @@ -34,23 +34,21 @@ impl Helius { collection_mint: Pubkey, delegated_collection_authority: Option, revoke_authority_keypair: &Keypair, - payer_keypair: Option<&Keypair> + payer_keypair: Option<&Keypair>, ) -> Result { - let collection_authority = delegated_collection_authority - .unwrap_or(self.config().mint_api_authority().into()); - let revoke_instruction = revoke_collection_authority_instruction(collection_mint, collection_authority, revoke_authority_keypair); + let collection_authority = delegated_collection_authority.unwrap_or(self.config().mint_api_authority().into()); + let revoke_instruction = + revoke_collection_authority_instruction(collection_mint, collection_authority, revoke_authority_keypair); let payer_keypair = payer_keypair.unwrap_or(revoke_authority_keypair); - let recent_blockhash = self.async_connection()? - .get_latest_blockhash() - .await?; + let recent_blockhash = self.async_connection()?.get_latest_blockhash().await?; self.async_connection()? - .send_and_confirm_transaction( - &Transaction::new_signed_with_payer( - &vec![revoke_instruction], - Some(&payer_keypair.pubkey()), - &vec![&payer_keypair, &revoke_authority_keypair], - recent_blockhash)) + .send_and_confirm_transaction(&Transaction::new_signed_with_payer( + &vec![revoke_instruction], + Some(&payer_keypair.pubkey()), + &vec![&payer_keypair, &revoke_authority_keypair], + recent_blockhash, + )) .await - .map_err(|e|e.into()) + .map_err(|e| e.into()) } } diff --git a/src/types/enums.rs b/src/types/enums.rs index 4ad62b7..01a82ad 100644 --- a/src/types/enums.rs +++ b/src/types/enums.rs @@ -147,8 +147,12 @@ pub enum MintApiAuthority { impl MintApiAuthority { pub fn from_cluster(cluster: &Cluster) -> Self { match cluster { - Cluster::MainnetBeta | Cluster::StakedMainnetBeta => MintApiAuthority::Mainnet(Pubkey::from_str("HnT5KVAywGgQDhmh6Usk4bxRg4RwKxCK4jmECyaDth5R").unwrap()), - Cluster::Devnet => MintApiAuthority::Devnet(Pubkey::from_str("2LbAtCJSaHqTnP9M5QSjvAMXk79RNLusFspFN5Ew67TC").unwrap()), + Cluster::MainnetBeta | Cluster::StakedMainnetBeta => { + MintApiAuthority::Mainnet(Pubkey::from_str("HnT5KVAywGgQDhmh6Usk4bxRg4RwKxCK4jmECyaDth5R").unwrap()) + } + Cluster::Devnet => { + MintApiAuthority::Devnet(Pubkey::from_str("2LbAtCJSaHqTnP9M5QSjvAMXk79RNLusFspFN5Ew67TC").unwrap()) + } } } } diff --git a/src/utils/collection_authority.rs b/src/utils/collection_authority.rs index bbcddd7..ed12f36 100644 --- a/src/utils/collection_authority.rs +++ b/src/utils/collection_authority.rs @@ -1,43 +1,33 @@ -use solana_program::pubkey::Pubkey; -use mpl_token_metadata::ID; -use solana_sdk::signature::{Keypair, Signer}; use mpl_token_metadata::instructions::RevokeCollectionAuthority; +use mpl_token_metadata::ID; use solana_program::instruction::Instruction; +use solana_program::pubkey::Pubkey; +use solana_sdk::signature::{Keypair, Signer}; -pub fn get_collection_authority_record( - collection_mint: &Pubkey, - collection_authority: &Pubkey -) -> Pubkey { +pub fn get_collection_authority_record(collection_mint: &Pubkey, collection_authority: &Pubkey) -> Pubkey { Pubkey::find_program_address( &[ "metadata".as_bytes(), ID.as_ref(), &collection_mint.to_bytes(), "collection_authority".as_bytes(), - &collection_authority.to_bytes()], + &collection_authority.to_bytes(), + ], &ID, ) - .0 + .0 } pub fn get_collection_metadata_account(collection_mint: &Pubkey) -> Pubkey { - Pubkey::find_program_address( - &[ - "metadata".as_bytes(), - ID.as_ref(), - &collection_mint.to_bytes() - ], - &ID, - ) - .0 + Pubkey::find_program_address(&["metadata".as_bytes(), ID.as_ref(), &collection_mint.to_bytes()], &ID).0 } pub fn revoke_collection_authority_instruction( collection_mint: Pubkey, collection_authority: Pubkey, - revoke_authority_keypair: &Keypair + revoke_authority_keypair: &Keypair, ) -> Instruction { let collection_metadata = get_collection_metadata_account(&collection_mint); - let collection_authority_record= get_collection_authority_record(&collection_mint, &collection_authority); + let collection_authority_record = get_collection_authority_record(&collection_mint, &collection_authority); let revoke_instruction = RevokeCollectionAuthority { collection_authority_record, delegate_authority: collection_authority, @@ -47,4 +37,3 @@ pub fn revoke_collection_authority_instruction( }; revoke_instruction.instruction() } - diff --git a/src/utils/mod.rs b/src/utils/mod.rs index a91bc6c..ba64657 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -6,4 +6,4 @@ mod deserialize_str_to_number; mod is_valid_solana_address; mod make_keypairs; -pub mod collection_authority; \ No newline at end of file +pub mod collection_authority; diff --git a/tests/test_mint_api.rs b/tests/test_mint_api.rs index 8e2fc7a..3f1b345 100644 --- a/tests/test_mint_api.rs +++ b/tests/test_mint_api.rs @@ -160,7 +160,6 @@ async fn test_get_asset_proof_failure() { assert!(result.is_err(), "Expected an error but got success"); } - #[cfg(test)] mod tests { use super::*; @@ -191,8 +190,7 @@ mod tests { let cluster = Cluster::Devnet; let mint_api_authority = MintApiAuthority::from_cluster(&cluster); - let expected_pubkey = - Pubkey::from_str("2LbAtCJSaHqTnP9M5QSjvAMXk79RNLusFspFN5Ew67TC").unwrap(); + let expected_pubkey = Pubkey::from_str("2LbAtCJSaHqTnP9M5QSjvAMXk79RNLusFspFN5Ew67TC").unwrap(); match mint_api_authority { MintApiAuthority::Devnet(pubkey) => { @@ -204,8 +202,7 @@ mod tests { let cluster = Cluster::MainnetBeta; let mint_api_authority = MintApiAuthority::from_cluster(&cluster); - let expected_pubkey = - Pubkey::from_str("HnT5KVAywGgQDhmh6Usk4bxRg4RwKxCK4jmECyaDth5R").unwrap(); + let expected_pubkey = Pubkey::from_str("HnT5KVAywGgQDhmh6Usk4bxRg4RwKxCK4jmECyaDth5R").unwrap(); match mint_api_authority { MintApiAuthority::Mainnet(pubkey) => { @@ -217,8 +214,7 @@ mod tests { let cluster = Cluster::StakedMainnetBeta; let mint_api_authority = MintApiAuthority::from_cluster(&cluster); - let expected_pubkey = - Pubkey::from_str("HnT5KVAywGgQDhmh6Usk4bxRg4RwKxCK4jmECyaDth5R").unwrap(); + let expected_pubkey = Pubkey::from_str("HnT5KVAywGgQDhmh6Usk4bxRg4RwKxCK4jmECyaDth5R").unwrap(); match mint_api_authority { MintApiAuthority::Mainnet(pubkey) => { @@ -227,4 +223,4 @@ mod tests { _ => panic!("Expected MintApiAuthority::Mainnet variant"), } } -} \ No newline at end of file +} diff --git a/tests/tests.rs b/tests/tests.rs index 3fb5319..a5a2be8 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -1,8 +1,8 @@ mod utils { + mod test_collection_authority; mod test_deserialize_str_to_number; mod test_is_valid_solana_address; mod test_make_keypairs; - mod test_collection_authority; } mod rpc { diff --git a/tests/utils/test_collection_authority.rs b/tests/utils/test_collection_authority.rs index ffb4741..3c89f2a 100644 --- a/tests/utils/test_collection_authority.rs +++ b/tests/utils/test_collection_authority.rs @@ -32,10 +32,8 @@ mod tests { let result = get_collection_metadata_account(&collection_mint); - let (expected_pubkey, _bump_seed) = Pubkey::find_program_address( - &[b"metadata", ID.as_ref(), &collection_mint.to_bytes()], - &ID, - ); + let (expected_pubkey, _bump_seed) = + Pubkey::find_program_address(&[b"metadata", ID.as_ref(), &collection_mint.to_bytes()], &ID); assert_eq!(result, expected_pubkey); } @@ -46,16 +44,16 @@ mod tests { let collection_authority = Pubkey::new_unique(); let revoke_authority_keypair = Keypair::new(); - let instruction = revoke_collection_authority_instruction( - collection_mint, - collection_authority, - &revoke_authority_keypair, - ); + let instruction = + revoke_collection_authority_instruction(collection_mint, collection_authority, &revoke_authority_keypair); assert_eq!(instruction.program_id, ID); let expected_accounts = vec![ - AccountMeta::new(get_collection_authority_record(&collection_mint, &collection_authority), false), + AccountMeta::new( + get_collection_authority_record(&collection_mint, &collection_authority), + false, + ), AccountMeta::new(collection_authority, false), AccountMeta::new(revoke_authority_keypair.pubkey(), true), AccountMeta::new_readonly(get_collection_metadata_account(&collection_mint), false), @@ -63,6 +61,5 @@ mod tests { ]; assert_eq!(instruction.accounts, expected_accounts); - } -} \ No newline at end of file +} From 87a698665acf72a7ac60ed79a9875c21baf7d179 Mon Sep 17 00:00:00 2001 From: Dmytro Medynskyi Date: Wed, 16 Oct 2024 19:27:29 +0200 Subject: [PATCH 27/48] add delegate_collection_authority --- ...legate_and_revoke_collection_authority.rs} | 66 +++++++++---------- src/client.rs | 1 + src/mint_api.rs | 41 +++++++++++- src/utils/collection_authority.rs | 27 +++++++- src/utils/mod.rs | 1 - tests/utils/test_collection_authority.rs | 32 +++++++++ 6 files changed, 131 insertions(+), 37 deletions(-) rename examples/{revoke_collection_authority.rs => delegate_and_revoke_collection_authority.rs} (77%) diff --git a/examples/revoke_collection_authority.rs b/examples/delegate_and_revoke_collection_authority.rs similarity index 77% rename from examples/revoke_collection_authority.rs rename to examples/delegate_and_revoke_collection_authority.rs index 4226395..99e63dd 100644 --- a/examples/revoke_collection_authority.rs +++ b/examples/delegate_and_revoke_collection_authority.rs @@ -1,6 +1,8 @@ use helius::error::Result; use helius::types::Cluster; -use helius::utils::collection_authority::{get_collection_authority_record, get_collection_metadata_account}; +use helius::utils::collection_authority::{ + delegate_collection_authority_instruction, get_collection_authority_record, get_collection_metadata_account, +}; use helius::Helius; use mpl_token_metadata::instructions::CreateMetadataAccountV3InstructionArgs; use mpl_token_metadata::instructions::{ApproveCollectionAuthority, CreateMetadataAccountV3}; @@ -16,8 +18,8 @@ use spl_token::{instruction::initialize_mint, state::Mint}; #[tokio::main] async fn main() -> Result<()> { + let api_key = ""; // Replace with your Helius API key let payer = Keypair::from_base58_string(""); - let api_key = ""; let cluster = Cluster::MainnetBeta; let helius = Helius::new_with_async_solana(api_key, cluster)?; // Get the async Solana RPC client from Helius @@ -59,7 +61,10 @@ async fn main() -> Result<()> { .send_and_confirm_transaction(&transaction) .await .expect("Failed to create and initialize mint"); - println!("Collection mint created: {}", collection_mint_keypair.pubkey()); + println!( + "Collection mint created and initialized: {}", + collection_mint_keypair.pubkey() + ); // Create Metadata account for the collection mint let metadata_pubkey = get_collection_metadata_account(&collection_mint_keypair.pubkey()); @@ -94,42 +99,31 @@ async fn main() -> Result<()> { recent_blockhash, ); rpc_client.send_and_confirm_transaction(&transaction).await?; - println!( - "Metadata account created for collection mint {:?}", - collection_mint_keypair.pubkey().to_string() - ); + println!("Metadata account created: {}", metadata_pubkey.to_string()); let delegated_authority_keypair = Keypair::new(); - let collection_authority_record = - get_collection_authority_record(&collection_mint_keypair.pubkey(), &delegated_authority_keypair.pubkey()); - // Delegate collection authority to delegated_authority_keypair - let approve_collection_authority_ix = ApproveCollectionAuthority { - collection_authority_record, - new_collection_authority: delegated_authority_keypair.pubkey(), - update_authority: collection_authority_keypair.pubkey(), - payer: payer.pubkey(), - metadata: metadata_pubkey, - mint: collection_mint_keypair.pubkey(), - system_program: system_program::ID, - rent: None, - } - .instruction(); - let mut transaction = Transaction::new_with_payer(&[approve_collection_authority_ix], Some(&payer.pubkey())); - let recent_blockhash = rpc_client - .get_latest_blockhash() - .await - .expect("Failed to get blockhash"); - transaction.sign(&[&payer, &collection_authority_keypair], recent_blockhash); - rpc_client - .send_and_confirm_transaction(&transaction) - .await - .expect("Failed to delegate collection authority"); - println!( - "Delegated collection authority to {}", + let result = helius + .delegate_collection_authority( + collection_mint_keypair.pubkey(), + delegated_authority_keypair.pubkey(), + &collection_authority_keypair, + Some(&payer), + ) + .await; + assert!( + result.is_ok(), + "Failed to delegate collection authority to {}", delegated_authority_keypair.pubkey() ); + println!( + "Delegate collection authority to {} transaction signature: {}", + delegated_authority_keypair.pubkey(), + result? + ); // The record for delegated collection authority should exist in blockchain + let collection_authority_record = + get_collection_authority_record(&collection_mint_keypair.pubkey(), &delegated_authority_keypair.pubkey()); let account = rpc_client.get_account(&collection_authority_record).await; assert!(account.is_ok(), "Collection authority record account should exist"); @@ -143,7 +137,11 @@ async fn main() -> Result<()> { ) .await; assert!(result.is_ok(), "Failed to revoke collection authority"); - println!("Revoke transaction signature: {}", result?); + println!( + "Revoke collection authority from {} transaction signature: {}", + delegated_authority_keypair.pubkey(), + result? + ); // Fetch the collection authority record account let account = rpc_client.get_account(&collection_authority_record).await; diff --git a/src/client.rs b/src/client.rs index da293ec..8d4ce9b 100644 --- a/src/client.rs +++ b/src/client.rs @@ -79,6 +79,7 @@ impl Helius { let client: Client = Client::builder().build().map_err(HeliusError::ReqwestError)?; let url: String = format!("{}/?api-key={}", config.endpoints.rpc, config.api_key); let async_solana_client: Arc = Arc::new(AsyncSolanaRpcClient::new(url)); + Ok(Helius { config: config.clone(), client: client.clone(), diff --git a/src/mint_api.rs b/src/mint_api.rs index 11c3df1..ffca3fe 100644 --- a/src/mint_api.rs +++ b/src/mint_api.rs @@ -1,6 +1,8 @@ use crate::error::Result; use crate::types::{MintCompressedNftRequest, MintResponse}; -use crate::utils::collection_authority::revoke_collection_authority_instruction; +use crate::utils::collection_authority::{ + delegate_collection_authority_instruction, revoke_collection_authority_instruction, +}; use crate::Helius; use solana_program::pubkey::Pubkey; use solana_sdk::signature::{Keypair, Signature}; @@ -19,6 +21,43 @@ impl Helius { self.rpc_client.post_rpc_request("mintCompressedNft", request).await } + /// Delegates collection authority to a new authority for a given collection mint. + /// + /// # Arguments + /// * `collection_mint` - The public key of the collection mint. + /// * `new_collection_authority` - The public key of the new authority to delegate to. + /// * `update_authority_keypair` - The keypair of the current update authority who is delegating the authority. + /// * `payer_keypair` - Optional keypair to pay for the transaction fees. If `None`, `update_authority_keypair` is used as the payer. + /// + /// # Returns + /// A `Result` containing the transaction `Signature` if successful. + pub async fn delegate_collection_authority( + &self, + collection_mint: Pubkey, + new_collection_authority: Pubkey, + update_authority_keypair: &Keypair, + payer_keypair: Option<&Keypair>, + ) -> Result { + let payer_keypair = payer_keypair.unwrap_or(update_authority_keypair); + let delegate_instruction = delegate_collection_authority_instruction( + collection_mint, + new_collection_authority, + update_authority_keypair, + payer_keypair.pubkey(), + ); + let recent_blockhash = self.async_connection()?.get_latest_blockhash().await?; + let transaction = Transaction::new_signed_with_payer( + &[delegate_instruction], + Some(&payer_keypair.pubkey()), + &[payer_keypair, update_authority_keypair], + recent_blockhash, + ); + self.async_connection()? + .send_and_confirm_transaction(&transaction) + .await + .map_err(|e| e.into()) + } + /// Revokes a delegated collection authority for a given collection mint. /// /// # Arguments diff --git a/src/utils/collection_authority.rs b/src/utils/collection_authority.rs index ed12f36..8d29291 100644 --- a/src/utils/collection_authority.rs +++ b/src/utils/collection_authority.rs @@ -1,4 +1,4 @@ -use mpl_token_metadata::instructions::RevokeCollectionAuthority; +use mpl_token_metadata::instructions::{ApproveCollectionAuthority, RevokeCollectionAuthority}; use mpl_token_metadata::ID; use solana_program::instruction::Instruction; use solana_program::pubkey::Pubkey; @@ -28,6 +28,7 @@ pub fn revoke_collection_authority_instruction( ) -> Instruction { let collection_metadata = get_collection_metadata_account(&collection_mint); let collection_authority_record = get_collection_authority_record(&collection_mint, &collection_authority); + let revoke_instruction = RevokeCollectionAuthority { collection_authority_record, delegate_authority: collection_authority, @@ -35,5 +36,29 @@ pub fn revoke_collection_authority_instruction( metadata: collection_metadata, mint: collection_mint, }; + revoke_instruction.instruction() } + +pub fn delegate_collection_authority_instruction( + collection_mint: Pubkey, + new_collection_authority: Pubkey, + update_authority_keypair: &Keypair, + payer_pubkey: Pubkey, +) -> Instruction { + let collection_metadata = get_collection_metadata_account(&collection_mint); + let collection_authority_record = get_collection_authority_record(&collection_mint, &new_collection_authority); + + let approve_instruction = ApproveCollectionAuthority { + collection_authority_record, + new_collection_authority, + update_authority: update_authority_keypair.pubkey(), + payer: payer_pubkey, + metadata: collection_metadata, + mint: collection_mint, + system_program: solana_program::system_program::ID, + rent: None, + }; + + approve_instruction.instruction() +} diff --git a/src/utils/mod.rs b/src/utils/mod.rs index ba64657..26b9c3d 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -5,5 +5,4 @@ pub use self::make_keypairs::make_keypairs; mod deserialize_str_to_number; mod is_valid_solana_address; mod make_keypairs; - pub mod collection_authority; diff --git a/tests/utils/test_collection_authority.rs b/tests/utils/test_collection_authority.rs index 3c89f2a..97f9958 100644 --- a/tests/utils/test_collection_authority.rs +++ b/tests/utils/test_collection_authority.rs @@ -38,6 +38,38 @@ mod tests { assert_eq!(result, expected_pubkey); } + #[test] + fn test_get_delegate_collection_authority_instruction() { + let collection_mint = Pubkey::new_unique(); + let new_collection_authority = Pubkey::new_unique(); + let update_authority_keypair = Keypair::new(); + let payer_pubkey = Pubkey::new_unique(); + + let instruction = delegate_collection_authority_instruction( + collection_mint, + new_collection_authority, + &update_authority_keypair, + payer_pubkey, + ); + + assert_eq!(instruction.program_id, ID); + + let collection_metadata = get_collection_metadata_account(&collection_mint); + let collection_authority_record = get_collection_authority_record(&collection_mint, &new_collection_authority); + + let expected_accounts = vec![ + AccountMeta::new(collection_authority_record, false), + AccountMeta::new_readonly(new_collection_authority, false), + AccountMeta::new(update_authority_keypair.pubkey(), true), + AccountMeta::new(payer_pubkey, true), + AccountMeta::new_readonly(collection_metadata, false), + AccountMeta::new_readonly(collection_mint, false), + AccountMeta::new_readonly(solana_program::system_program::ID, false), + ]; + + assert_eq!(instruction.accounts, expected_accounts); + } + #[test] fn test_get_revoke_collection_authority_instruction() { let collection_mint = Pubkey::new_unique(); From cee8ec0a53f44e9d792e187d99c87b595927e117 Mon Sep 17 00:00:00 2001 From: Dmytro Medynskyi Date: Thu, 17 Oct 2024 00:30:43 +0200 Subject: [PATCH 28/48] remove unused variable --- src/optimized_transaction.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/optimized_transaction.rs b/src/optimized_transaction.rs index 5d9eb8f..23233eb 100644 --- a/src/optimized_transaction.rs +++ b/src/optimized_transaction.rs @@ -95,8 +95,6 @@ impl Helius { let interval: Duration = Duration::from_secs(5); let start: Instant = Instant::now(); - let commitment_config: CommitmentConfig = CommitmentConfig::confirmed(); - loop { if start.elapsed() >= timeout { return Err(HeliusError::Timeout { From 4cad98537db12352d9a530eb484903425b03f1ee Mon Sep 17 00:00:00 2001 From: Dmytro Medynskyi Date: Thu, 17 Oct 2024 00:30:55 +0200 Subject: [PATCH 29/48] cargo fix --- examples/delegate_and_revoke_collection_authority.rs | 4 ++-- src/utils/mod.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/delegate_and_revoke_collection_authority.rs b/examples/delegate_and_revoke_collection_authority.rs index 99e63dd..2491037 100644 --- a/examples/delegate_and_revoke_collection_authority.rs +++ b/examples/delegate_and_revoke_collection_authority.rs @@ -1,11 +1,11 @@ use helius::error::Result; use helius::types::Cluster; use helius::utils::collection_authority::{ - delegate_collection_authority_instruction, get_collection_authority_record, get_collection_metadata_account, + get_collection_authority_record, get_collection_metadata_account, }; use helius::Helius; use mpl_token_metadata::instructions::CreateMetadataAccountV3InstructionArgs; -use mpl_token_metadata::instructions::{ApproveCollectionAuthority, CreateMetadataAccountV3}; +use mpl_token_metadata::instructions::CreateMetadataAccountV3; use mpl_token_metadata::types::DataV2; use solana_program::system_instruction::create_account; use solana_program::system_program; diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 26b9c3d..0c8a29b 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -2,7 +2,7 @@ pub use self::deserialize_str_to_number::deserialize_str_to_number; pub use self::is_valid_solana_address::is_valid_solana_address; pub use self::make_keypairs::make_keypairs; +pub mod collection_authority; mod deserialize_str_to_number; mod is_valid_solana_address; mod make_keypairs; -pub mod collection_authority; From 1231f6c9f3f99953930927e190f9d00b9ef88186 Mon Sep 17 00:00:00 2001 From: Richard Strnad Date: Sun, 20 Oct 2024 09:49:32 +0200 Subject: [PATCH 30/48] feat: add slot to transaction notification --- src/types/enhanced_websocket.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/types/enhanced_websocket.rs b/src/types/enhanced_websocket.rs index 2935a6d..2195241 100644 --- a/src/types/enhanced_websocket.rs +++ b/src/types/enhanced_websocket.rs @@ -96,4 +96,5 @@ pub struct RpcTransactionsConfig { pub struct TransactionNotification { pub transaction: EncodedTransactionWithStatusMeta, pub signature: String, + pub slot: u64, } From e485eb23b9a9ca5a9cd8f257f095b8e9d963d5ee Mon Sep 17 00:00:00 2001 From: Richard Strnad Date: Sun, 20 Oct 2024 09:54:05 +0200 Subject: [PATCH 31/48] fix: remove debug print --- src/websocket.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/websocket.rs b/src/websocket.rs index cd6212d..c217f4b 100644 --- a/src/websocket.rs +++ b/src/websocket.rs @@ -218,7 +218,6 @@ impl EnhancedWebsocket { request_id += 1; let method = format!("{operation}Subscribe"); let body = json!({"jsonrpc":"2.0","id":request_id,"method":method,"params":params}); - println!("subscription: {:#}", body); ws.send(Message::Text(body.to_string())).await?; requests_subscribe.insert(request_id, (operation, response_sender)); }, From 506d0a086a221908a7bea3c4c0f511f1c86069a8 Mon Sep 17 00:00:00 2001 From: Dmytro Medynskyi Date: Sun, 20 Oct 2024 13:57:14 +0200 Subject: [PATCH 32/48] cargo fmt --- examples/delegate_and_revoke_collection_authority.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/examples/delegate_and_revoke_collection_authority.rs b/examples/delegate_and_revoke_collection_authority.rs index 2491037..4207cbd 100644 --- a/examples/delegate_and_revoke_collection_authority.rs +++ b/examples/delegate_and_revoke_collection_authority.rs @@ -1,11 +1,9 @@ use helius::error::Result; use helius::types::Cluster; -use helius::utils::collection_authority::{ - get_collection_authority_record, get_collection_metadata_account, -}; +use helius::utils::collection_authority::{get_collection_authority_record, get_collection_metadata_account}; use helius::Helius; -use mpl_token_metadata::instructions::CreateMetadataAccountV3InstructionArgs; use mpl_token_metadata::instructions::CreateMetadataAccountV3; +use mpl_token_metadata::instructions::CreateMetadataAccountV3InstructionArgs; use mpl_token_metadata::types::DataV2; use solana_program::system_instruction::create_account; use solana_program::system_program; From 2f0cadee98e0aa3b3b05f1f1ac2077282dc20e22 Mon Sep 17 00:00:00 2001 From: 4lve Date: Tue, 22 Oct 2024 14:58:02 +0200 Subject: [PATCH 33/48] Bump solana to 2.0.x --- Cargo.toml | 16 ++++++++-------- src/types/types.rs | 4 ++-- src/websocket.rs | 2 -- tests/test_factory.rs | 6 +++--- 4 files changed, 13 insertions(+), 15 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 9a2f72d..402e64d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "helius" -version = "0.2.1" +version = "0.2.2" edition = "2021" description = "An asynchronous Helius Rust SDK for building the future of Solana" keywords = ["helius", "solana", "asynchronous-sdk", "das", "cryptocurrency"] @@ -24,16 +24,16 @@ semver = "1.0.23" serde = "1.0.198" serde-enum-str = "0.4.0" serde_json = "1.0.116" -solana-account-decoder = "=1.17.0" -solana-client = "=1.17.0" -solana-program = "=1.17.0" -solana-rpc-client-api = "=1.17.0" -solana-sdk = "=1.17.0" -solana-transaction-status = "=1.17.0" +solana-account-decoder = "2.0" +solana-client = "2.0" +solana-program = "2.0" +solana-rpc-client-api = "2.0" +solana-sdk = "2.0" +solana-transaction-status = "2.0" thiserror = "1.0.58" tokio = { version = "1.37.0", features = ["macros", "rt-multi-thread", "net"] } tokio-stream = "0.1.15" -tokio-tungstenite = { version = "0.21.0", features = ["native-tls", "handshake"] } +tokio-tungstenite = { version = "0.24.0", features = ["native-tls", "handshake"] } url = "2.5.0" [dev-dependencies] diff --git a/src/types/types.rs b/src/types/types.rs index 062ef13..e63af21 100644 --- a/src/types/types.rs +++ b/src/types/types.rs @@ -34,11 +34,11 @@ impl HeliusEndpoints { rpc: "https://devnet.helius-rpc.com/".to_string(), }, Cluster::MainnetBeta => HeliusEndpoints { - api: "https://api.helius-rpc.com/".to_string(), + api: "https://api-mainnet.helius-rpc.com/".to_string(), rpc: "https://mainnet.helius-rpc.com/".to_string(), }, Cluster::StakedMainnetBeta => HeliusEndpoints { - api: "https://api.helius-rpc.com/".to_string(), + api: "https://api-mainnet.helius-rpc.com/".to_string(), rpc: "https://staked.helius-rpc.com/".to_string(), }, } diff --git a/src/websocket.rs b/src/websocket.rs index c217f4b..86558b6 100644 --- a/src/websocket.rs +++ b/src/websocket.rs @@ -28,7 +28,6 @@ use tokio_tungstenite::{ }, MaybeTlsStream, WebSocketStream, }; -use url::Url; pub const ENHANCED_WEBSOCKET_URL: &str = "wss://atlas-mainnet.helius-rpc.com?api-key="; const DEFAULT_PING_DURATION_SECONDS: u64 = 10; @@ -54,7 +53,6 @@ pub struct EnhancedWebsocket { impl EnhancedWebsocket { /// Expects enhanced websocket endpoint: wss://atlas-mainnet.helius-rpc.com?api-key= pub async fn new(url: &str) -> Result { - let url = Url::parse(url)?; let (ws, _response) = connect_async(url).await.map_err(HeliusError::Tungstenite)?; let (subscribe_sender, subscribe_receiver) = mpsc::unbounded_channel(); diff --git a/tests/test_factory.rs b/tests/test_factory.rs index 62a3ec0..55826eb 100644 --- a/tests/test_factory.rs +++ b/tests/test_factory.rs @@ -17,7 +17,7 @@ fn test_factory_create_mainnet_instance() { let helius: Helius = factory.create(Cluster::MainnetBeta).unwrap(); assert_eq!(helius.config.api_key, "valid_api_key"); - assert_eq!(helius.config.endpoints.api, "https://api.helius-rpc.com/"); + assert_eq!(helius.config.endpoints.api, "https://api-mainnet.helius-rpc.com/"); assert_eq!(helius.config.endpoints.rpc, "https://mainnet.helius-rpc.com/"); } @@ -27,7 +27,7 @@ fn test_factory_create_staked_mainnet_instance() { let helius: Helius = factory.create(Cluster::StakedMainnetBeta).unwrap(); assert_eq!(helius.config.api_key, "valid_api_key"); - assert_eq!(helius.config.endpoints.api, "https://api.helius-rpc.com/"); + assert_eq!(helius.config.endpoints.api, "https://api-mainnet.helius-rpc.com/"); assert_eq!(helius.config.endpoints.rpc, "https://staked.helius-rpc.com/"); } @@ -40,6 +40,6 @@ fn test_factory_create_with_reqwest() { .unwrap(); assert_eq!(helius.config.api_key, "valid_api_key"); - assert_eq!(helius.config.endpoints.api, "https://api.helius-rpc.com/"); + assert_eq!(helius.config.endpoints.api, "https://api-mainnet.helius-rpc.com/"); assert_eq!(helius.config.endpoints.rpc, "https://mainnet.helius-rpc.com/"); } From 853669cb37b58bf41345f453a1f28d2e79fc8630 Mon Sep 17 00:00:00 2001 From: Dmytro Medynskyi Date: Tue, 22 Oct 2024 17:31:47 +0200 Subject: [PATCH 34/48] remove comments --- examples/delegate_and_revoke_collection_authority.rs | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/examples/delegate_and_revoke_collection_authority.rs b/examples/delegate_and_revoke_collection_authority.rs index 4207cbd..1fbcc24 100644 --- a/examples/delegate_and_revoke_collection_authority.rs +++ b/examples/delegate_and_revoke_collection_authority.rs @@ -16,21 +16,17 @@ use spl_token::{instruction::initialize_mint, state::Mint}; #[tokio::main] async fn main() -> Result<()> { - let api_key = ""; // Replace with your Helius API key + let api_key = ""; let payer = Keypair::from_base58_string(""); let cluster = Cluster::MainnetBeta; let helius = Helius::new_with_async_solana(api_key, cluster)?; - // Get the async Solana RPC client from Helius let rpc_client = helius.async_connection()?; - // Create a new SPL Token mint (collection mint) let collection_mint_keypair = Keypair::new(); - // Calculate rent-exempt amount for the mint account let rent = rpc_client .get_minimum_balance_for_rent_exemption(Mint::LEN) .await .expect("Failed to get rent exemption amount"); - // Create mint account instruction let create_mint_account_ix = create_account( &payer.pubkey(), &collection_mint_keypair.pubkey(), @@ -64,7 +60,6 @@ async fn main() -> Result<()> { collection_mint_keypair.pubkey() ); - // Create Metadata account for the collection mint let metadata_pubkey = get_collection_metadata_account(&collection_mint_keypair.pubkey()); let create_metadata_accounts_ix = CreateMetadataAccountV3 { metadata: metadata_pubkey, @@ -119,13 +114,11 @@ async fn main() -> Result<()> { result? ); - // The record for delegated collection authority should exist in blockchain let collection_authority_record = get_collection_authority_record(&collection_mint_keypair.pubkey(), &delegated_authority_keypair.pubkey()); let account = rpc_client.get_account(&collection_authority_record).await; assert!(account.is_ok(), "Collection authority record account should exist"); - // Revoke the collection authority using your method let result = helius .revoke_collection_authority( collection_mint_keypair.pubkey(), @@ -141,9 +134,7 @@ async fn main() -> Result<()> { result? ); - // Fetch the collection authority record account let account = rpc_client.get_account(&collection_authority_record).await; - // The account should not exist if the authority was revoked assert!(account.is_err(), "Collection authority record account should be closed"); println!("Delegated collection authority successfully revoked"); From 626a7987110c2312d1388de5d1c09cdc81732d5d Mon Sep 17 00:00:00 2001 From: Dmytro Medynskyi Date: Tue, 22 Oct 2024 17:38:20 +0200 Subject: [PATCH 35/48] remove comments --- examples/delegate_and_revoke_collection_authority.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/examples/delegate_and_revoke_collection_authority.rs b/examples/delegate_and_revoke_collection_authority.rs index 1fbcc24..0ab88a8 100644 --- a/examples/delegate_and_revoke_collection_authority.rs +++ b/examples/delegate_and_revoke_collection_authority.rs @@ -35,7 +35,6 @@ async fn main() -> Result<()> { &spl_token::id(), ); let collection_authority_keypair = Keypair::new(); - // Initialize the mint instruction let initialize_mint_ix = initialize_mint( &spl_token::id(), &collection_mint_keypair.pubkey(), @@ -84,7 +83,6 @@ async fn main() -> Result<()> { collection_details: None, }); let recent_blockhash = rpc_client.get_latest_blockhash().await?; - // Send the transaction to create the metadata account let transaction = Transaction::new_signed_with_payer( &[create_metadata_accounts_ix], Some(&payer.pubkey()), From b3e4376db738cce7d7e1c35343ec6f3a1882097f Mon Sep 17 00:00:00 2001 From: 4lve Date: Tue, 22 Oct 2024 23:00:15 +0200 Subject: [PATCH 36/48] Bump versions again --- Cargo.toml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 5be7811..40e5832 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,7 @@ bincode = "1.3.3" chrono = { version = "0.4.11", features = ["serde"] } futures = "0.3.30" futures-util = "0.3.30" -mpl-token-metadata = "4.1.2" +mpl-token-metadata = { version = "5.0.0-beta.0" } phf = { version = "0.11.2", features = ["macros"] } rand = "0.8.5" reqwest = { version = "0.11", features = ["json"], default-features = false } @@ -36,7 +36,7 @@ tokio = { version = "1.37.0", features = ["macros", "rt-multi-thread", "net"] } tokio-stream = "0.1.15" tokio-tungstenite = { version = "0.24.0", features = ["native-tls", "handshake"] } url = "2.5.0" -spl-token = { version = "3.5.0", features = ["no-entrypoint"] } +spl-token = { version = "6.0", features = ["no-entrypoint"] } [dev-dependencies] mockito = "1.4.0" @@ -44,4 +44,5 @@ mockito = "1.4.0" [features] default = ["native-tls"] native-tls = ["reqwest/native-tls"] -rustls = ["reqwest/rustls-tls"] \ No newline at end of file +rustls = ["reqwest/rustls-tls"] + From bc7c3090e156c8d799156ce172011dfedc5d4b99 Mon Sep 17 00:00:00 2001 From: Luca Spinazzola Date: Thu, 24 Oct 2024 19:45:17 -0400 Subject: [PATCH 37/48] update reqwest back to v12. patch curve25519-dalek patch curve25519-dalek with the same version solana uses --- Cargo.toml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 40e5832..c626daa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,7 +20,7 @@ futures-util = "0.3.30" mpl-token-metadata = { version = "5.0.0-beta.0" } phf = { version = "0.11.2", features = ["macros"] } rand = "0.8.5" -reqwest = { version = "0.11", features = ["json"], default-features = false } +reqwest = { version = "0.12.8", features = ["json"], default-features = false } semver = "1.0.23" serde = "1.0.198" serde-enum-str = "0.4.0" @@ -46,3 +46,8 @@ default = ["native-tls"] native-tls = ["reqwest/native-tls"] rustls = ["reqwest/rustls-tls"] +[patch.crates-io] +# https://github.com/solana-labs/solana/issues/26688#issuecomment-2136066879 +# For curve25519-dalek use the same revision Solana uses +# https://github.com/solana-labs/solana/blob/27eff8408b7223bb3c4ab70523f8a8dca3ca6645/Cargo.toml#L475-L563 +curve25519-dalek = { git = "https://github.com/solana-labs/curve25519-dalek.git", rev = "b500cdc2a920cd5bff9e2dd974d7b97349d61464" } From e66b0dc10c3842183254f7b7a82bf486a29ce5a3 Mon Sep 17 00:00:00 2001 From: Dmytro Medynskyi Date: Fri, 25 Oct 2024 18:37:55 +0200 Subject: [PATCH 38/48] feat: configurable timeout --- examples/send_smart_transaction_with_tip.rs | 1 + src/optimized_transaction.rs | 17 +++++++-------- src/types/types.rs | 23 +++++++++++++++++++-- 3 files changed, 29 insertions(+), 12 deletions(-) diff --git a/examples/send_smart_transaction_with_tip.rs b/examples/send_smart_transaction_with_tip.rs index 2f6fa76..12a9817 100644 --- a/examples/send_smart_transaction_with_tip.rs +++ b/examples/send_smart_transaction_with_tip.rs @@ -42,6 +42,7 @@ async fn main() { max_retries: None, min_context_slot: None, }, + timeout: Timeout::default(), }; // Send the optimized transaction with a 10k lamport tip using the New York region's API URL diff --git a/src/optimized_transaction.rs b/src/optimized_transaction.rs index 23233eb..5abc969 100644 --- a/src/optimized_transaction.rs +++ b/src/optimized_transaction.rs @@ -1,7 +1,7 @@ use crate::error::{HeliusError, Result}; use crate::types::{ CreateSmartTransactionConfig, CreateSmartTransactionSeedConfig, GetPriorityFeeEstimateOptions, - GetPriorityFeeEstimateRequest, GetPriorityFeeEstimateResponse, SmartTransaction, SmartTransactionConfig, + GetPriorityFeeEstimateRequest, GetPriorityFeeEstimateResponse, SmartTransaction, SmartTransactionConfig, Timeout, }; use crate::Helius; @@ -337,13 +337,7 @@ impl Helius { let (transaction, last_valid_block_height) = self.create_smart_transaction(&config.create_config).await?; // Common logic for sending transactions - let send_transaction_config: RpcSendTransactionConfig = RpcSendTransactionConfig { - skip_preflight: config.send_options.skip_preflight, - preflight_commitment: config.send_options.preflight_commitment, - encoding: config.send_options.encoding, - max_retries: config.send_options.max_retries, - min_context_slot: config.send_options.min_context_slot, - }; + let send_transaction_config = config.send_options.clone(); let send_result = |transaction: &Transaction| { self.connection() @@ -354,8 +348,8 @@ impl Helius { .send_transaction_with_config(transaction, send_transaction_config) }; - // Retry logic with a timeout of 60 seconds - let timeout: Duration = Duration::from_secs(60); + // Retry logic with a timeout + let timeout: Duration = config.timeout.into(); let start_time: Instant = Instant::now(); while Instant::now().duration_since(start_time) < timeout @@ -400,6 +394,7 @@ impl Helius { /// - `fee_payer_seed`: Optional seed bytes for generating the fee payer keypair. /// - `lookup_tables`: Optional address lookup tables for the transaction. /// * `send_options` - Optional `RpcSendTransactionConfig` for sending the transaction. + /// * `timeout` - Optional `Timeout` wait time for polling transaction confirmation. /// /// # Returns /// @@ -417,6 +412,7 @@ impl Helius { &self, create_config: CreateSmartTransactionSeedConfig, send_options: Option, + timeout: Option, ) -> Result { if create_config.signer_seeds.is_empty() { return Err(HeliusError::InvalidInput( @@ -452,6 +448,7 @@ impl Helius { let smart_transaction_config: SmartTransactionConfig<'_> = SmartTransactionConfig { create_config: create_smart_transaction_config, send_options: send_options.unwrap_or_default(), + timeout: timeout.unwrap_or_default(), }; self.send_smart_transaction(smart_transaction_config).await diff --git a/src/types/types.rs b/src/types/types.rs index e63af21..bf7180b 100644 --- a/src/types/types.rs +++ b/src/types/types.rs @@ -4,9 +4,9 @@ use super::{ TransactionStatus, TransactionType, UiTransactionEncoding, WebhookType, }; use crate::types::{DisplayOptions, GetAssetOptions}; -// use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; use serde_json::Value; +use std::time::Duration; use solana_client::rpc_config::RpcSendTransactionConfig; use solana_sdk::{address_lookup_table::AddressLookupTableAccount, instruction::Instruction, signature::Signer}; @@ -967,17 +967,36 @@ impl<'a> CreateSmartTransactionConfig<'a> { } } } +pub struct Timeout { + pub duration: Duration, +} + +impl Default for Timeout { + fn default() -> Self { + Self { + duration: Duration::from_secs(60), + } + } +} + +impl Into for Timeout { + fn into(self) -> Duration { + self.duration + } +} pub struct SmartTransactionConfig<'a> { pub create_config: CreateSmartTransactionConfig<'a>, pub send_options: RpcSendTransactionConfig, + pub timeout: Timeout, } impl<'a> SmartTransactionConfig<'a> { - pub fn new(instructions: Vec, signers: Vec<&'a dyn Signer>) -> Self { + pub fn new(instructions: Vec, signers: Vec<&'a dyn Signer>, timeout: Timeout) -> Self { Self { create_config: CreateSmartTransactionConfig::new(instructions, signers), send_options: RpcSendTransactionConfig::default(), + timeout, } } } From 543bce6eed671a47e584510fb375e169b9dc425d Mon Sep 17 00:00:00 2001 From: Eliascm17 Date: Fri, 25 Oct 2024 21:03:24 -0500 Subject: [PATCH 39/48] add send_and_confirm_transaction --- README.md | 1 + src/optimized_transaction.rs | 50 +++++++++++++++++++++++++----------- 2 files changed, 36 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index f9793a8..b2fda5f 100644 --- a/README.md +++ b/README.md @@ -137,6 +137,7 @@ Our SDK is designed to provide a seamless developer experience when building on - [`get_compute_units`](https://github.com/helius-labs/helius-rust-sdk/blob/a79a751e1a064125010bdb359068a366d635d005/src/optimized_transaction.rs#L29-L75) - Simulates a transaction to get the total compute units consumed - [`poll_transaction_confirmation`](https://github.com/helius-labs/helius-rust-sdk/blob/a79a751e1a064125010bdb359068a366d635d005/src/optimized_transaction.rs#L77-L112) - Polls a transaction to check whether it has been confirmed in 5 second intervals with a 15 second timeout - [`send_smart_transaction`](https://github.com/helius-labs/helius-rust-sdk/blob/705d66fb7d4004fc32c2a5f0d6ca4a1f2a7b175d/src/optimized_transaction.rs#L314-L374) - Builds and sends an optimized transaction, and handles its confirmation status +- [`send_and_confirm_transaction`] - Sends a transaction and handles its confirmation status with retry logic - [`send_smart_transaction_with_seeds`]() - Sends a smart transaction using seed bytes ### Jito Smart Transactions and Helper Methods diff --git a/src/optimized_transaction.rs b/src/optimized_transaction.rs index 23233eb..81d0c66 100644 --- a/src/optimized_transaction.rs +++ b/src/optimized_transaction.rs @@ -7,8 +7,11 @@ use crate::Helius; use bincode::{serialize, ErrorKind}; use reqwest::StatusCode; -use solana_client::rpc_config::{RpcSendTransactionConfig, RpcSimulateTransactionConfig}; -use solana_client::rpc_response::{Response, RpcSimulateTransactionResult}; +use solana_client::{ + rpc_client::SerializableTransaction, + rpc_config::{RpcSendTransactionConfig, RpcSimulateTransactionConfig}, + rpc_response::{Response, RpcSimulateTransactionResult}, +}; use solana_sdk::signature::{keypair_from_seed, Keypair}; use solana_sdk::{ address_lookup_table::AddressLookupTableAccount, @@ -345,26 +348,43 @@ impl Helius { min_context_slot: config.send_options.min_context_slot, }; - let send_result = |transaction: &Transaction| { - self.connection() - .send_transaction_with_config(transaction, send_transaction_config) - }; - let send_versioned_result = |transaction: &VersionedTransaction| { - self.connection() - .send_transaction_with_config(transaction, send_transaction_config) - }; + match transaction { + SmartTransaction::Legacy(tx) => { + self.send_and_confirm_transaction(&tx, send_transaction_config, last_valid_block_height) + .await + } + SmartTransaction::Versioned(tx) => { + self.send_and_confirm_transaction(&tx, send_transaction_config, last_valid_block_height) + .await + } + } + } + /// Sends a transaction and handles its confirmation status + /// + /// # Arguments + /// * `transaction` - The transaction to be sent, which implements `SerializableTransaction` + /// * `send_transaction_config` - Configuration options for sending the transaction + /// * `last_valid_block_hash` - The last block hash at which the transaction is valid + /// + /// # Returns + /// The transaction signature, if successful + pub async fn send_and_confirm_transaction( + &self, + transaction: &impl SerializableTransaction, + send_transaction_config: RpcSendTransactionConfig, + last_valid_block_hash: u64, + ) -> Result { // Retry logic with a timeout of 60 seconds let timeout: Duration = Duration::from_secs(60); let start_time: Instant = Instant::now(); while Instant::now().duration_since(start_time) < timeout - || self.connection().get_block_height()? <= last_valid_block_height + || self.connection().get_block_height()? <= last_valid_block_hash { - let result = match &transaction { - SmartTransaction::Legacy(tx) => send_result(tx), - SmartTransaction::Versioned(tx) => send_versioned_result(tx), - }; + let result = self + .connection() + .send_transaction_with_config(transaction, send_transaction_config); match result { Ok(signature) => { From ca5e15282065ab3d2a7d79d7041bbea109088e0c Mon Sep 17 00:00:00 2001 From: Eliascm17 Date: Thu, 31 Oct 2024 11:39:37 -0500 Subject: [PATCH 40/48] add optional timeout for send_and_confirm_transaction --- src/optimized_transaction.rs | 31 ++++++++++++++++++++----------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/src/optimized_transaction.rs b/src/optimized_transaction.rs index 7f6d4c8..030f563 100644 --- a/src/optimized_transaction.rs +++ b/src/optimized_transaction.rs @@ -339,17 +339,24 @@ impl Helius { pub async fn send_smart_transaction(&self, config: SmartTransactionConfig<'_>) -> Result { let (transaction, last_valid_block_height) = self.create_smart_transaction(&config.create_config).await?; - // Common logic for sending transactions - let send_transaction_config = config.send_options.clone(); - match transaction { SmartTransaction::Legacy(tx) => { - self.send_and_confirm_transaction(&tx, send_transaction_config, last_valid_block_height) - .await + self.send_and_confirm_transaction( + &tx, + config.send_options, + last_valid_block_height, + Some(config.timeout.into()), + ) + .await } SmartTransaction::Versioned(tx) => { - self.send_and_confirm_transaction(&tx, send_transaction_config, last_valid_block_height) - .await + self.send_and_confirm_transaction( + &tx, + config.send_options, + last_valid_block_height, + Some(config.timeout.into()), + ) + .await } } } @@ -359,7 +366,8 @@ impl Helius { /// # Arguments /// * `transaction` - The transaction to be sent, which implements `SerializableTransaction` /// * `send_transaction_config` - Configuration options for sending the transaction - /// * `last_valid_block_hash` - The last block hash at which the transaction is valid + /// * `last_valid_block_height` - The last block height at which the transaction is valid + /// * `timeout` - Optional duration for polling transaction confirmation, defaults to 60 seconds /// /// # Returns /// The transaction signature, if successful @@ -367,14 +375,15 @@ impl Helius { &self, transaction: &impl SerializableTransaction, send_transaction_config: RpcSendTransactionConfig, - last_valid_block_hash: u64, + last_valid_block_height: u64, + timeout: Option, ) -> Result { // Retry logic with a timeout - let timeout: Duration = config.timeout.into(); + let timeout: Duration = timeout.unwrap_or(Duration::from_secs(60)); let start_time: Instant = Instant::now(); while Instant::now().duration_since(start_time) < timeout - || self.connection().get_block_height()? <= last_valid_block_hash + || self.connection().get_block_height()? <= last_valid_block_height { let result = self .connection() From e9c07668244dd4de2530a6a70c521d66487ab5b3 Mon Sep 17 00:00:00 2001 From: Levi Cook Date: Sun, 3 Nov 2024 17:04:52 -0700 Subject: [PATCH 41/48] make InstructionError optional because it is not always there --- src/types/enhanced_transaction_types.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/types/enhanced_transaction_types.rs b/src/types/enhanced_transaction_types.rs index 98d4dc7..99c06bd 100644 --- a/src/types/enhanced_transaction_types.rs +++ b/src/types/enhanced_transaction_types.rs @@ -78,7 +78,7 @@ pub struct TokenTransfer { #[serde(rename_all = "camelCase")] pub struct TransactionError { #[serde(rename = "InstructionError")] - pub instruciton_error: serde_json::Value, + pub instruciton_error: Option, } #[derive(Serialize, Deserialize, Debug)] From c36cc890afdfcfb8213198d17b90ed5d9257099e Mon Sep 17 00:00:00 2001 From: Levi Cook Date: Mon, 4 Nov 2024 14:28:28 -0700 Subject: [PATCH 42/48] spelling: instruciton_error -> instruction_error --- src/types/enhanced_transaction_types.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/types/enhanced_transaction_types.rs b/src/types/enhanced_transaction_types.rs index 99c06bd..f349393 100644 --- a/src/types/enhanced_transaction_types.rs +++ b/src/types/enhanced_transaction_types.rs @@ -78,7 +78,7 @@ pub struct TokenTransfer { #[serde(rename_all = "camelCase")] pub struct TransactionError { #[serde(rename = "InstructionError")] - pub instruciton_error: Option, + pub instruction_error: Option, } #[derive(Serialize, Deserialize, Debug)] From 107532981e86bae871c169ac1617b3c3a157a626 Mon Sep 17 00:00:00 2001 From: Dmytro Medynskyi Date: Thu, 7 Nov 2024 18:38:40 +0100 Subject: [PATCH 43/48] feat: replace lifetime with Arc --- examples/send_smart_transaction_with_tip.rs | 3 +- src/jito.rs | 5 ++- src/optimized_transaction.rs | 46 +++++++++++---------- src/types/types.rs | 19 +++++---- 4 files changed, 39 insertions(+), 34 deletions(-) diff --git a/examples/send_smart_transaction_with_tip.rs b/examples/send_smart_transaction_with_tip.rs index 12a9817..9e3dc74 100644 --- a/examples/send_smart_transaction_with_tip.rs +++ b/examples/send_smart_transaction_with_tip.rs @@ -6,6 +6,7 @@ use solana_sdk::{ system_instruction::transfer, }; use std::str::FromStr; +use std::sync::Arc; use std::time::Duration; use tokio::time::sleep; @@ -28,7 +29,7 @@ async fn main() { let create_config: CreateSmartTransactionConfig = CreateSmartTransactionConfig { instructions, - signers: vec![&from_keypair], + signers: vec![Arc::new(from_keypair)], lookup_tables: None, fee_payer: None, }; diff --git a/src/jito.rs b/src/jito.rs index 1534f0c..af4828f 100644 --- a/src/jito.rs +++ b/src/jito.rs @@ -92,7 +92,7 @@ impl Helius { /// A `Result` containing the serialized transaction as a base58-encoded string and the last valid block height pub async fn create_smart_transaction_with_tip( &self, - mut config: CreateSmartTransactionConfig<'_>, + mut config: CreateSmartTransactionConfig, tip_amount: Option, ) -> Result<(String, u64)> { if config.signers.is_empty() { @@ -105,6 +105,7 @@ impl Helius { let random_tip_account: &str = *JITO_TIP_ACCOUNTS.choose(&mut rand::thread_rng()).unwrap(); let payer_key: Pubkey = config .fee_payer + .as_ref() .map_or_else(|| config.signers[0].pubkey(), |signer| signer.pubkey()); self.add_tip_instruction(&mut config.instructions, payer_key, random_tip_account, tip_amount); @@ -212,7 +213,7 @@ impl Helius { /// A `Result` containing the bundle IDc pub async fn send_smart_transaction_with_tip( &self, - config: SmartTransactionConfig<'_>, + config: SmartTransactionConfig, tip_amount: Option, region: Option, ) -> Result { diff --git a/src/optimized_transaction.rs b/src/optimized_transaction.rs index 5abc969..cb82745 100644 --- a/src/optimized_transaction.rs +++ b/src/optimized_transaction.rs @@ -4,12 +4,13 @@ use crate::types::{ GetPriorityFeeEstimateRequest, GetPriorityFeeEstimateResponse, SmartTransaction, SmartTransactionConfig, Timeout, }; use crate::Helius; +use std::sync::Arc; use bincode::{serialize, ErrorKind}; use reqwest::StatusCode; use solana_client::rpc_config::{RpcSendTransactionConfig, RpcSimulateTransactionConfig}; use solana_client::rpc_response::{Response, RpcSimulateTransactionResult}; -use solana_sdk::signature::{keypair_from_seed, Keypair}; +use solana_sdk::signature::keypair_from_seed; use solana_sdk::{ address_lookup_table::AddressLookupTableAccount, bs58::encode, @@ -42,7 +43,7 @@ impl Helius { instructions: Vec, payer: Pubkey, lookup_tables: Vec, - signers: Option<&[&dyn Signer]>, + signers: Option<&[Arc]>, ) -> Result> { // Set the compute budget limit let test_instructions: Vec = vec![ComputeBudgetInstruction::set_compute_unit_limit(1_400_000)] @@ -134,7 +135,7 @@ impl Helius { /// An optimized `SmartTransaction` (i.e., `Transaction` or `VersionedTransaction`) and the `last_valid_block_height` pub async fn create_smart_transaction( &self, - config: &CreateSmartTransactionConfig<'_>, + config: &CreateSmartTransactionConfig, ) -> Result<(SmartTransaction, u64)> { if config.signers.is_empty() { return Err(HeliusError::InvalidInput( @@ -144,6 +145,7 @@ impl Helius { let payer_pubkey: Pubkey = config .fee_payer + .as_ref() .map_or(config.signers[0].pubkey(), |signer| signer.pubkey()); let (recent_blockhash, last_valid_block_hash) = self .connection() @@ -174,10 +176,10 @@ impl Helius { v0::Message::try_compile(&payer_pubkey, &config.instructions, lookup_tables, recent_blockhash)?; let versioned_message: VersionedMessage = VersionedMessage::V0(v0_message); - let all_signers: Vec<&dyn Signer> = if let Some(fee_payer) = config.fee_payer { - let mut all_signers: Vec<&dyn Signer> = config.signers.clone(); + let all_signers: Vec> = if let Some(fee_payer) = &config.fee_payer { + let mut all_signers = config.signers.clone(); if !all_signers.iter().any(|signer| signer.pubkey() == fee_payer.pubkey()) { - all_signers.push(fee_payer); + all_signers.push(fee_payer.clone()); } all_signers @@ -200,7 +202,7 @@ impl Helius { let mut tx: Transaction = Transaction::new_with_payer(&config.instructions, Some(&payer_pubkey)); tx.try_partial_sign(&config.signers, recent_blockhash)?; - if let Some(fee_payer) = config.fee_payer { + if let Some(fee_payer) = config.fee_payer.as_ref() { tx.try_partial_sign(&[fee_payer], recent_blockhash)?; } @@ -284,10 +286,10 @@ impl Helius { v0::Message::try_compile(&payer_pubkey, &final_instructions, lookup_tables, recent_blockhash)?; let versioned_message: VersionedMessage = VersionedMessage::V0(v0_message); - let all_signers: Vec<&dyn Signer> = if let Some(fee_payer) = config.fee_payer { + let all_signers: Vec> = if let Some(fee_payer) = config.fee_payer.as_ref() { let mut all_signers = config.signers.clone(); if !all_signers.iter().any(|signer| signer.pubkey() == fee_payer.pubkey()) { - all_signers.push(fee_payer); + all_signers.push(fee_payer.clone()); } all_signers } else { @@ -312,7 +314,7 @@ impl Helius { let mut tx: Transaction = Transaction::new_with_payer(&final_instructions, Some(&payer_pubkey)); tx.try_partial_sign(&config.signers, recent_blockhash)?; - if let Some(fee_payer) = config.fee_payer { + if let Some(fee_payer) = config.fee_payer.as_ref() { tx.try_partial_sign(&[fee_payer], recent_blockhash)?; } @@ -333,7 +335,7 @@ impl Helius { /// /// # Returns /// The transaction signature, if successful - pub async fn send_smart_transaction(&self, config: SmartTransactionConfig<'_>) -> Result { + pub async fn send_smart_transaction(&self, config: SmartTransactionConfig) -> Result { let (transaction, last_valid_block_height) = self.create_smart_transaction(&config.create_config).await?; // Common logic for sending transactions @@ -420,32 +422,32 @@ impl Helius { )); } - let mut signers: Vec = create_config + let mut signers: Vec> = create_config .signer_seeds .into_iter() - .map(|seed| keypair_from_seed(&seed).expect("Failed to create keypair from seed")) + .map(|seed| { + Arc::new(keypair_from_seed(&seed).expect("Failed to create keypair from seed")) as Arc + }) .collect(); // Determine the fee payer let fee_payer_index: usize = if let Some(fee_payer_seed) = create_config.fee_payer_seed { - let fee_payer: Keypair = - keypair_from_seed(&fee_payer_seed).expect("Failed to create fee payer keypair from seed"); + let fee_payer = + Arc::new(keypair_from_seed(&fee_payer_seed).expect("Failed to create fee payer keypair from seed")); signers.push(fee_payer); signers.len() - 1 // Index of the last signer (fee payer) } else { 0 // Index of the first signer }; - - let signer_refs: Vec<&dyn Signer> = signers.iter().map(|keypair| keypair as &dyn Signer).collect(); - - let create_smart_transaction_config: CreateSmartTransactionConfig<'_> = CreateSmartTransactionConfig { + let fee_payer = signers[fee_payer_index].clone(); + let create_smart_transaction_config: CreateSmartTransactionConfig = CreateSmartTransactionConfig { instructions: create_config.instructions, - signers: signer_refs, + signers, lookup_tables: create_config.lookup_tables, - fee_payer: Some(&signers[fee_payer_index] as &dyn Signer), + fee_payer: Some(fee_payer), }; - let smart_transaction_config: SmartTransactionConfig<'_> = SmartTransactionConfig { + let smart_transaction_config: SmartTransactionConfig = SmartTransactionConfig { create_config: create_smart_transaction_config, send_options: send_options.unwrap_or_default(), timeout: timeout.unwrap_or_default(), diff --git a/src/types/types.rs b/src/types/types.rs index bf7180b..3b0f97c 100644 --- a/src/types/types.rs +++ b/src/types/types.rs @@ -6,6 +6,7 @@ use super::{ use crate::types::{DisplayOptions, GetAssetOptions}; use serde::{Deserialize, Serialize}; use serde_json::Value; +use std::sync::Arc; use std::time::Duration; use solana_client::rpc_config::RpcSendTransactionConfig; @@ -950,15 +951,15 @@ pub struct EditWebhookRequest { pub encoding: AccountWebhookEncoding, } -pub struct CreateSmartTransactionConfig<'a> { +pub struct CreateSmartTransactionConfig { pub instructions: Vec, - pub signers: Vec<&'a dyn Signer>, + pub signers: Vec>, pub lookup_tables: Option>, - pub fee_payer: Option<&'a dyn Signer>, + pub fee_payer: Option>, } -impl<'a> CreateSmartTransactionConfig<'a> { - pub fn new(instructions: Vec, signers: Vec<&'a dyn Signer>) -> Self { +impl CreateSmartTransactionConfig { + pub fn new(instructions: Vec, signers: Vec>) -> Self { Self { instructions, signers, @@ -985,14 +986,14 @@ impl Into for Timeout { } } -pub struct SmartTransactionConfig<'a> { - pub create_config: CreateSmartTransactionConfig<'a>, +pub struct SmartTransactionConfig { + pub create_config: CreateSmartTransactionConfig, pub send_options: RpcSendTransactionConfig, pub timeout: Timeout, } -impl<'a> SmartTransactionConfig<'a> { - pub fn new(instructions: Vec, signers: Vec<&'a dyn Signer>, timeout: Timeout) -> Self { +impl SmartTransactionConfig { + pub fn new(instructions: Vec, signers: Vec>, timeout: Timeout) -> Self { Self { create_config: CreateSmartTransactionConfig::new(instructions, signers), send_options: RpcSendTransactionConfig::default(), From 57d51461bc9c3a451c166411862ef4fd08adba8e Mon Sep 17 00:00:00 2001 From: Evan <0xIchigo@protonmail.com> Date: Tue, 26 Nov 2024 13:32:15 -0500 Subject: [PATCH 44/48] Update Links in README --- README.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index b2fda5f..d47b67f 100644 --- a/README.md +++ b/README.md @@ -133,19 +133,19 @@ Our SDK is designed to provide a seamless developer experience when building on - [`remove_addresses_from_webhook`](https://github.com/helius-labs/helius-rust-sdk/blob/bf24259e3333ae93126bb65b342c2c63e80e07a6/src/webhook.rs#L75-L105) - Removes a list of addresses from an existing webhook by its ID ### Smart Transactions -- [`create_smart_transaction`](https://github.com/helius-labs/helius-rust-sdk/blob/705d66fb7d4004fc32c2a5f0d6ca4a1f2a7b175d/src/optimized_transaction.rs#L113-L312) - Creates an optimized transaction based on the provided configuration -- [`get_compute_units`](https://github.com/helius-labs/helius-rust-sdk/blob/a79a751e1a064125010bdb359068a366d635d005/src/optimized_transaction.rs#L29-L75) - Simulates a transaction to get the total compute units consumed -- [`poll_transaction_confirmation`](https://github.com/helius-labs/helius-rust-sdk/blob/a79a751e1a064125010bdb359068a366d635d005/src/optimized_transaction.rs#L77-L112) - Polls a transaction to check whether it has been confirmed in 5 second intervals with a 15 second timeout -- [`send_smart_transaction`](https://github.com/helius-labs/helius-rust-sdk/blob/705d66fb7d4004fc32c2a5f0d6ca4a1f2a7b175d/src/optimized_transaction.rs#L314-L374) - Builds and sends an optimized transaction, and handles its confirmation status -- [`send_and_confirm_transaction`] - Sends a transaction and handles its confirmation status with retry logic -- [`send_smart_transaction_with_seeds`]() - Sends a smart transaction using seed bytes +- [`create_smart_transaction`](https://github.com/helius-labs/helius-rust-sdk/blob/bd9e0b10c81ab9ea56dfcd286336b086f6737b64/src/optimized_transaction.rs#L131-L331) - Creates an optimized transaction based on the provided configuration +- [`get_compute_units`](https://github.com/helius-labs/helius-rust-sdk/blob/bd9e0b10c81ab9ea56dfcd286336b086f6737b64/src/optimized_transaction.rs#L34-L87) - Simulates a transaction to get the total compute units consumed +- [`poll_transaction_confirmation`](https://github.com/helius-labs/helius-rust-sdk/blob/bd9e0b10c81ab9ea56dfcd286336b086f6737b64/src/optimized_transaction.rs#L89-L129) - Polls a transaction to check whether it has been confirmed in 5 second intervals with a 15 second timeout +- [`send_smart_transaction`](https://github.com/helius-labs/helius-rust-sdk/blob/bd9e0b10c81ab9ea56dfcd286336b086f6737b64/src/optimized_transaction.rs#L333-L364) - Builds and sends an optimized transaction, and handles its confirmation status +- [`send_and_confirm_transaction`](https://github.com/helius-labs/helius-rust-sdk/blob/bd9e0b10c81ab9ea56dfcd286336b086f6737b64/src/optimized_transaction.rs#L366-L412) - Sends a transaction and handles its confirmation status with retry logic +- [`send_smart_transaction_with_seeds`](https://github.com/helius-labs/helius-rust-sdk/blob/bd9e0b10c81ab9ea56dfcd286336b086f6737b64/src/optimized_transaction.rs#L414-L487) - Sends a smart transaction using seed bytes ### Jito Smart Transactions and Helper Methods - [`add_tip_instruction`](https://github.com/helius-labs/helius-rust-sdk/blob/02b351a5ee3fe16a36078b40f92dc72d0ad077ed/src/jito.rs#L66-L83) - Adds a tip instruction to the instructions provided -- [`create_smart_transaction_with_tip`](https://github.com/helius-labs/helius-rust-sdk/blob/02b351a5ee3fe16a36078b40f92dc72d0ad077ed/src/jito.rs#L85-L124) - Creates a smart transaction with a Jito tip -- [`get_bundle_statuses`](https://github.com/helius-labs/helius-rust-sdk/blob/02b351a5ee3fe16a36078b40f92dc72d0ad077ed/src/jito.rs#L169-L202) - Get the status of Jito bundles -- [`send_jito_bundle`](https://github.com/helius-labs/helius-rust-sdk/blob/02b351a5ee3fe16a36078b40f92dc72d0ad077ed/src/jito.rs#L126-L167) - Sends a bundle of transactions to the Jito Block Engine -- [`send_smart_transaction_with_tip`](https://github.com/helius-labs/helius-rust-sdk/blob/02b351a5ee3fe16a36078b40f92dc72d0ad077ed/src/jito.rs#L204-L269) - Sends a smart transaction as a Jito bundle with a tip +- [`create_smart_transaction_with_tip`](https://github.com/helius-labs/helius-rust-sdk/blob/bd9e0b10c81ab9ea56dfcd286336b086f6737b64/src/jito.rs#L85-L125) - Creates a smart transaction with a Jito tip +- [`get_bundle_statuses`](https://github.com/helius-labs/helius-rust-sdk/blob/bd9e0b10c81ab9ea56dfcd286336b086f6737b64/src/jito.rs#L170-L203) - Get the status of Jito bundles +- [`send_jito_bundle`](https://github.com/helius-labs/helius-rust-sdk/blob/bd9e0b10c81ab9ea56dfcd286336b086f6737b64/src/jito.rs#L127-L168) - Sends a bundle of transactions to the Jito Block Engine +- [`send_smart_transaction_with_tip`](https://github.com/helius-labs/helius-rust-sdk/blob/bd9e0b10c81ab9ea56dfcd286336b086f6737b64/src/jito.rs#L205-L270) - Sends a smart transaction as a Jito bundle with a tip ### Helper Methods - [`get_priority_fee_estimate`](https://docs.helius.dev/solana-rpc-nodes/alpha-priority-fee-api) - Gets an estimate of the priority fees required for a transaction to be processed more quickly From 71952b463443555ee51722e1c9c03cd1e5a3e94b Mon Sep 17 00:00:00 2001 From: Evan <0xIchigo@protonmail.com> Date: Tue, 26 Nov 2024 13:33:31 -0500 Subject: [PATCH 45/48] Remove Unused Import --- src/optimized_transaction.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/optimized_transaction.rs b/src/optimized_transaction.rs index a066058..108885a 100644 --- a/src/optimized_transaction.rs +++ b/src/optimized_transaction.rs @@ -13,7 +13,7 @@ use solana_client::{ rpc_config::{RpcSendTransactionConfig, RpcSimulateTransactionConfig}, rpc_response::{Response, RpcSimulateTransactionResult}, }; -use solana_sdk::signature::{keypair_from_seed, Keypair}; +use solana_sdk::signature::keypair_from_seed; use solana_sdk::{ address_lookup_table::AddressLookupTableAccount, bs58::encode, From f146a359720e46c75ad652dda859bd126f0faa5b Mon Sep 17 00:00:00 2001 From: Evan <0xIchigo@protonmail.com> Date: Tue, 26 Nov 2024 15:22:01 -0500 Subject: [PATCH 46/48] Remove Unreachable Code --- src/types/enums.rs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/types/enums.rs b/src/types/enums.rs index 01a82ad..4e17fa5 100644 --- a/src/types/enums.rs +++ b/src/types/enums.rs @@ -29,13 +29,6 @@ pub enum Interface { ProgrammableNFT, #[serde(rename = "FungibleToken")] FungibleToken, - #[serde(rename = "V1_PRINT")] - V1PRINT, - #[allow(non_camel_case_types)] - #[serde(rename = "LEGACY_NFT")] - LEGACY_NFT, - #[serde(rename = "V2_NFT")] - Nft, #[serde(rename = "MplCoreAsset")] MplCoreAsset, #[serde(rename = "MplCoreCollection")] From da3d1421c0a6860e6e31f6a226a028cf39a636e2 Mon Sep 17 00:00:00 2001 From: Evan <0xIchigo@protonmail.com> Date: Tue, 26 Nov 2024 17:07:12 -0500 Subject: [PATCH 47/48] Fix Issue With Websockets Not Working --- Cargo.toml | 16 +++++++++++----- src/websocket.rs | 2 +- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index c626daa..42037cc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,7 +20,7 @@ futures-util = "0.3.30" mpl-token-metadata = { version = "5.0.0-beta.0" } phf = { version = "0.11.2", features = ["macros"] } rand = "0.8.5" -reqwest = { version = "0.12.8", features = ["json"], default-features = false } +reqwest = { version = "0.12.8", features = ["json", "native-tls"] } semver = "1.0.23" serde = "1.0.198" serde-enum-str = "0.4.0" @@ -32,9 +32,9 @@ solana-rpc-client-api = "2.0" solana-sdk = "2.0" solana-transaction-status = "2.0" thiserror = "1.0.58" -tokio = { version = "1.37.0", features = ["macros", "rt-multi-thread", "net"] } +tokio = { version = "1.37.0", features = ["macros", "rt-multi-thread", "net", "time"] } tokio-stream = "0.1.15" -tokio-tungstenite = { version = "0.24.0", features = ["native-tls", "handshake"] } +tokio-tungstenite = { version = "0.24.0", features = ["native-tls"] } url = "2.5.0" spl-token = { version = "6.0", features = ["no-entrypoint"] } @@ -43,8 +43,14 @@ mockito = "1.4.0" [features] default = ["native-tls"] -native-tls = ["reqwest/native-tls"] -rustls = ["reqwest/rustls-tls"] +native-tls = [ + "reqwest/native-tls", + "tokio-tungstenite/native-tls" +] +rustls = [ + "reqwest/rustls-tls", + "tokio-tungstenite/rustls-tls-webpki-roots" +] [patch.crates-io] # https://github.com/solana-labs/solana/issues/26688#issuecomment-2136066879 diff --git a/src/websocket.rs b/src/websocket.rs index 86558b6..fc6c43c 100644 --- a/src/websocket.rs +++ b/src/websocket.rs @@ -29,7 +29,7 @@ use tokio_tungstenite::{ MaybeTlsStream, WebSocketStream, }; -pub const ENHANCED_WEBSOCKET_URL: &str = "wss://atlas-mainnet.helius-rpc.com?api-key="; +pub const ENHANCED_WEBSOCKET_URL: &str = "wss://atlas-mainnet.helius-rpc.com/?api-key="; const DEFAULT_PING_DURATION_SECONDS: u64 = 10; // pub type Result = Result; From eeeed5464ab0dac30dc077ad74f6f25b4e697f95 Mon Sep 17 00:00:00 2001 From: Evan <0xIchigo@protonmail.com> Date: Tue, 26 Nov 2024 17:12:36 -0500 Subject: [PATCH 48/48] Update Enhanced Websocket Account Example --- examples/enhanced_websocket_accounts.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/enhanced_websocket_accounts.rs b/examples/enhanced_websocket_accounts.rs index 5357bdc..9a4af96 100644 --- a/examples/enhanced_websocket_accounts.rs +++ b/examples/enhanced_websocket_accounts.rs @@ -9,7 +9,7 @@ async fn main() -> Result<()> { let api_key: &str = "your_api_key"; let cluster: Cluster = Cluster::MainnetBeta; - let helius: Helius = Helius::new_with_ws(api_key, cluster).await.unwrap(); + let helius: Helius = Helius::new_with_ws(api_key, cluster).await?; let key: pubkey::Pubkey = pubkey!("BtsmiEEvnSuUnKxqXj2PZRYpPJAc7C34mGz8gtJ1DAaH");