DEV Community

hideyoshi
hideyoshi

Posted on • Originally published at chainsight.network

Step-by-step creating an EVM Price Oracle with Chainsight

When we start a defi project, we might need a price oracle to get the price of the token. Usually we use Chainlink or Band Protocol for a famous token.
But if we want to get the price of a new token or a token that is not famous, we might need to create our own price oracle.
In this article, I will show you how to create an EVM oracle with Chainsight.

What is Chainsight?

Chainsight is a composable-oracle platform that a developer can create an oracle for their own project easily.
It is a permissionless oracle network that anyone can create an oracle for their own project. And its components can be used by other developers to create their own oracle.

Blockchains are state machines and are not designed to handle large amounts of data. DApps running on the blockchain are basically designed to be simple state management. This is quite straightforward in designing the protocol. However, it is difficult when trying to develop slightly more complex applications. As a result, the future that World Computer originally envisioned, where any kind of applications keep working sustainably, has not yet arrived.

Chainsight provides a data processing layer to develop applications that go one step ahead. It is an extension layer that leverages historical data, constantly updated data, and cross-chain data to compute the information you need for your application. A user can use a privacy layer for sensitive transactions, a scaling layer for high-volume transactions, and a data processing layer for computation with large amounts of data. With Chainsight, on-chain applications will be able to address more use cases that were previously difficult to achieve.s

Chainsight's smart contracts is deployed on the Internet Computer and called canisters. Using Internet Computer's Chain key criptography, Chainsight can securely connect to other blockchains and get the data from them. And the data can be used by other canisters on the Internet Computer.

Prerequisite

In this example, we will use the following tools.

Install Chainsight SDK(csx)

To install Chainsight SDK(csx), run the following command.

git clone https://github.com/horizonx-tech/chainsight-cli.git
cd chainsight-cli
cargo install --path .
Enter fullscreen mode Exit fullscreen mode

Project overview we will create

In this article, we will create a project that gets the price of ETH from a chainlink contract on the Ethereum network and relay it to the Sepolia network.

Create a project

At first, we need to create a Chainsight project. To create a project, run the following command.

csx new price_oracle
Enter fullscreen mode Exit fullscreen mode

This command will create a project named price_oracle in the current directory.

You can see the project structure like this.

price_oracle
├── components
    |── sample_algorithm_indexer.yaml
    |── sample_algorithm_lens.yaml
    |── sample_event_indexer.yaml
    |── sample_relayer.yaml
    |── sample_snapshot_indexer_evm.yaml
    |── sample_snapshot_indexer_https.yaml
    |── sample_snapshot_indexer_icp.yaml
├── interfaces
├── .chainsight
├── .env
├── project.yaml
Enter fullscreen mode Exit fullscreen mode

Not all of the files are necessary for this project. So we will delete some of them.

First of all, let's enter the project directory.

cd price_oracle
Enter fullscreen mode Exit fullscreen mode

Build and deploy the project for the first time

To build the project, run the following command.

csx build
Enter fullscreen mode Exit fullscreen mode

Just as a test, let's deploy the project to dfx local network. To start dfx local network, run the following command.

dfx start --clean
Enter fullscreen mode Exit fullscreen mode

To deploy the project, run the following command.

csx deploy
Enter fullscreen mode Exit fullscreen mode

You can see the log like this.

Jan 09 06:41:11.227 INFO Current deployed status:
Canister Name: __Candid_UI
  Canister Id: {"local": "avqkn-guaaa-aaaaa-qaaea-cai"}
  Controllers: yuk46-o67cx-p3ddo-vzb27-o3cp4-owe4v-u4r3g-htj3u-4v2h6-sne4q-wqe
  Module Hash: 0x934756863c010898a24345ce4842d173b3ea7639a8eb394a0d027a9423c70b5c

Canister Name: sample_algorithm_indexer
  Canister Id: {"local": "bkyz2-fmaaa-aaaaa-qaaaq-cai"}
  Controllers: 7fpuj-hqaaa-aaaal-acg7q-cai bnz7o-iuaaa-aaaaa-qaaaa-cai yuk46-o67cx-p3ddo-vzb27-o3cp4-owe4v-u4r3g-htj3u-4v2h6-sne4q-wqe
  Module Hash: 0x35b7fd5e5a57044b50b44007a4a5a275abe9c88464c6b8848294eb4e90291417

