apex_sdk/
sdk.rs

1//! Main ApexSDK implementation
2//!
3//! This module provides the core SDK struct and methods for interacting
4//! with multiple blockchain networks through a unified interface.
5//!
6//! # Examples
7//!
8//! ```rust,no_run
9//! use apex_sdk::prelude::*;
10//!
11//! #[tokio::main]
12//! async fn main() -> Result<()> {
13//!     // Initialize SDK with both Substrate and EVM support
14//!     let sdk = ApexSDK::builder()
15//!         .with_substrate_endpoint("wss://polkadot.api.onfinality.io/public-ws")
16//!         .with_evm_endpoint("https://mainnet.infura.io/v3/YOUR_KEY")
17//!         .build()
18//!         .await?;
19//!
20//!     // Check chain support
21//!     if sdk.is_chain_supported(&Chain::Ethereum) {
22//!         println!("Ethereum is supported!");
23//!     }
24//!
25//!     // Create and execute a transaction
26//!     let tx = sdk.transaction()
27//!         .from_evm_address("0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb7")
28//!         .to_evm_address("0x1234567890123456789012345678901234567890")
29//!         .amount(1000)
30//!         .build()?;
31//!
32//!     let result = sdk.execute(tx).await?;
33//!     println!("Transaction hash: {}", result.source_tx_hash);
34//!
35//!     Ok(())
36//! }
37//! ```
38
39use crate::builder::ApexSDKBuilder;
40use crate::error::{Error, Result};
41use crate::transaction::{Transaction, TransactionBuilder, TransactionResult};
42use apex_sdk_evm::EvmAdapter;
43use apex_sdk_substrate::SubstrateAdapter;
44use apex_sdk_types::{Chain, TransactionStatus};
45
46/// Main Apex SDK struct providing unified interface to blockchain operations.
47///
48/// The `ApexSDK` is the primary entry point for interacting with multiple
49/// blockchain networks. It manages connections to both Substrate-based and
50/// EVM-compatible chains through adapter interfaces.
51///
52/// # Examples
53///
54/// ## Basic Usage
55///
56/// ```rust,no_run
57/// use apex_sdk::prelude::*;
58///
59/// # #[tokio::main]
60/// # async fn main() -> Result<()> {
61/// let sdk = ApexSDK::builder()
62///     .with_evm_endpoint("https://mainnet.infura.io/v3/YOUR_KEY")
63///     .build()
64///     .await?;
65/// # Ok(())
66/// # }
67/// ```
68pub struct ApexSDK {
69    pub(crate) substrate_adapter: Option<SubstrateAdapter>,
70    pub(crate) evm_adapter: Option<EvmAdapter>,
71}
72
73impl ApexSDK {
74    /// Create a new builder for configuring the SDK.
75    ///
76    /// This is the recommended way to create an ApexSDK instance.
77    /// Use the builder pattern to configure which blockchain adapters
78    /// you need before initialization.
79    ///
80    /// # Examples
81    ///
82    /// ```rust,no_run
83    /// use apex_sdk::prelude::*;
84    ///
85    /// # #[tokio::main]
86    /// # async fn main() -> Result<()> {
87    /// let sdk = ApexSDK::builder()
88    ///     .with_substrate_endpoint("wss://polkadot.api.onfinality.io/public-ws")
89    ///     .with_evm_endpoint("https://mainnet.infura.io/v3/YOUR_KEY")
90    ///     .build()
91    ///     .await?;
92    /// # Ok(())
93    /// # }
94    /// ```
95    pub fn builder() -> ApexSDKBuilder {
96        ApexSDKBuilder::new()
97    }
98
99    /// Create a new SDK instance with default configuration.
100    ///
101    /// # Note
102    ///
103    /// This method will always return an error. Use `ApexSDK::builder()`
104    /// instead to properly configure the SDK.
105    ///
106    /// # Errors
107    ///
108    /// Always returns a configuration error directing you to use the builder.
109    pub async fn new() -> Result<Self> {
110        Err(Error::config("Use ApexSDK::builder() to configure the SDK"))
111    }
112
113    /// Get a reference to the Substrate adapter.
114    ///
115    /// # Errors
116    ///
117    /// Returns an error if the Substrate adapter was not configured
118    /// during SDK initialization.
119    ///
120    /// # Examples
121    ///
122    /// ```rust,no_run
123    /// use apex_sdk::prelude::*;
124    ///
125    /// # #[tokio::main]
126    /// # async fn main() -> Result<()> {
127    /// let sdk = ApexSDK::builder()
128    ///     .with_substrate_endpoint("wss://polkadot.api.onfinality.io/public-ws")
129    ///     .build()
130    ///     .await?;
131    ///
132    /// let substrate = sdk.substrate()?;
133    /// // Use the Substrate adapter...
134    /// # Ok(())
135    /// # }
136    /// ```
137    pub fn substrate(&self) -> Result<&SubstrateAdapter> {
138        self.substrate_adapter
139            .as_ref()
140            .ok_or_else(|| Error::config("Substrate adapter not configured"))
141    }
142
143    /// Get a reference to the EVM adapter.
144    ///
145    /// # Errors
146    ///
147    /// Returns an error if the EVM adapter was not configured
148    /// during SDK initialization.
149    ///
150    /// # Examples
151    ///
152    /// ```rust,no_run
153    /// use apex_sdk::prelude::*;
154    ///
155    /// # #[tokio::main]
156    /// # async fn main() -> Result<()> {
157    /// let sdk = ApexSDK::builder()
158    ///     .with_evm_endpoint("https://mainnet.infura.io/v3/YOUR_KEY")
159    ///     .build()
160    ///     .await?;
161    ///
162    /// let evm = sdk.evm()?;
163    /// // Use the EVM adapter...
164    /// # Ok(())
165    /// # }
166    /// ```
167    pub fn evm(&self) -> Result<&EvmAdapter> {
168        self.evm_adapter
169            .as_ref()
170            .ok_or_else(|| Error::config("EVM adapter not configured"))
171    }
172
173    /// Check if a specific blockchain is supported by the current SDK configuration.
174    ///
175    /// Returns `true` if the chain is supported, `false` otherwise. Support
176    /// depends on which adapters were configured during SDK initialization.
177    ///
178    /// # Arguments
179    ///
180    /// * `chain` - The blockchain to check for support
181    ///
182    /// # Examples
183    ///
184    /// ```rust,no_run
185    /// use apex_sdk::prelude::*;
186    ///
187    /// # #[tokio::main]
188    /// # async fn main() -> Result<()> {
189    /// let sdk = ApexSDK::builder()
190    ///     .with_evm_endpoint("https://mainnet.infura.io/v3/YOUR_KEY")
191    ///     .build()
192    ///     .await?;
193    ///
194    /// if sdk.is_chain_supported(&Chain::Ethereum) {
195    ///     println!("Ethereum is supported!");
196    /// }
197    ///
198    /// if !sdk.is_chain_supported(&Chain::Polkadot) {
199    ///     println!("Polkadot is not supported (no Substrate adapter)");
200    /// }
201    /// # Ok(())
202    /// # }
203    /// ```
204    pub fn is_chain_supported(&self, chain: &Chain) -> bool {
205        match chain {
206            Chain::Polkadot
207            | Chain::Kusama
208            | Chain::Acala
209            | Chain::Phala
210            | Chain::Bifrost
211            | Chain::Westend
212            | Chain::Paseo => self.substrate_adapter.is_some(),
213            Chain::Ethereum
214            | Chain::Polygon
215            | Chain::BinanceSmartChain
216            | Chain::Avalanche
217            | Chain::Arbitrum
218            | Chain::Optimism
219            | Chain::ZkSync
220            | Chain::Base => self.evm_adapter.is_some(),
221            Chain::Moonbeam | Chain::Astar => {
222                self.substrate_adapter.is_some() && self.evm_adapter.is_some()
223            }
224        }
225    }
226
227    /// Get the status of a transaction
228    pub async fn get_transaction_status(
229        &self,
230        chain: &Chain,
231        tx_hash: &str,
232    ) -> Result<TransactionStatus> {
233        match chain {
234            Chain::Polkadot
235            | Chain::Kusama
236            | Chain::Acala
237            | Chain::Phala
238            | Chain::Bifrost
239            | Chain::Westend
240            | Chain::Paseo => self
241                .substrate()?
242                .get_transaction_status(tx_hash)
243                .await
244                .map_err(Error::Substrate),
245            Chain::Ethereum
246            | Chain::Polygon
247            | Chain::BinanceSmartChain
248            | Chain::Avalanche
249            | Chain::Arbitrum
250            | Chain::Optimism
251            | Chain::ZkSync
252            | Chain::Base => self
253                .evm()?
254                .get_transaction_status(tx_hash)
255                .await
256                .map_err(Error::Evm),
257            Chain::Moonbeam | Chain::Astar => {
258                // Try EVM first for hybrid chains
259                self.evm()?
260                    .get_transaction_status(tx_hash)
261                    .await
262                    .map_err(Error::Evm)
263            }
264        }
265    }
266
267    /// Create a new transaction builder
268    pub fn transaction(&self) -> TransactionBuilder {
269        TransactionBuilder::new()
270    }
271
272    /// Prepare and validate a transaction
273    ///
274    /// This method validates that the required adapters are configured and
275    /// prepares the transaction for execution. Returns a transaction result
276    /// with status set to Pending, indicating the transaction is ready to
277    /// be signed and broadcast.
278    ///
279    /// Note: Actual transaction signing and broadcasting requires a signer.
280    /// Use the substrate or EVM adapter directly with a wallet for full
281    /// transaction execution.
282    pub async fn execute(&self, transaction: Transaction) -> Result<TransactionResult> {
283        tracing::info!(
284            "Preparing transaction from {:?} to {:?}",
285            transaction.source_chain,
286            transaction.destination_chain
287        );
288
289        // Validate that the required adapters are configured
290        match transaction.source_chain {
291            Chain::Polkadot
292            | Chain::Kusama
293            | Chain::Acala
294            | Chain::Phala
295            | Chain::Bifrost
296            | Chain::Westend
297            | Chain::Paseo => {
298                self.substrate()?;
299            }
300            Chain::Ethereum
301            | Chain::Polygon
302            | Chain::BinanceSmartChain
303            | Chain::Avalanche
304            | Chain::Arbitrum
305            | Chain::Optimism
306            | Chain::ZkSync
307            | Chain::Base => {
308                self.evm()?;
309            }
310            Chain::Moonbeam | Chain::Astar => {
311                self.substrate()?;
312                self.evm()?;
313            }
314        }
315
316        // Compute transaction hash using the transaction's hash method
317        let source_tx_hash = transaction.hash();
318
319        // For cross-chain transactions, generate a destination hash
320        let destination_tx_hash = if transaction.is_cross_chain() {
321            // Create a modified hash for the destination chain
322            let mut dest_tx = transaction.clone();
323            std::mem::swap(&mut dest_tx.from, &mut dest_tx.to);
324            Some(dest_tx.hash())
325        } else {
326            None
327        };
328
329        // Return transaction as pending - ready for signing
330        Ok(TransactionResult {
331            source_tx_hash,
332            destination_tx_hash,
333            status: TransactionStatus::Pending,
334            block_number: None,
335            gas_used: None,
336        })
337    }
338}
339
340#[cfg(test)]
341mod tests {
342    use super::*;
343
344    #[tokio::test]
345    async fn test_builder_requires_adapter() {
346        let result = ApexSDK::builder().build().await;
347        assert!(result.is_err());
348    }
349
350    #[tokio::test]
351    async fn test_new_returns_error() {
352        let result = ApexSDK::new().await;
353        assert!(result.is_err());
354        match result {
355            Err(Error::Config(_, _)) => {}
356            _ => panic!("Expected Config error"),
357        }
358    }
359
360    #[tokio::test]
361    #[ignore] // Requires network connection
362    async fn test_substrate_adapter_not_configured() {
363        let sdk = ApexSDK::builder()
364            .with_evm_endpoint("https://eth.llamarpc.com")
365            .build()
366            .await
367            .unwrap();
368
369        let result = sdk.substrate();
370        assert!(result.is_err());
371        match result {
372            Err(Error::Config(msg, _)) => {
373                assert!(msg.contains("Substrate adapter not configured"));
374            }
375            _ => panic!("Expected Config error"),
376        }
377    }
378
379    #[tokio::test]
380    #[ignore] // Requires network connection
381    async fn test_evm_adapter_not_configured() {
382        let sdk = ApexSDK::builder()
383            .with_substrate_endpoint("wss://test")
384            .build()
385            .await
386            .unwrap();
387
388        let result = sdk.evm();
389        assert!(result.is_err());
390        match result {
391            Err(Error::Config(msg, _)) => {
392                assert!(msg.contains("EVM adapter not configured"));
393            }
394            _ => panic!("Expected Config error"),
395        }
396    }
397
398    #[tokio::test]
399    #[ignore] // Requires network connection
400    async fn test_is_chain_supported_substrate_only() {
401        let sdk = ApexSDK::builder()
402            .with_substrate_endpoint("wss://test")
403            .build()
404            .await
405            .unwrap();
406
407        assert!(sdk.is_chain_supported(&Chain::Polkadot));
408        assert!(sdk.is_chain_supported(&Chain::Kusama));
409        assert!(!sdk.is_chain_supported(&Chain::Ethereum));
410        assert!(!sdk.is_chain_supported(&Chain::Polygon));
411        assert!(!sdk.is_chain_supported(&Chain::Moonbeam)); // Requires both adapters
412    }
413
414    #[tokio::test]
415    #[ignore] // Requires network connection
416    async fn test_is_chain_supported_evm_only() {
417        let sdk = ApexSDK::builder()
418            .with_evm_endpoint("https://eth.llamarpc.com")
419            .build()
420            .await
421            .unwrap();
422
423        assert!(!sdk.is_chain_supported(&Chain::Polkadot));
424        assert!(!sdk.is_chain_supported(&Chain::Kusama));
425        assert!(sdk.is_chain_supported(&Chain::Ethereum));
426        assert!(sdk.is_chain_supported(&Chain::Polygon));
427        assert!(!sdk.is_chain_supported(&Chain::Moonbeam)); // Requires both adapters
428    }
429
430    #[tokio::test]
431    #[ignore] // Requires network connection
432    async fn test_is_chain_supported_both_adapters() {
433        let sdk = ApexSDK::builder()
434            .with_substrate_endpoint("wss://rpc.polkadot.io")
435            .with_evm_endpoint("https://eth.llamarpc.com")
436            .build()
437            .await
438            .unwrap();
439
440        assert!(sdk.is_chain_supported(&Chain::Polkadot));
441        assert!(sdk.is_chain_supported(&Chain::Ethereum));
442        assert!(sdk.is_chain_supported(&Chain::Moonbeam));
443        assert!(sdk.is_chain_supported(&Chain::Astar));
444    }
445
446    #[tokio::test]
447    #[ignore] // Requires network connection
448    async fn test_transaction_builder() {
449        let sdk = ApexSDK::builder()
450            .with_evm_endpoint("https://eth.llamarpc.com")
451            .build()
452            .await
453            .unwrap();
454
455        let tx = sdk
456            .transaction()
457            .from_evm_address("0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb7")
458            .to_evm_address("0x1234567890123456789012345678901234567890")
459            .amount(1000)
460            .build();
461
462        assert!(tx.is_ok());
463    }
464
465    #[tokio::test]
466    #[ignore] // Requires network connection
467    async fn test_execute_transaction() {
468        let sdk = ApexSDK::builder()
469            .with_evm_endpoint("https://eth.llamarpc.com")
470            .build()
471            .await
472            .unwrap();
473
474        let tx = sdk
475            .transaction()
476            .from_evm_address("0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb7")
477            .to_evm_address("0x1234567890123456789012345678901234567890")
478            .amount(1000)
479            .build()
480            .unwrap();
481
482        let result = sdk.execute(tx).await;
483        assert!(result.is_ok());
484
485        let tx_result = result.unwrap();
486        assert!(!tx_result.source_tx_hash.is_empty());
487        assert!(tx_result.source_tx_hash.starts_with("0x"));
488        assert!(tx_result.destination_tx_hash.is_none());
489        assert!(matches!(tx_result.status, TransactionStatus::Pending));
490    }
491
492    #[tokio::test]
493    #[ignore] // Requires network connection
494    async fn test_execute_cross_chain_transaction() {
495        let sdk = ApexSDK::builder()
496            .with_substrate_endpoint("wss://rpc.polkadot.io")
497            .with_evm_endpoint("https://eth.llamarpc.com")
498            .build()
499            .await
500            .unwrap();
501
502        let tx = sdk
503            .transaction()
504            .from_substrate_account("5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY")
505            .to_evm_address("0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb7")
506            .amount(1000)
507            .build()
508            .unwrap();
509
510        assert!(tx.is_cross_chain());
511
512        let result = sdk.execute(tx).await;
513        assert!(result.is_ok());
514
515        let tx_result = result.unwrap();
516        assert!(!tx_result.source_tx_hash.is_empty());
517        assert!(tx_result.source_tx_hash.starts_with("0x"));
518        assert!(tx_result.destination_tx_hash.is_some());
519        assert!(matches!(tx_result.status, TransactionStatus::Pending));
520    }
521}