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