use bevy::prelude::*;
use dotenv::dotenv;
use ethers::prelude::{Provider, Http, SignerMiddleware, LocalWallet, abigen, Middleware};
use ethers::signers::Signer;
use eyre::Result;
use std::{str::FromStr, sync::Arc, fs};
use ethers::types::{Address, U256};
use serde::Deserialize;
use toml;
#[derive(Debug, Deserialize)]
struct StylusConfig {
contract: ContractConfig,
deployment: DeploymentConfig,
functions: FunctionsConfig,
}
#[derive(Debug, Deserialize)]
struct ContractConfig {
address: String,
network: String,
rpc_url: String,
}
#[derive(Debug, Deserialize)]
struct DeploymentConfig {
tx_hash: String,
activation_tx_hash: String,
contract_size: String,
wasm_size: String,
wasm_data_fee: String,
}
#[derive(Debug, Deserialize)]
struct FunctionsConfig {
signatures: Vec<String>,
}
abigen!(
BlockchainContract,
r#"[
function getSwordCounts() external view returns (uint256, uint256, uint256)
function incrementSword(uint256 color) external
]"#
);
#[derive(Resource, Clone)]
pub struct StylusClient {
pub contract_client: Option<Arc<SignerMiddleware<Provider<Http>, LocalWallet>>>,
pub contract_address: Option<Address>,
pub contract: Option<BlockchainContract<SignerMiddleware<Provider<Http>, LocalWallet>>>,
}
impl StylusClient {
pub fn u8_to_u256(&self, value: u8) -> U256 {
U256::from(value)
}
pub fn u64_to_u256(&self, value: u64) -> U256 {
U256::from(value)
}
pub fn u32_to_u256(&self, value: u32) -> U256 {
U256::from(value)
}
pub fn u16_to_u256(&self, value: u16) -> U256 {
U256::from(value)
}
pub fn usize_to_u256(&self, value: usize) -> U256 {
U256::from(value)
}
pub fn get_sword_counts(&self) -> Result<(u64, u64, u64)> {
if let Some(contract) = &self.contract {
let runtime = tokio::runtime::Runtime::new()?;
let result = runtime.block_on(contract.get_sword_counts().call())?;
Ok((
result.0.as_u64(),
result.1.as_u64(),
result.2.as_u64(),
))
} else {
Err(eyre::eyre!("Contract not initialized"))
}
}
pub fn increment_sword(&self, color: u8) -> Result<()> {
if let Some(contract) = &self.contract {
let runtime = tokio::runtime::Runtime::new()?;
let _ = runtime.block_on(contract.increment_sword(self.u8_to_u256(color)).send())?;
Ok(())
} else {
Err(eyre::eyre!("Contract not initialized"))
}
}
pub fn increment_sword_async(&self, color: u8) {
if let Some(contract) = &self.contract {
let contract = contract.clone();
let color_u256 = self.u8_to_u256(color);
std::thread::spawn(move || {
tokio::runtime::Runtime::new().unwrap().block_on(async {
let _ = contract.increment_sword(color_u256).send().await;
});
});
}
}
}
pub struct StylusPlugin;
impl Plugin for StylusPlugin {
fn build(&self, app: &mut App) {
app.add_systems(Startup, init_stylus);
}
}
pub fn init_stylus(mut commands: Commands) {
let stylus_client = std::thread::spawn(|| {
tokio::runtime::Runtime::new()
.unwrap()
.block_on(async {
init_stylus_client().await
})
})
.join()
.unwrap();
match stylus_client {
Ok(client) => {
println!("✅ Stylus client initialized successfully");
commands.insert_resource(client);
}
Err(e) => {
println!("❌ Failed to initialize Stylus client: {:?}", e);
commands.insert_resource(StylusClient {
contract_client: None,
contract_address: None,
contract: None,
});
}
}
}
async fn init_stylus_client() -> Result<StylusClient> {
dotenv().ok();
let mut client = StylusClient {
contract_client: None,
contract_address: None,
contract: None,
};
let config_content = fs::read_to_string("Stylus.toml")
.map_err(|e| eyre::eyre!("Failed to read Stylus.toml: {}", e))?;
let config: StylusConfig = toml::from_str(&config_content)
.map_err(|e| eyre::eyre!("Failed to parse Stylus.toml: {}", e))?;
println!("📋 Loaded Stylus configuration:");
println!(" - Contract Address: {}", config.contract.address);
println!(" - Network: {}", config.contract.network);
println!(" - RPC URL: {}", config.contract.rpc_url);
println!(" - Functions: {} signatures", config.functions.signatures.len());
let private_key = std::env::var("PRIVATE_KEY")
.unwrap_or_else(|_| "0xb6b15c8cb491557369f3c7d2c287b053eb229daa9c22138887752191c9520659".to_string());
println!("🔑 Using private key: {}", if private_key.len() > 10 {
format!("{}...{}", &private_key[..10], &private_key[private_key.len()-10..])
} else {
private_key.clone()
});
let provider = Provider::<Http>::try_from(&config.contract.rpc_url)?;
let wallet = LocalWallet::from_str(&private_key)?;
let chain_id = provider.get_chainid().await?.as_u64();
let client_arc = Arc::new(SignerMiddleware::new(
provider,
wallet.with_chain_id(chain_id),
));
let contract_address: Address = config.contract.address.parse()?;
let contract = BlockchainContract::new(contract_address, client_arc.clone());
client.contract_client = Some(client_arc);
client.contract_address = Some(contract_address);
client.contract = Some(contract);
println!("✅ Stylus client initialized successfully!");
Ok(client)
}
pub use BlockchainContract;