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}