use crate::jsonrpc::client::HTTPClient;
use crate::jsonrpc::error::Web3Error;
use crate::types::{Block, Log, NewFilter, TransactionRequest, TransactionResponse};
use crate::types::{ConciseBlock, ConciseXdaiBlock, Data, SendTxOption, UnpaddedHex, XdaiBlock};
use clarity::abi::{encode_call, Token};
use clarity::utils::bytes_to_hex_str;
use clarity::{Address, PrivateKey, Transaction};
use num256::Uint256;
use std::{cmp::min, time::Duration};
use std::{sync::Arc, time::Instant};
use tokio::time::delay_for;
#[derive(Clone)]
pub struct Web3 {
url: String,
jsonrpc_client: Arc<Box<HTTPClient>>,
timeout: Duration,
}
impl Web3 {
pub fn new(url: &str, timeout: Duration) -> Self {
Self {
jsonrpc_client: Arc::new(Box::new(HTTPClient::new(url))),
timeout,
url: url.to_string(),
}
}
pub fn get_timeout(&self) -> Duration {
self.timeout
}
pub fn get_url(&self) -> String {
self.url.clone()
}
pub async fn eth_accounts(&self) -> Result<Vec<Address>, Web3Error> {
self.jsonrpc_client
.request_method("eth_accounts", Vec::<String>::new(), self.timeout, None)
.await
}
pub async fn net_version(&self) -> Result<u64, Web3Error> {
let ret: Result<String, Web3Error> = self
.jsonrpc_client
.request_method("net_version", Vec::<String>::new(), self.timeout, None)
.await;
Ok(ret?.parse()?)
}
pub async fn eth_new_filter(&self, new_filter: NewFilter) -> Result<Uint256, Web3Error> {
self.jsonrpc_client
.request_method("eth_newFilter", vec![new_filter], self.timeout, None)
.await
}
pub async fn eth_get_filter_changes(&self, filter_id: Uint256) -> Result<Vec<Log>, Web3Error> {
self.jsonrpc_client
.request_method(
"eth_getFilterChanges",
vec![format!("{:#x}", filter_id.clone())],
self.timeout,
Some(10_000_000),
)
.await
}
pub async fn eth_uninstall_filter(&self, filter_id: Uint256) -> Result<bool, Web3Error> {
self.jsonrpc_client
.request_method(
"eth_uninstallFilter",
vec![format!("{:#x}", filter_id.clone())],
self.timeout,
None,
)
.await
}
pub async fn eth_get_logs(&self, new_filter: NewFilter) -> Result<Vec<Log>, Web3Error> {
self.jsonrpc_client
.request_method(
"eth_getLogs",
vec![new_filter],
self.timeout,
Some(10_000_000),
)
.await
}
pub async fn eth_get_transaction_count(&self, address: Address) -> Result<Uint256, Web3Error> {
self.jsonrpc_client
.request_method(
"eth_getTransactionCount",
vec![address.to_string(), "latest".to_string()],
self.timeout,
None,
)
.await
}
pub async fn eth_gas_price(&self) -> Result<Uint256, Web3Error> {
self.jsonrpc_client
.request_method("eth_gasPrice", Vec::<String>::new(), self.timeout, None)
.await
}
pub async fn eth_estimate_gas(
&self,
transaction: TransactionRequest,
) -> Result<Uint256, Web3Error> {
self.jsonrpc_client
.request_method("eth_estimateGas", vec![transaction], self.timeout, None)
.await
}
pub async fn eth_get_balance(&self, address: Address) -> Result<Uint256, Web3Error> {
self.jsonrpc_client
.request_method(
"eth_getBalance",
vec![address.to_string(), "latest".to_string()],
self.timeout,
None,
)
.await
}
pub async fn eth_send_transaction(
&self,
transactions: Vec<TransactionRequest>,
) -> Result<Uint256, Web3Error> {
self.jsonrpc_client
.request_method("eth_sendTransaction", transactions, self.timeout, None)
.await
}
pub async fn eth_call(&self, transaction: TransactionRequest) -> Result<Data, Web3Error> {
self.jsonrpc_client
.request_method("eth_call", (transaction, "latest"), self.timeout, None)
.await
}
pub async fn eth_block_number(&self) -> Result<Uint256, Web3Error> {
self.jsonrpc_client
.request_method("eth_blockNumber", Vec::<String>::new(), self.timeout, None)
.await
}
pub async fn eth_get_block_by_number(&self, block_number: Uint256) -> Result<Block, Web3Error> {
self.jsonrpc_client
.request_method(
"eth_getBlockByNumber",
(format!("{:#x}", block_number), true),
self.timeout,
Some(5_000_000),
)
.await
}
pub async fn xdai_get_block_by_number(
&self,
block_number: Uint256,
) -> Result<XdaiBlock, Web3Error> {
self.jsonrpc_client
.request_method(
"eth_getBlockByNumber",
(format!("{:#x}", block_number), true),
self.timeout,
Some(5_000_000),
)
.await
}
pub async fn eth_get_concise_block_by_number(
&self,
block_number: Uint256,
) -> Result<ConciseBlock, Web3Error> {
self.jsonrpc_client
.request_method(
"eth_getBlockByNumber",
(format!("{:#x}", block_number), false),
self.timeout,
None,
)
.await
}
pub async fn xdai_get_concise_block_by_number(
&self,
block_number: Uint256,
) -> Result<ConciseXdaiBlock, Web3Error> {
self.jsonrpc_client
.request_method(
"eth_getBlockByNumber",
(format!("{:#x}", block_number), false),
self.timeout,
None,
)
.await
}
pub async fn eth_get_latest_block(&self) -> Result<ConciseBlock, Web3Error> {
self.jsonrpc_client
.request_method(
"eth_getBlockByNumber",
("latest", false),
self.timeout,
None,
)
.await
}
pub async fn xdai_get_latest_block(&self) -> Result<ConciseXdaiBlock, Web3Error> {
self.jsonrpc_client
.request_method(
"eth_getBlockByNumber",
("latest", false),
self.timeout,
None,
)
.await
}
pub async fn eth_get_latest_block_full(&self) -> Result<Block, Web3Error> {
self.jsonrpc_client
.request_method(
"eth_getBlockByNumber",
("latest", true),
self.timeout,
Some(5_000_000),
)
.await
}
pub async fn xdai_get_latest_block_full(&self) -> Result<XdaiBlock, Web3Error> {
self.jsonrpc_client
.request_method(
"eth_getBlockByNumber",
("latest", true),
self.timeout,
Some(5_000_000),
)
.await
}
pub async fn eth_send_raw_transaction(&self, data: Vec<u8>) -> Result<Uint256, Web3Error> {
self.jsonrpc_client
.request_method(
"eth_sendRawTransaction",
vec![format!("0x{}", bytes_to_hex_str(&data))],
self.timeout,
None,
)
.await
}
pub async fn eth_get_transaction_by_hash(
&self,
hash: Uint256,
) -> Result<Option<TransactionResponse>, Web3Error> {
self.jsonrpc_client
.request_method(
"eth_getTransactionByHash",
vec![format!("{:#066x}", hash)],
self.timeout,
None,
)
.await
}
pub async fn evm_snapshot(&self) -> Result<Uint256, Web3Error> {
self.jsonrpc_client
.request_method("evm_snapshot", Vec::<String>::new(), self.timeout, None)
.await
}
pub async fn evm_revert(&self, snapshot_id: Uint256) -> Result<Uint256, Web3Error> {
self.jsonrpc_client
.request_method(
"evm_revert",
vec![format!("{:#066x}", snapshot_id)],
self.timeout,
None,
)
.await
}
pub async fn send_transaction(
&self,
to_address: Address,
data: Vec<u8>,
value: Uint256,
own_address: Address,
secret: PrivateKey,
options: Vec<SendTxOption>,
) -> Result<Uint256, Web3Error> {
let mut gas_price = None;
let mut gas_price_multiplier = 1u64.into();
let mut gas_limit = None;
let mut network_id = None;
let our_balance = self.eth_get_balance(own_address).await?;
let mut nonce = self.eth_get_transaction_count(own_address).await?;
for option in options {
match option {
SendTxOption::GasPrice(gp) => gas_price = Some(gp),
SendTxOption::GasPriceMultiplier(gpm) => gas_price_multiplier = gpm,
SendTxOption::GasLimit(gl) => gas_limit = Some(gl),
SendTxOption::NetworkId(ni) => network_id = Some(ni),
SendTxOption::Nonce(n) => nonce = n,
}
}
let mut gas_price = if let Some(gp) = gas_price {
gp
} else {
self.eth_gas_price().await? * gas_price_multiplier
};
let gas_limit = if let Some(gl) = gas_limit {
gl
} else {
let gas_price: Uint256 = 1u8.into();
let gas_limit = min((u64::MAX - 1).into(), our_balance.clone());
self.eth_estimate_gas(TransactionRequest {
from: Some(own_address),
to: to_address,
nonce: Some(nonce.clone().into()),
gas_price: Some(gas_price.into()),
gas: Some(gas_limit.into()),
value: Some(value.clone().into()),
data: Some(data.clone().into()),
})
.await?
};
let network_id = if let Some(ni) = network_id {
ni
} else {
self.net_version().await?
};
if gas_price.clone() * gas_limit.clone() > our_balance {
gas_price = our_balance / gas_limit.clone();
}
let transaction = Transaction {
to: to_address,
nonce,
gas_price,
gas_limit,
value,
data,
signature: None,
};
let transaction = transaction.sign(&secret, Some(network_id));
self.eth_send_raw_transaction(
transaction
.to_bytes()
.expect("transaction.to_bytes() failed"),
)
.await
}
pub async fn contract_call(
&self,
contract_address: Address,
sig: &str,
tokens: &[Token],
own_address: Address,
) -> Result<Vec<u8>, Web3Error> {
let our_balance = self.eth_get_balance(own_address).await?;
let nonce = self.eth_get_transaction_count(own_address).await?;
let payload = encode_call(sig, tokens)?;
let gas_price: Uint256 = 1u8.into();
let gas_limit = min((u64::MAX - 1).into(), our_balance);
let transaction = TransactionRequest {
from: Some(own_address),
to: contract_address,
nonce: Some(UnpaddedHex(nonce)),
gas: Some(gas_limit.into()),
gas_price: Some(UnpaddedHex(gas_price)),
value: Some(UnpaddedHex(0u64.into())),
data: Some(Data(payload)),
};
let bytes = match self.eth_call(transaction).await {
Ok(val) => val,
Err(e) => return Err(e),
};
Ok(bytes.0)
}
pub async fn wait_for_transaction(
&self,
tx_hash: Uint256,
timeout: Duration,
blocks_to_wait: Option<Uint256>,
) -> Result<TransactionResponse, Web3Error> {
let start = Instant::now();
loop {
delay_for(Duration::from_secs(1)).await;
match self.eth_get_transaction_by_hash(tx_hash.clone()).await {
Ok(maybe_transaction) => {
if let Some(transaction) = maybe_transaction {
if blocks_to_wait.clone().is_none() && transaction.block_number.is_some() {
return Ok(transaction);
}
else if let (Some(blocks_to_wait), Some(tx_block)) =
(blocks_to_wait.clone(), transaction.block_number.clone())
{
let current_block = self.eth_block_number().await?;
if current_block > blocks_to_wait
&& current_block - blocks_to_wait >= tx_block
{
return Ok(transaction);
}
}
}
}
Err(e) => return Err(e),
}
if Instant::now() - start > timeout {
return Err(Web3Error::TransactionTimeout);
}
}
}
}
#[test]
#[ignore]
fn test_complex_response() {
use actix::Arbiter;
use actix::System;
System::run(|| {
let web3 = Web3::new("https://eth.althea.net", Duration::from_secs(5));
let txid1 = "0x8b9ef028f99016cd3cb8d4168df7491a0bf44f08b678d37f63ab61e782c500ab"
.parse()
.unwrap();
Arbiter::spawn(async move {
let val = web3.eth_get_transaction_by_hash(txid1).await;
let val = val.expect("Actix failure");
let response = val.expect("Failed to parse transaction response");
assert!(response.block_number.unwrap() > 10u32.into());
System::current().stop();
});
})
.expect("Actix failure");
}
#[test]
fn test_transaction_count_response() {
use actix::Arbiter;
use actix::System;
System::run(|| {
let web3 = Web3::new("https://eth.althea.net", Duration::from_secs(5));
let address: Address = "0x04668ec2f57cc15c381b461b9fedab5d451c8f7f"
.parse()
.unwrap();
Arbiter::spawn(async move {
let val = web3.eth_get_transaction_count(address).await;
let val = val.unwrap();
assert!(val > 0u32.into());
System::current().stop();
});
})
.expect("Actix failure");
}
#[test]
fn test_block_response() {
env_logger::init();
use actix::Arbiter;
use actix::System;
System::run(|| {
let web3 = Web3::new("https://eth.altheamesh.com", Duration::from_secs(5));
Arbiter::spawn(async move {
let val = web3.eth_get_latest_block().await;
let val = val.expect("Actix failure");
assert!(val.number > 10u32.into());
System::current().stop();
});
})
.expect("Actix failure");
}
#[test]
fn test_dai_block_response() {
use actix::Arbiter;
use actix::System;
System::run(|| {
let web3 = Web3::new("https://dai.althea.net", Duration::from_secs(5));
Arbiter::spawn(async move {
let val = web3.xdai_get_latest_block().await;
let val = val.expect("Actix failure");
assert!(val.number > 10u32.into());
System::current().stop();
});
})
.expect("Actix failure");
}