Canister Name: sample_algorithm_lens
  Canister Id: {"local": "bd3sg-teaaa-aaaaa-qaaba-cai"}
  Controllers: 7fpuj-hqaaa-aaaal-acg7q-cai bnz7o-iuaaa-aaaaa-qaaaa-cai yuk46-o67cx-p3ddo-vzb27-o3cp4-owe4v-u4r3g-htj3u-4v2h6-sne4q-wqe
  Module Hash: 0xdf5b0e5a6a61192cc3659ecaddf5f1866b67c8e6ee9544b55cb7b7cf51b7c4b2

Canister Name: sample_event_indexer
  Canister Id: {"local": "be2us-64aaa-aaaaa-qaabq-cai"}
  Controllers: 7fpuj-hqaaa-aaaal-acg7q-cai bnz7o-iuaaa-aaaaa-qaaaa-cai yuk46-o67cx-p3ddo-vzb27-o3cp4-owe4v-u4r3g-htj3u-4v2h6-sne4q-wqe
  Module Hash: 0x06a6afd2c985c268864ec0b2d13e058a24f395d2196db8349e536df17312f733

Canister Name: sample_relayer
  Canister Id: {"local": "br5f7-7uaaa-aaaaa-qaaca-cai"}
  Controllers: 7fpuj-hqaaa-aaaal-acg7q-cai bnz7o-iuaaa-aaaaa-qaaaa-cai yuk46-o67cx-p3ddo-vzb27-o3cp4-owe4v-u4r3g-htj3u-4v2h6-sne4q-wqe
  Module Hash: 0x76de26fc1016eea97d8b342bc60a74eb878a7fee85216b08103fc4855a50ebb8

Canister Name: sample_snapshot_indexer_evm
  Canister Id: {"local": "bw4dl-smaaa-aaaaa-qaacq-cai"}
  Controllers: 7fpuj-hqaaa-aaaal-acg7q-cai bnz7o-iuaaa-aaaaa-qaaaa-cai yuk46-o67cx-p3ddo-vzb27-o3cp4-owe4v-u4r3g-htj3u-4v2h6-sne4q-wqe
  Module Hash: 0x36cfd3c2816d7a27d8d1cfd77953c5404c1c87e42b78363d4114c65b86384fd3

Canister Name: sample_snapshot_indexer_https
  Canister Id: {"local": "b77ix-eeaaa-aaaaa-qaada-cai"}
  Controllers: 7fpuj-hqaaa-aaaal-acg7q-cai bnz7o-iuaaa-aaaaa-qaaaa-cai yuk46-o67cx-p3ddo-vzb27-o3cp4-owe4v-u4r3g-htj3u-4v2h6-sne4q-wqe
  Module Hash: 0x7871ed8328b75f26a51a1587545a67a15a9b9fecc1a1cbfba8a7df5233c8a724

Canister Name: sample_snapshot_indexer_icp
  Canister Id: {"local": "by6od-j4aaa-aaaaa-qaadq-cai"}
  Controllers: 7fpuj-hqaaa-aaaal-acg7q-cai bnz7o-iuaaa-aaaaa-qaaaa-cai yuk46-o67cx-p3ddo-vzb27-o3cp4-owe4v-u4r3g-htj3u-4v2h6-sne4q-wqe
  Module Hash: 0xe5f2c9c5627f668cf52e8d975baf17c56bbab654f61d78a7396815db20c71d80


Jan 09 06:41:11.227 INFO Project 'price_oracle' deployed successfully
Enter fullscreen mode Exit fullscreen mode

Okay, we have deployed the canisters successfully. Next, we will create canisters for our project.

Update the project

Before updating the project, we need to know Chainsight's canister types. There are 7 canister types in Chainsight.

Canister type Description
Event Indexer An indexer that indexes smart contract events on EVM blockchains
Snapshot Indexer EVM An indexer that indexes smart contract states on EVM blockchains. You can call the smart contract's view function from this canister and store the result.
Snapshot Indexer HTTPS An indexer that indexes data from an external API. You can call the GET Web API from this canister and store the result.
Snapshot Indexer ICP An indexer that indexes data from other chainsight canisters. You can call other chainsight canister's function from this canister and store the result.
Algorithm Indexer An indexer that indexes data from other canisters. You can call other chainsight canister's function from this canister and store the result.
Algorithm Lens A canister that provides a view function for other chainsight canisters. You can call this canister's view function from other chainsight canisters. You can implement any logic in this canister.
Relayer A canister that relays data from other chainsight canisters to smart contracts on EVM blockchains.

