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    async fn test_evm_adapter_not_configured() {
362        let sdk = ApexSDK::builder()
363            .with_substrate_endpoint("wss://test")
364            .build()
365            .await
366            .unwrap();
367
368        let result = sdk.evm();
369        assert!(result.is_err());
370        match result {
371            Err(Error::Config(msg)) => {
372                assert!(msg.contains("EVM adapter not configured"));
373            }
374            _ => panic!("Expected Config error"),
375        }
376    }
377
378    #[tokio::test]
379    async fn test_is_chain_supported_substrate_only() {
380        let sdk = ApexSDK::builder()
381            .with_substrate_endpoint("wss://test")
382            .build()
383            .await
384            .unwrap();
385
386        assert!(sdk.is_chain_supported(&Chain::Polkadot));
387        assert!(sdk.is_chain_supported(&Chain::Kusama));
388        assert!(!sdk.is_chain_supported(&Chain::Ethereum));
389        assert!(!sdk.is_chain_supported(&Chain::Polygon));
390        assert!(!sdk.is_chain_supported(&Chain::Moonbeam)); // Requires both adapters
391    }
392
393    #[tokio::test]
394    #[ignore] // Requires network connection
395    async fn test_is_chain_supported_evm_only() {
396        let sdk = ApexSDK::builder()
397            .with_evm_endpoint("https://eth.llamarpc.com")
398            .build()
399            .await
400            .unwrap();
401
402        assert!(!sdk.is_chain_supported(&Chain::Polkadot));
403        assert!(!sdk.is_chain_supported(&Chain::Kusama));
404        assert!(sdk.is_chain_supported(&Chain::Ethereum));
405        assert!(sdk.is_chain_supported(&Chain::Polygon));
406        assert!(!sdk.is_chain_supported(&Chain::Moonbeam)); // Requires both adapters
407    }
408
409    #[tokio::test]
410    #[ignore] // Requires network connection
411    async fn test_is_chain_supported_both_adapters() {
412        let sdk = ApexSDK::builder()
413            .with_substrate_endpoint("wss://rpc.polkadot.io")
414            .with_evm_endpoint("https://eth.llamarpc.com")
415            .build()
416            .await
417            .unwrap();
418
419        assert!(sdk.is_chain_supported(&Chain::Polkadot));
420        assert!(sdk.is_chain_supported(&Chain::Ethereum));
421        assert!(sdk.is_chain_supported(&Chain::Moonbeam));
422        assert!(sdk.is_chain_supported(&Chain::Astar));
423    }
424
425    #[tokio::test]
426    #[ignore] // Requires network connection
427    async fn test_transaction_builder() {
428        let sdk = ApexSDK::builder()
429            .with_evm_endpoint("https://eth.llamarpc.com")
430            .build()
431            .await
432            .unwrap();
433
434        let tx = sdk
435            .transaction()
436            .from_evm_address("0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb7")
437            .to_evm_address("0x1234567890123456789012345678901234567890")
438            .amount(1000)
439            .build();
440
441        assert!(tx.is_ok());
442    }
443
444    #[tokio::test]
445    #[ignore] // Requires network connection
446    async fn test_execute_transaction() {
447        let sdk = ApexSDK::builder()
448            .with_evm_endpoint("https://eth.llamarpc.com")
449            .build()
450            .await
451            .unwrap();
452
453        let tx = sdk
454            .transaction()
455            .from_evm_address("0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb7")
456            .to_evm_address("0x1234567890123456789012345678901234567890")
457            .amount(1000)
458            .build()
459            .unwrap();
460
461        let result = sdk.execute(tx).await;
462        assert!(result.is_ok());
463
464        let tx_result = result.unwrap();
465        assert!(!tx_result.source_tx_hash.is_empty());
466        assert!(tx_result.destination_tx_hash.is_none());
467    }
468
469    #[tokio::test]
470    #[ignore] // Requires network connection
471    async fn test_execute_cross_chain_transaction() {
472        let sdk = ApexSDK::builder()
473            .with_substrate_endpoint("wss://rpc.polkadot.io")
474            .with_evm_endpoint("https://eth.llamarpc.com")
475            .build()
476            .await
477            .unwrap();
478
479        let tx = sdk
480            .transaction()
481            .from_substrate_account("5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY")
482            .to_evm_address("0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb7")
483            .amount(1000)
484            .build()
485            .unwrap();
486
487        assert!(tx.is_cross_chain());
488
489        let result = sdk.execute(tx).await;
490        assert!(result.is_ok());
491
492        let tx_result = result.unwrap();
493        assert!(!tx_result.source_tx_hash.is_empty());
494        assert!(tx_result.destination_tx_hash.is_some());
495    }
496}