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}