Skip to content

Commit

Permalink
Merge pull request #4 from starkbamse/ethers-port
Browse files Browse the repository at this point in the history
Ethers port
  • Loading branch information
starkbamse committed May 16, 2024
2 parents fbba781 + 010d8c5 commit 83d080b
Show file tree
Hide file tree
Showing 10 changed files with 1,735 additions and 1,015 deletions.
2,623 changes: 1,671 additions & 952 deletions Cargo.lock

Large diffs are not rendered by default.

13 changes: 8 additions & 5 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
[lib]
crate-type = ["cdylib", "rlib"]

[package]
name = "rustlink"
version = "0.0.1"
edition = "2021"
license = "MIT OR Apache-2.0"
description = "A lightweight and easy-to-use library for periodically retrieving data from the Chainlink decentralized data feed."
repository = "https://github.com/starkbamse/rustlink"
include = ["**/*.json"]
keywords = ["crypto", "cryptocurrencies","chainlink","prices"]
include = ["IAggregatorV3Interface.json","src/**/*.rs", "Cargo.toml"]
keywords = ["crypto", "cryptocurrencies","chainlink","prices","bitcoin","ethereum","evm"]
categories = ["wasm"]

[dependencies]
log = "0.4.21"
alloy = { git = "https://github.com/alloy-rs/alloy", version = "0.1.0", features = ["rpc-types-eth","eips","signers","signer-wallet","consensus","network","providers","transports","transport-http","contract"] }
reqwest = "0.12.4"
bincode = "1.3.3"
serde = "1.0.201"
Expand All @@ -25,6 +28,8 @@ wasm-bindgen-futures = "0.4.42"
serde-wasm-bindgen = "0.6.5"
futures = "0.3.30"
web-sys = "0.3.69"
serde_json = "1.0.117"
ethers = "2.0.14"

# Dependencies for non-WASM targets
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
Expand All @@ -37,5 +42,3 @@ tokio = "1.37.0"
[lints.clippy]
empty_docs = "allow"

[lib]
crate-type = ["cdylib", "rlib"]
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,13 +52,13 @@ rustlink = "0.0.1"
**For the browser:**

```bash
./web-build.sh
wasm-pack build --target web --out-name rustlink --out-dir <directory>
```

**For Node.js:**

```bash
./node-build.sh
wasm-pack build --target nodejs --out-name rustlink --out-dir <directory>
```


Expand Down
1 change: 0 additions & 1 deletion node-build.sh

This file was deleted.

22 changes: 13 additions & 9 deletions src/core.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
use crate::{error::Error, fetcher::fetch_rounds, interface::Round};
use alloy::{
providers::{ProviderBuilder, RootProvider},
transports::http::Http,
};

use async_std::channel::{unbounded, Receiver, RecvError, Sender};
use ethers::{providers::{Http, Provider}, types::Address};
use js_sys::Function;
use reqwest::{Client, Url};
use serde_wasm_bindgen::{from_value, to_value};
use workflow_rs::core::cfg_if;
use std::str::FromStr;
Expand All @@ -20,8 +17,8 @@ use wasm_bindgen_futures::spawn_local;
#[derive(Clone)]
pub struct Configuration {
pub fetch_interval_seconds: u64,
pub contracts: Vec<(String, String)>,
pub provider: RootProvider<Http<Client>>,
pub contracts: Vec<(String, Address)>,
pub provider: Provider<Http>,
}

