bevy_stylus_plugin/
lib.rs

1use bevy::prelude::*;
2use dotenv::dotenv;
3use ethers::prelude::{Provider, Http, SignerMiddleware, LocalWallet, abigen, Middleware};
4use ethers::signers::Signer;
5use eyre::Result;
6use std::{str::FromStr, sync::Arc, fs};
7use ethers::types::{Address, U256};
8use serde::Deserialize;
9use toml;
10
11#[derive(Debug, Deserialize)]
12struct StylusConfig {
13    contract: ContractConfig,
14    deployment: DeploymentConfig,
15    functions: FunctionsConfig,
16}
17
18#[derive(Debug, Deserialize)]
19struct ContractConfig {
20    address: String,
21    network: String,
22    rpc_url: String,
23}
24
25#[derive(Debug, Deserialize)]
26struct DeploymentConfig {
27    tx_hash: String,
28    activation_tx_hash: String,
29    contract_size: String,
30    wasm_size: String,
31    wasm_data_fee: String,
32}
33
34#[derive(Debug, Deserialize)]
35struct FunctionsConfig {
36    signatures: Vec<String>,
37}
38
39// Generate the contract bindings
40abigen!(
41    BlockchainContract,
42    r#"[
43        function getSwordCounts() external view returns (uint256, uint256, uint256)
44        function incrementSword(uint256 color) external
45    ]"#
46);
47
48#[derive(Resource, Clone)]
49pub struct StylusClient {
50    pub contract_client: Option<Arc<SignerMiddleware<Provider<Http>, LocalWallet>>>,
51    pub contract_address: Option<Address>,
52    pub contract: Option<BlockchainContract<SignerMiddleware<Provider<Http>, LocalWallet>>>,
53}
54
55impl StylusClient {
56    /// Convert a u8 to U256 for blockchain operations
57    pub fn u8_to_u256(&self, value: u8) -> U256 {
58        U256::from(value)
59    }
60
61    /// Convert a u64 to U256 for blockchain operations
62    pub fn u64_to_u256(&self, value: u64) -> U256 {
63        U256::from(value)
64    }
65
66    /// Convert a u32 to U256 for blockchain operations
67    pub fn u32_to_u256(&self, value: u32) -> U256 {
68        U256::from(value)
69    }
70
71    /// Convert a u16 to U256 for blockchain operations
72    pub fn u16_to_u256(&self, value: u16) -> U256 {
73        U256::from(value)
74    }
75
76    /// Convert a usize to U256 for blockchain operations
77    pub fn usize_to_u256(&self, value: usize) -> U256 {
78        U256::from(value)
79    }
80
81    /// Get sword counts from the blockchain
82    pub fn get_sword_counts(&self) -> Result<(u64, u64, u64)> {
83        if let Some(contract) = &self.contract {
84            let runtime = tokio::runtime::Runtime::new()?;
85            let result = runtime.block_on(contract.get_sword_counts().call())?;
86            Ok((
87                result.0.as_u64(),
88                result.1.as_u64(),
89                result.2.as_u64(),
90            ))
91        } else {
92            Err(eyre::eyre!("Contract not initialized"))
93        }
94    }
95
96    /// Increment sword count on the blockchain
97    pub fn increment_sword(&self, color: u8) -> Result<()> {
98        if let Some(contract) = &self.contract {
99            let runtime = tokio::runtime::Runtime::new()?;
100            let _ = runtime.block_on(contract.increment_sword(self.u8_to_u256(color)).send())?;
101            Ok(())
102        } else {
103            Err(eyre::eyre!("Contract not initialized"))
104        }
105    }
106
107    /// Increment sword count on the blockchain asynchronously (spawns a thread)
108    pub fn increment_sword_async(&self, color: u8) {
109        if let Some(contract) = &self.contract {
110            let contract = contract.clone();
111            let color_u256 = self.u8_to_u256(color);
112            std::thread::spawn(move || {
113                tokio::runtime::Runtime::new().unwrap().block_on(async {
114                    let _ = contract.increment_sword(color_u256).send().await;
115                });
116            });
117        }
118    }
119}
120
121pub struct StylusPlugin;
122
123impl Plugin for StylusPlugin {
124    fn build(&self, app: &mut App) {
125        app.add_systems(Startup, init_stylus);
126    }
127}
128
129pub fn init_stylus(mut commands: Commands) {
130    let stylus_client = std::thread::spawn(|| {
131        tokio::runtime::Runtime::new()
132            .unwrap()
133            .block_on(async {
134                init_stylus_client().await
135            })
136    })
137    .join()
138    .unwrap();
139
140    match stylus_client {
141        Ok(client) => {
142            println!("✅ Stylus client initialized successfully");
143            commands.insert_resource(client);
144        }
145        Err(e) => {
146            println!("❌ Failed to initialize Stylus client: {:?}", e);
147            commands.insert_resource(StylusClient {
148                contract_client: None,
149                contract_address: None,
150                contract: None,
151            });
152        }
153    }
154}
155
156async fn init_stylus_client() -> Result<StylusClient> {
157    dotenv().ok();
158
159    let mut client = StylusClient {
160        contract_client: None,
161        contract_address: None,
162        contract: None,
163    };
164
165    // Read Stylus.toml configuration
166    let config_content = fs::read_to_string("Stylus.toml")
167        .map_err(|e| eyre::eyre!("Failed to read Stylus.toml: {}", e))?;
168    
169    let config: StylusConfig = toml::from_str(&config_content)
170        .map_err(|e| eyre::eyre!("Failed to parse Stylus.toml: {}", e))?;
171
172    println!("📋 Loaded Stylus configuration:");
173    println!("  - Contract Address: {}", config.contract.address);
174    println!("  - Network: {}", config.contract.network);
175    println!("  - RPC URL: {}", config.contract.rpc_url);
176    println!("  - Functions: {} signatures", config.functions.signatures.len());
177
178    // Get private key from environment or use default
179    let private_key = std::env::var("PRIVATE_KEY")
180        .unwrap_or_else(|_| "0xb6b15c8cb491557369f3c7d2c287b053eb229daa9c22138887752191c9520659".to_string());
181
182    println!("🔑 Using private key: {}", if private_key.len() > 10 { 
183        format!("{}...{}", &private_key[..10], &private_key[private_key.len()-10..]) 
184    } else { 
185        private_key.clone() 
186    });
187
188    // Create provider and wallet
189    let provider = Provider::<Http>::try_from(&config.contract.rpc_url)?;
190    let wallet = LocalWallet::from_str(&private_key)?;
191    let chain_id = provider.get_chainid().await?.as_u64();
192    let client_arc = Arc::new(SignerMiddleware::new(
193        provider,
194        wallet.with_chain_id(chain_id),
195    ));
196
197    let contract_address: Address = config.contract.address.parse()?;
198    let contract = BlockchainContract::new(contract_address, client_arc.clone());
199
200    client.contract_client = Some(client_arc);
201    client.contract_address = Some(contract_address);
202    client.contract = Some(contract);
203
204    println!("✅ Stylus client initialized successfully!");
205
206    Ok(client)
207}
208
209// Re-export the contract type for convenience
210pub use BlockchainContract;