In this project, we will use the following canisters.

Canister type Usage
Snapshot Indexer EVM To get the price of ETH from a mock chainlink contract on the Scroll Sepolia network
Algorithm Lens To get the price from the Snapshot Indexer EVM canister and convert it into one with 18 digits
Relayer To relay the price to the smart contract on the Sepolia network

Scenario:

Scenario

Let's update the project.
At first, we will delete the unnecessary canisters.
Canisters that we will deploy is defined in project.yaml and components/*.yaml. So lets' update/delete them.

project.yaml

version: v1
label: price_oracle
components:
- component_path: components/sample_event_indexer.yaml
- component_path: components/sample_algorithm_indexer.yaml
- component_path: components/sample_snapshot_indexer_evm.yaml
- component_path: components/sample_snapshot_indexer_icp.yaml
- component_path: components/sample_relayer.yaml
- component_path: components/sample_algorithm_lens.yaml
- component_path: components/sample_snapshot_indexer_https.yaml

Enter fullscreen mode Exit fullscreen mode

Let's update the project.yaml like this.

version: v1
label: price_oracle
components:
- component_path: components/sample_snapshot_indexer_evm.yaml
- component_path: components/sample_algorithm_lens.yaml
- component_path: components/sample_relayer.yaml
Enter fullscreen mode Exit fullscreen mode

note: components/sample_algorith_lens.yaml needs to be later than components/sample_snapshot_indexer_evm.yaml because components/sample_algorithm_lens.yaml uses components/sample_snapshot_indexer_evm.yaml as a datasource. Also, components/sample_relayer.yaml needs to be later than components/sample_algorithm_lens.yaml for the same reason.

And delete the following files.

rm components/sample_algorithm_indexer.yaml
rm components/sample_event_indexer.yaml
rm components/sample_snapshot_indexer_https.yaml
rm components/sample_snapshot_indexer_icp.yaml
rm -rf src/logics/*
Enter fullscreen mode Exit fullscreen mode

Next, we will update the components/sample_snapshot_indexer_evm.yaml file.
We need to get data from Mock price feed at address 0x9dA09CA893b089774DEb20AA1e375E613BA48cE9 on the Scrol Sepolia network and need to call latestAnswer() function to get a mock price of ETH.
So let's update the file like this.

# yaml-language-server: $schema=https://raw.githubusercontent.com/horizonx-tech/chainsight-cli/main/resources/schema/snapshot_indexer_evm.json
version: v1
metadata:
  label: Sample Snapshot Indexer Evm
  type: snapshot_indexer_evm
  description: ''
  tags:
  - ERC-20
  - Ethereum
  - DAI
datasource:
  location:
    id: 0x9dA09CA893b089774DEb20AA1e375E613BA48cE9 # Mock Chainlink price feed
    args:
      network_id: 534351 # Scroll sepolia network id
      rpc_url: https://sepolia-rpc.scroll.io
  method:
    identifier: latestAnswer():(uint256) # Call latestAnswer() function. 
    interface: MockOracle.json # Interface file. We can get it from https://sepolia.scrollscan.dev/address/0x9dA09CA893b089774DEb20AA1e375E613BA48cE9#code
    args: []
interval: 100 # Interval to call the function
cycles: null
Enter fullscreen mode Exit fullscreen mode

We need a abi file for the mock price feed contract. So let's create a file named MockOracle.json in the interfaces directory and copy the abi from here.

[
    {
      "inputs": [],
      "name": "latestAnswer",
      "outputs": [
        {
          "internalType": "uint256",
          "name": "",
          "type": "uint256"
        }
      ],
      "stateMutability": "pure",
      "type": "function"
    }
  ]
Enter fullscreen mode Exit fullscreen mode

Okay, it's done. Next, let's update the sample_algorithm_lens.
As a default, the datasource of the sample_algorithm_lens is sample_snapshot_indexer_icp. But we will use sample_snapshot_indexer_evm as a datasource. So let's update the sample_algorithm_lens like this.

# yaml-language-server: $schema=https://raw.githubusercontent.com/horizonx-tech/chainsight-cli/main/resources/schema/algorithm_lens.json
version: v1
metadata:
  label: Sample Algorithm Lens
  type: algorithm_lens
  description: ''
  tags:
    - Price
    - Chainlink
datasource:
  methods:
  - id: sample_snapshot_indexer_evm # Use sample_snapshot_indexer_evm as a datasource
    identifier: 'get_last_snapshot_value : () -> (text)' # Function to get the last indexed value from the canister. You can find it in artifacts/sample_snapshot_indexer_evm.did.
    candid_file_path: null
cycles: null
Enter fullscreen mode Exit fullscreen mode

And build the project again:

csx build
Enter fullscreen mode Exit fullscreen mode

Okay, next, le'ts implement the lens calculation function in src/logics/sample_algorithm_lens.rs.

As a default, the function is like this.

use sample_algorithm_lens_accessors::*;
#[derive(Clone, Debug, Default, candid :: CandidType, serde :: Deserialize, serde :: Serialize)]
pub struct LensValue {
    pub dummy: u64,
}
pub async fn calculate(targets: Vec<String>) -> LensValue {
    let _result = get_get_last_snapshot_value_in_sample_snapshot_indexer_evm(
        targets.get(0usize).unwrap().clone(),
    )
    .await;
    todo!()
}

Enter fullscreen mode Exit fullscreen mode

We want to return the price of ETH as u128 and with 18 digits. So let's update the function like this.

use sample_algorithm_lens_accessors::*;
const PRECISION: u32 = 18;
pub type LensValue = u128;
pub async fn calculate(targets: Vec<String>) -> LensValue {
    let ethusd = get_get_last_snapshot_value_in_sample_snapshot_indexer_evm(targets.get(0usize).unwrap().clone())
        .await
        .unwrap()
        .parse::<u128>()
        .unwrap();
    format_ethusd(ethusd)
}

// The raw data is 8 digits precision, so we need to convert it into 18 digits
fn format_ethusd(ethusd: u128) -> u128 {
    ethusd * 10u128.pow(PRECISION - 8)
}

#[cfg(test)]
pub mod tests {
    use super::*;
    #[test]
    fn test_format_ethusd() {
        let ethusd = 2956575400000;
        let formated = format_ethusd(ethusd);
        assert_eq!(formated, 29565754000000000000000);
    }
}
Enter fullscreen mode Exit fullscreen mode

It's almost done. Finally, we need to update the sample_relayer canister.

As a default, the sample_relayer canister has sample_snapshot_indexer_evm as a datasource. But we will use sample_algorithm_lens as a datasource. And, the destination network is polygon-mumbai. We need to update the sample_relayer like this.

# yaml-language-server: $schema=https://raw.githubusercontent.com/horizonx-tech/chainsight-cli/main/resources/schema/relayer.json
version: v1
metadata:
  label: Sample Relayer
  type: relayer
  description: ""
  tags:
    - Oracle
    - Sepolia
datasource:
  location:
    id: sample_algorithm_lens # This is the id of the algorithm lens
  method:
    identifier: "get_result : (vec text) -> (nat)" # This is the function to get the result from the algorithm lens. You can find it in artifacts/sample_algorithm_lens.did.
    interface: null
    args: []
destination:
  network_id: 11155111 # This is the network id of the Sepolia network
  type: uint256
  oracle_address: 0xb6cC49fA578bb155F6811f735437c74495905C1d # This is the address of the oracle contract on the Sepolia network
  rpc_url: https://rpc.sepolia.org # This is the rpc url of the Sepolia network
  method_name: null
  interface: null
interval: 100 # This is the interval to relay the data to the Sepolia network
lens_targets:
  identifiers:
    - sample_snapshot_indexer_evm # This is the id of the snapshot indexer evm canister. The lens canister(Algorithm Lens) will get the data from this canister.
cycles: null


Enter fullscreen mode Exit fullscreen mode

It's done. Let's build the project again.

csx build
Enter fullscreen mode Exit fullscreen mode

Deploy the project again

Re-run the dfx local network and deploy the project again.

dfx stop
dfx start --clean
Enter fullscreen mode Exit fullscreen mode

log:

$ dfx start --clean
Running dfx start for version 0.15.1
Enter fullscreen mode Exit fullscreen mode
csx deploy
Enter fullscreen mode Exit fullscreen mode

You can see the log like this.

Jan 09 08:03:29.922 INFO Current deployed status:
Canister Name: __Candid_UI
  Canister Id: {"local": "br5f7-7uaaa-aaaaa-qaaca-cai"}
  Controllers: yuk46-o67cx-p3ddo-vzb27-o3cp4-owe4v-u4r3g-htj3u-4v2h6-sne4q-wqe
  Module Hash: 0x934756863c010898a24345ce4842d173b3ea7639a8eb394a0d027a9423c70b5c

Canister Name: sample_algorithm_lens
  Canister Id: {"local": "bkyz2-fmaaa-aaaaa-qaaaq-cai"}
  Controllers: 7fpuj-hqaaa-aaaal-acg7q-cai bnz7o-iuaaa-aaaaa-qaaaa-cai yuk46-o67cx-p3ddo-vzb27-o3cp4-owe4v-u4r3g-htj3u-4v2h6-sne4q-wqe
  Module Hash: 0xd19f567c8a783ca7b74c5756ae6e836657390d7e69c5f6d570ec9ebd4e03bd31

Canister Name: sample_relayer
  Canister Id: {"local": "bd3sg-teaaa-aaaaa-qaaba-cai"}
  Controllers: 7fpuj-hqaaa-aaaal-acg7q-cai bnz7o-iuaaa-aaaaa-qaaaa-cai yuk46-o67cx-p3ddo-vzb27-o3cp4-owe4v-u4r3g-htj3u-4v2h6-sne4q-wqe
  Module Hash: 0x327df98ec3bf2f07fb247362edd7ae4f53bef3a9a24abfdc2333900852766b2c

Canister Name: sample_snapshot_indexer_evm
  Canister Id: {"local": "be2us-64aaa-aaaaa-qaabq-cai"}
  Controllers: 7fpuj-hqaaa-aaaal-acg7q-cai bnz7o-iuaaa-aaaaa-qaaaa-cai yuk46-o67cx-p3ddo-vzb27-o3cp4-owe4v-u4r3g-htj3u-4v2h6-sne4q-wqe
  Module Hash: 0x36cfd3c2816d7a27d8d1cfd77953c5404c1c87e42b78363d4114c65b86384fd3


Jan 09 08:03:29.922 INFO Project 'price_oracle' deployed successfully
Enter fullscreen mode Exit fullscreen mode

Run the project

So let's run the project.
To run the project locally, we need to run chainsight-management-canisters locally. To run the canisters, run the following command.

git clone https://github.com/horizonx-tech/chainsight-management-canisters.git
cd chainsight-management-canisters
make local port=${YOUR_DFX_PORT} # The port can be seen in the dfx start log. In this case, it's 39749.
cd ..
Enter fullscreen mode Exit fullscreen mode

This may take a few minutes. After the canisters are deployed, run the following command to run the canisters locally. This also may take a few minutes.

csx exec
Enter fullscreen mode Exit fullscreen mode

Supply the SepoliaETH to the relayer

To relay the data to the Sepolia network, we need to supply the SepoliaETH to the relayer canister. The source EVM account address is determined when the relayer canister is deployed. So at first, let's get the address.

cd artifacts
dfx canister call sample_relayer get_ethereum_address
Enter fullscreen mode Exit fullscreen mode

You can see the address like this

$ dfx canister call sample_relayer get_ethereum_address
("0xe43e266dce52a16be2ae6ad6d10dacf59fc4d659")
Enter fullscreen mode Exit fullscreen mode

This address is the sender address of the relayer canister. Send some SepoliaETH to the address on the Sepolia network. It's sufficient to send about 0.1 SepoliaETH for 1 transaction.
The key of the address is generated by ICP's chain key criptography. So you can't get the private key of the address. If you want to know more about the chain key criptography, please check here.

Get the price from the destination contract

Then, let's get the price from the destination contract on the Sepolia network.
https://sepolia.etherscan.io/address/0xb6cC49fA578bb155F6811f735437c74495905C1d#readContract
Read readAsUint256 with the sample_relayer address as a parameter.

Read contract

You will see 10000000000000000000000000000 as a result.
This is the relayed price from the mock price feed contract on the Scroll Sepolia network.

Conclusion

In this article, we have created an EVM oracle with Chainsight. We have created a project that gets the price of ETH from a mock chainlink contract on the Scroll Sepolia network and relay it to the Sepolia network.
A complete example can be found here. Please check it out.
As we can see, Chainsight is a very powerful tool for creating an secure and reliable oracle. And it's very easy to use. I hope this article will help you to create your own oracle.
Let's enjoy building a defi project with Chainsight!

Links

Top comments (0)