bevy_stylus_plugin/
lib.rs1use 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
39abigen!(
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 pub fn u8_to_u256(&self, value: u8) -> U256 {
58 U256::from(value)
59 }
60
61 pub fn u64_to_u256(&self, value: u64) -> U256 {
63 U256::from(value)
64 }
65
66 pub fn u32_to_u256(&self, value: u32) -> U256 {
68 U256::from(value)
69 }
70
71 pub fn u16_to_u256(&self, value: u16) -> U256 {
73 U256::from(value)
74 }
75
76 pub fn usize_to_u256(&self, value: usize) -> U256 {
78 U256::from(value)
79 }
80
81 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 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 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 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 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 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
209pub use BlockchainContract;