use bevy::prelude::*;
use std::collections::VecDeque;
use std::str::FromStr;
use std::sync::Arc;
use crate::tokio::TokioRuntime;
use starknet::accounts::single_owner::SignError;
use starknet::signers::local_wallet::SignError as LocalWalletSignError;
use starknet::{
accounts::{Account, AccountError, ExecutionEncoding, SingleOwnerAccount},
core::types::{Call, Felt, InvokeTransactionResult},
providers::{AnyProvider, JsonRpcClient, Provider, Url, jsonrpc::HttpTransport},
signers::{LocalWallet, SigningKey},
};
use tokio::task::JoinHandle;
#[derive(Resource, Default)]
pub struct StarknetConnection {
connecting_task: Option<JoinHandle<Arc<SingleOwnerAccount<AnyProvider, LocalWallet>>>>,
account: Option<Arc<SingleOwnerAccount<AnyProvider, LocalWallet>>>,
pending_txs: VecDeque<
JoinHandle<Result<InvokeTransactionResult, AccountError<SignError<LocalWalletSignError>>>>,
>,
}
impl StarknetConnection {
pub fn is_connected(&self) -> bool {
self.account.is_some()
}
pub fn is_connecting(&self) -> bool {
self.connecting_task.is_some()
}
pub fn pending_tx_count(&self) -> usize {
self.pending_txs.len()
}
}
#[derive(Resource, Clone)]
pub struct DefaultStarknetConfig {
pub rpc_url: String,
pub account_address: String,
pub private_key: String,
}
impl Default for DefaultStarknetConfig {
fn default() -> Self {
Self {
rpc_url: std::env::var("STARKNET_RPC_URL").unwrap_or_default(),
account_address: std::env::var("STARKNET_ACCOUNT_ADDRESS").unwrap_or_default(),
private_key: std::env::var("STARKNET_PRIVATE_KEY").unwrap_or_default(),
}
}
}
pub fn init_starknet_connection(
runtime: Res<TokioRuntime>,
config: Res<DefaultStarknetConfig>,
mut sn: ResMut<StarknetConnection>,
) {
if sn.connecting_task.is_none() && sn.account.is_none() {
let config_clone = config.clone();
let handle = runtime
.runtime
.spawn(async move { connect_to_starknet(config_clone).await });
sn.connecting_task = Some(handle);
info!("Connecting to Starknet...");
}
}
pub fn execute_transaction(
runtime: Res<TokioRuntime>,
mut sn: ResMut<StarknetConnection>,
calls: Vec<Call>,
) -> bool {
if let Some(account) = sn.account.clone() {
let task = runtime.runtime.spawn(async move {
let tx = account.execute_v3(calls);
tx.send().await
});
sn.pending_txs.push_back(task);
true
} else {
false
}
}
pub fn check_sn_task(runtime: Res<TokioRuntime>, mut sn: ResMut<StarknetConnection>) {
if let Some(task) = &mut sn.connecting_task {
if let Ok(account) = runtime.runtime.block_on(async { task.await }) {
info!("Connected to Starknet!");
sn.account = Some(account);
sn.connecting_task = None;
}
}
if !sn.pending_txs.is_empty() && sn.account.is_some() {
if let Some(task) = sn.pending_txs.pop_front() {
if let Ok(Ok(result)) = runtime.runtime.block_on(async { task.await }) {
info!("Transaction completed: {:#x}", result.transaction_hash);
}
}
}
}
pub async fn connect_to_starknet(
config: DefaultStarknetConfig,
) -> Arc<SingleOwnerAccount<AnyProvider, LocalWallet>> {
let provider = AnyProvider::JsonRpcHttp(JsonRpcClient::new(HttpTransport::new(
Url::parse(&config.rpc_url).expect("Invalid RPC URL"),
)));
let account_addr = Felt::from_str(&config.account_address).expect("Invalid account address");
let chain_id = provider.chain_id().await.unwrap();
let signer = LocalWallet::from(SigningKey::from_secret_scalar(
Felt::from_str(&config.private_key).expect("Invalid private key"),
));
Arc::new(SingleOwnerAccount::new(
provider,
signer,
account_addr,
chain_id,
ExecutionEncoding::New,
))
}