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    #[allow(clippy::result_large_err)]
138    pub fn substrate(&self) -> Result<&SubstrateAdapter> {
139        self.substrate_adapter
140            .as_ref()
141            .ok_or_else(|| Error::config("Substrate adapter not configured"))
142    }
143
144    /// Get a reference to the EVM adapter.
145    ///
146    /// # Errors
147    ///
148    /// Returns an error if the EVM adapter was not configured
149    /// during SDK initialization.
150    ///
151    /// # Examples
152    ///
153    /// ```rust,no_run
154    /// use apex_sdk::prelude::*;
155    ///
156    /// # #[tokio::main]
157    /// # async fn main() -> Result<()> {
158    /// let sdk = ApexSDK::builder()
159    ///     .with_evm_endpoint("https://mainnet.infura.io/v3/YOUR_KEY")
160    ///     .build()
161    ///     .await?;
162    ///
163    /// let evm = sdk.evm()?;
164    /// // Use the EVM adapter...
165    /// # Ok(())
166    /// # }
167    /// ```
168    #[allow(clippy::result_large_err)]
169    pub fn evm(&self) -> Result<&EvmAdapter> {
170        self.evm_adapter
171            .as_ref()
172            .ok_or_else(|| Error::config("EVM adapter not configured"))
173    }
174
175    /// Check if a specific blockchain is supported by the current SDK configuration.
176    ///
177    /// Returns `true` if the chain is supported, `false` otherwise. Support
178    /// depends on which adapters were configured during SDK initialization.
179    ///
180    /// # Arguments
181    ///
182    /// * `chain` - The blockchain to check for support
183    ///
184    /// # Examples
185    ///
186    /// ```rust,no_run
187    /// use apex_sdk::prelude::*;
188    ///
189    /// # #[tokio::main]
190    /// # async fn main() -> Result<()> {
191    /// let sdk = ApexSDK::builder()
192    ///     .with_evm_endpoint("https://mainnet.infura.io/v3/YOUR_KEY")
193    ///     .build()
194    ///     .await?;
195    ///
196    /// if sdk.is_chain_supported(&Chain::Ethereum) {
197    ///     println!("Ethereum is supported!");
198    /// }
199    ///
200    /// if !sdk.is_chain_supported(&Chain::Polkadot) {
201    ///     println!("Polkadot is not supported (no Substrate adapter)");
202    /// }
203    /// # Ok(())
204    /// # }
205    /// ```
206    pub fn is_chain_supported(&self, chain: &Chain) -> bool {
207        match chain {
208            Chain::Polkadot
209            | Chain::Kusama
210            | Chain::Acala
211            | Chain::Phala
212            | Chain::Bifrost
213            | Chain::Westend => self.substrate_adapter.is_some(),
214            Chain::Ethereum
215            | Chain::Polygon
216            | Chain::BinanceSmartChain
217            | Chain::Avalanche
218            | Chain::Arbitrum
219            | Chain::Optimism
220            | Chain::ZkSync
221            | Chain::Base => self.evm_adapter.is_some(),
222            Chain::Moonbeam | Chain::Astar => {
223                self.substrate_adapter.is_some() && self.evm_adapter.is_some()
224            }
225        }
226    }
227
228    /// Get the status of a transaction
229    pub async fn get_transaction_status(
230        &self,
231        chain: &Chain,
232        tx_hash: &str,
233    ) -> Result<TransactionStatus> {
234        match chain {
235            Chain::Polkadot
236            | Chain::Kusama
237            | Chain::Acala
238            | Chain::Phala
239            | Chain::Bifrost
240            | Chain::Westend => 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    /// Execute a transaction
273    pub async fn execute(&self, transaction: Transaction) -> Result<TransactionResult> {
274        tracing::info!(
275            "Executing transaction from {:?} to {:?}",
276            transaction.source_chain,
277            transaction.destination_chain
278        );
279
280        // Validate that the required adapters are configured
281        match transaction.source_chain {
282            Chain::Polkadot
283            | Chain::Kusama
284            | Chain::Acala
285            | Chain::Phala
286            | Chain::Bifrost
287            | Chain::Westend => {
288                self.substrate()?;
289            }
290            Chain::Ethereum
291            | Chain::Polygon
292            | Chain::BinanceSmartChain
293            | Chain::Avalanche
294            | Chain::Arbitrum
295            | Chain::Optimism
296            | Chain::ZkSync
297            | Chain::Base => {
298                self.evm()?;
299            }
300            Chain::Moonbeam | Chain::Astar => {
301                self.substrate()?;
302                self.evm()?;
303            }
304        }
305
306        // For MVP, return a mock successful result
307        // In production, this would interact with the actual blockchain
308        let source_tx_hash = format!(
309            "0x{}",
310            hex::encode(
311                transaction
312                    .from
313                    .as_str()
314                    .as_bytes()
315                    .iter()
316                    .take(8)
317                    .cloned()
318                    .collect::<Vec<u8>>()
319            )
320        );
321
322        let destination_tx_hash = if transaction.is_cross_chain() {
323            Some(format!(
324                "0x{}",
325                hex::encode(
326                    transaction
327                        .to
328                        .as_str()
329                        .as_bytes()
330                        .iter()
331                        .take(8)
332                        .cloned()
333                        .collect::<Vec<u8>>()
334                )
335            ))
336        } else {
337            None
338        };
339
340        Ok(TransactionResult {
341            source_tx_hash,
342            destination_tx_hash,
343            status: TransactionStatus::Confirmed {
344                block_hash: "0xabc123".to_string(),
345                block_number: Some(12345),
346            },
347            block_number: Some(12345),
348            gas_used: Some(21000),
349        })
350    }
351}
352
353#[cfg(test)]
354mod tests {
355    use super::*;
356
357    #[tokio::test]
358    async fn test_builder_requires_adapter() {
359        let result = ApexSDK::builder().build().await;
360        assert!(result.is_err());
361    }
362
363    #[tokio::test]
364    async fn test_new_returns_error() {
365        let result = ApexSDK::new().await;
366        assert!(result.is_err());
367        match result {
368            Err(Error::Config(_, _)) => {}
369            _ => panic!("Expected Config error"),
370        }
371    }
372
373    #[tokio::test]
374    #[ignore] // Requires network connection
375    async fn test_substrate_adapter_not_configured() {
376        let sdk = ApexSDK::builder()
377            .with_evm_endpoint("https://eth.llamarpc.com")
378            .build()
379            .await
380            .unwrap();
381
382        let result = sdk.substrate();
383        assert!(result.is_err());
384        match result {
385            Err(Error::Config(msg, _)) => {
386                assert!(msg.contains("Substrate adapter not configured"));
387            }
388            _ => panic!("Expected Config error"),
389        }
390    }
391
392    #[tokio::test]
393    #[ignore] // Requires network connection
394    async fn test_evm_adapter_not_configured() {
395        let sdk = ApexSDK::builder()
396            .with_substrate_endpoint("wss://test")
397            .build()
398            .await
399            .unwrap();
400
401        let result = sdk.evm();
402        assert!(result.is_err());
403        match result {
404            Err(Error::Config(msg, _)) => {
405                assert!(msg.contains("EVM adapter not configured"));
406            }
407            _ => panic!("Expected Config error"),
408        }
409    }
410
411    #[tokio::test]
412    #[ignore] // Requires network connection
413    async fn test_is_chain_supported_substrate_only() {
414        let sdk = ApexSDK::builder()
415            .with_substrate_endpoint("wss://test")
416            .build()
417            .await
418            .unwrap();
419
420        assert!(sdk.is_chain_supported(&Chain::Polkadot));
421        assert!(sdk.is_chain_supported(&Chain::Kusama));
422        assert!(!sdk.is_chain_supported(&Chain::Ethereum));
423        assert!(!sdk.is_chain_supported(&Chain::Polygon));
424        assert!(!sdk.is_chain_supported(&Chain::Moonbeam)); // Requires both adapters
425    }
426
427    #[tokio::test]
428    #[ignore] // Requires network connection
429    async fn test_is_chain_supported_evm_only() {
430        let sdk = ApexSDK::builder()
431            .with_evm_endpoint("https://eth.llamarpc.com")
432            .build()
433            .await
434            .unwrap();
435
436        assert!(!sdk.is_chain_supported(&Chain::Polkadot));
437        assert!(!sdk.is_chain_supported(&Chain::Kusama));
438        assert!(sdk.is_chain_supported(&Chain::Ethereum));
439        assert!(sdk.is_chain_supported(&Chain::Polygon));
440        assert!(!sdk.is_chain_supported(&Chain::Moonbeam)); // Requires both adapters
441    }
442
443    #[tokio::test]
444    #[ignore] // Requires network connection
445    async fn test_is_chain_supported_both_adapters() {
446        let sdk = ApexSDK::builder()
447            .with_substrate_endpoint("wss://rpc.polkadot.io")
448            .with_evm_endpoint("https://eth.llamarpc.com")
449            .build()
450            .await
451            .unwrap();
452
453        assert!(sdk.is_chain_supported(&Chain::Polkadot));
454        assert!(sdk.is_chain_supported(&Chain::Ethereum));
455        assert!(sdk.is_chain_supported(&Chain::Moonbeam));
456        assert!(sdk.is_chain_supported(&Chain::Astar));
457    }
458
459    #[tokio::test]
460    #[ignore] // Requires network connection
461    async fn test_transaction_builder() {
462        let sdk = ApexSDK::builder()
463            .with_evm_endpoint("https://eth.llamarpc.com")
464            .build()
465            .await
466            .unwrap();
467
468        let tx = sdk
469            .transaction()
470            .from_evm_address("0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb7")
471            .to_evm_address("0x1234567890123456789012345678901234567890")
472            .amount(1000)
473            .build();
474
475        assert!(tx.is_ok());
476    }
477
478    #[tokio::test]
479    #[ignore] // Requires network connection
480    async fn test_execute_transaction() {
481        let sdk = ApexSDK::builder()
482            .with_evm_endpoint("https://eth.llamarpc.com")
483            .build()
484            .await
485            .unwrap();
486
487        let tx = sdk
488            .transaction()
489            .from_evm_address("0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb7")
490            .to_evm_address("0x1234567890123456789012345678901234567890")
491            .amount(1000)
492            .build()
493            .unwrap();
494
495        let result = sdk.execute(tx).await;
496        assert!(result.is_ok());
497
498        let tx_result = result.unwrap();
499        assert!(!tx_result.source_tx_hash.is_empty());
500        assert!(tx_result.destination_tx_hash.is_none());
501    }
502
503    #[tokio::test]
504    #[ignore] // Requires network connection
505    async fn test_execute_cross_chain_transaction() {
506        let sdk = ApexSDK::builder()
507            .with_substrate_endpoint("wss://rpc.polkadot.io")
508            .with_evm_endpoint("https://eth.llamarpc.com")
509            .build()
510            .await
511            .unwrap();
512
513        let tx = sdk
514            .transaction()
515            .from_substrate_account("5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY")
516            .to_evm_address("0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb7")
517            .amount(1000)
518            .build()
519            .unwrap();
520
521        assert!(tx.is_cross_chain());
522
523        let result = sdk.execute(tx).await;
524        assert!(result.is_ok());
525
526        let tx_result = result.unwrap();
527        assert!(!tx_result.source_tx_hash.is_empty());
528        assert!(tx_result.destination_tx_hash.is_some());
529    }
530}