/// ## Rustlink instance. This is the main struct that you will interact with.
Expand Down Expand Up @@ -108,14 +105,19 @@ impl Rustlink {
contracts: Vec<(String, String)>,
) -> Result<Self, Error> {

let provider = ProviderBuilder::new().on_http(Url::from_str(rpc_url).unwrap());
let provider = Provider::try_from(rpc_url).expect("Invalid RPC URL");
let (termination_send, termination_recv) = unbounded::<()>();
let (shutdown_send, shutdown_recv) = unbounded::<()>();

let parsed_contracts=contracts.iter().map(|(identifier, address)| {
(identifier.clone(), Address::from_str(address).expect("Invalid contract address specified"))
}).collect();

Ok(Rustlink {
configuration: Configuration {
fetch_interval_seconds,
provider,
contracts,
contracts:parsed_contracts,
},
reflector,
termination_send,
Expand Down Expand Up @@ -145,6 +147,7 @@ impl Rustlink {

/// RustlinkJS is a JavaScript wrapper for Rustlink.
/// It allows you to create a Rustlink instance in JavaScript and start fetching data when you use WASM.
/// You should use this one when you want to use Rustlink in a web environment.
#[wasm_bindgen]
pub struct RustlinkJS {
rustlink: Rustlink,
Expand Down Expand Up @@ -173,6 +176,7 @@ cfg_if! {

#[wasm_bindgen]
extern "C" {
// A JavaScript array of contract tuples
#[wasm_bindgen(extends = js_sys::Function, typescript_type = "Contract[]")]
pub type Contracts;
}
Expand Down
8 changes: 5 additions & 3 deletions src/fetcher/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
use std::time::Duration;

use async_std::stream::StreamExt;
use ethers::abi::AbiError;
use ethers::types::Address;
use futures::{select, FutureExt};

use super::interface::{ChainlinkContract, Round};
Expand All @@ -11,8 +13,8 @@ use crate::core::{Configuration, Rustlink};
async fn fetch_round_data_for_contract(
rustlink_configuration: &Configuration,
identifier: &str,
address: &str,
) -> Result<Round, alloy::contract::Error> {
address: Address,
) -> Result<Round, AbiError> {
let contract =
ChainlinkContract::new(&rustlink_configuration.provider, identifier, address).await?;
contract.latest_round_data().await
Expand Down Expand Up @@ -41,7 +43,7 @@ pub async fn fetch_rounds(rustlink: Rustlink) {
let address = &contract_configuration.1;

// Fetch price data and attempt to send it via the channel.
match fetch_round_data_for_contract(&rustlink.configuration, identifier, address).await
match fetch_round_data_for_contract(&rustlink.configuration, identifier, *address).await
{
Ok(price_data) => {
match rustlink.reflector {
Expand Down
File renamed without changes.
75 changes: 33 additions & 42 deletions src/interface/mod.rs
Original file line number Diff line number Diff line change
@@ -1,23 +1,11 @@
use alloy::{
contract::Error, primitives::Uint, providers::RootProvider, sol, transports::http::Http,
};
use reqwest::Client;
use std::sync::Arc;
use serde::{Deserialize, Serialize};
use ethers::{abi::{Abi, AbiError}, contract::Contract, providers::{Http, Provider}, types::{Address, U256}};

use self::IAggregatorV3Interface::IAggregatorV3InterfaceInstance;



sol!(
#[allow(missing_docs)]
#[sol(rpc)]
IAggregatorV3Interface,
"src/abi/IAggregatorV3Interface.json"
);

#[derive(Clone)]
pub struct ChainlinkContract<'a> {
pub contract: IAggregatorV3InterfaceInstance<Http<Client>, &'a RootProvider<Http<Client>>>,
pub contract: Contract<&'a Provider<Http>>,
pub identifier: &'a str,
pub decimals: u8,
}
Expand All @@ -34,9 +22,9 @@ pub struct Round {
/// Answered in round
pub answered_in_round: u128,
/// Timestamp for when the aggregator started collecting data
pub started_at: Uint<256, 4>,
pub started_at: U256,
/// Timestamp for when the aggregator posted the price update
pub updated_at: Uint<256, 4>,
pub updated_at: U256,
/// Answer of this round
pub answer: f64,
}
Expand All @@ -45,14 +33,16 @@ impl<'a> ChainlinkContract<'a> {
/// Creates a new instance of a chainlink price aggregator. This is just a wrapper
/// function to simplify the interactions with the contract.
pub async fn new(
provider: &'a RootProvider<Http<Client>>,
provider: &'a Provider<Http>,
identifier: &'a str,
contract_address: &str,
) -> Result<ChainlinkContract<'a>, Error> {
let contract = IAggregatorV3Interface::new(contract_address.parse().unwrap(), provider);
contract_address: Address,
) -> Result<ChainlinkContract<'a>, AbiError> {
let abi:Abi=serde_json::from_str(include_str!("IAggregatorV3Interface.json")).unwrap();
let contract = Contract::new(contract_address, abi, Arc::new(provider));

let decimals=contract.method::<_,U256>("decimals", ()).unwrap()
.call().await.unwrap().as_u64() as u8;

let IAggregatorV3Interface::decimalsReturn { _0: decimals } =
contract.decimals().call().await?;
Ok(ChainlinkContract {
contract,
decimals,
Expand All @@ -62,14 +52,18 @@ impl<'a> ChainlinkContract<'a> {

/// Retrieves the latest price of this underlying asset
/// from the chainlink decentralized data feed
pub async fn latest_round_data(&self) -> Result<Round, Error> {
let IAggregatorV3Interface::latestRoundDataReturn {
roundId,
answer,
startedAt,
updatedAt,
answeredInRound,
} = self.contract.latestRoundData().call().await?;
pub async fn latest_round_data(&self) -> Result<Round, AbiError> {
let (round_id, answer, started_at, updated_at, answered_in_round): (
u128,
u128,
U256,
U256,
u128,
) = self
.contract
.method("latestRoundData", ())?
.call()
.await.unwrap();

// Convert the answer on contract to a string.
let float_answer: f64 = answer.to_string().parse().unwrap();
Expand All @@ -79,10 +73,10 @@ impl<'a> ChainlinkContract<'a> {

Ok(Round {
identifier: self.identifier.to_string(),
round_id: roundId,
answered_in_round: answeredInRound,
started_at: startedAt,
updated_at: updatedAt,
round_id,
answered_in_round,
started_at,
updated_at,
answer: human_answer,
})
}
Expand All @@ -91,21 +85,18 @@ impl<'a> ChainlinkContract<'a> {
#[cfg(test)]
mod tests {

use alloy::providers::ProviderBuilder;
use reqwest::Url;
use std::str::FromStr;

use ethers::{abi::Address, providers::Provider};
use crate::interface::ChainlinkContract;

#[tokio::test]
async fn valid_answer() {
let provider = ProviderBuilder::new()
.on_http(Url::from_str("https://bsc-dataseed1.binance.org/").unwrap());

let provider=Provider::try_from("https://bsc-dataseed1.binance.org/").unwrap();

let chainlink_contract = ChainlinkContract::new(
&provider,
"ETH",
"0x9ef1B8c0E4F7dc8bF5719Ea496883DC6401d5b2e",
"0x9ef1B8c0E4F7dc8bF5719Ea496883DC6401d5b2e".parse::<Address>().unwrap(),
)
.await
.unwrap();
Expand Down
3 changes: 3 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
/// # Rustlink
/// This library provides a simple interface to fetch price data from the Chainlink decentralized data feed.
/// Core is the main module that contains the main struct `Rustlink` that you will need to interact with.
pub mod core;
mod error;
mod fetcher;
Expand Down
1 change: 0 additions & 1 deletion web-build.sh

This file was deleted.

0 comments on commit 83d080b

Please sign in to comment.