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