Dec 22, 2024

An easy way to create a price oracle with Chsingith Platform

by Hideyoshi

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 .

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

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

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

Build and deploy the project for the first time

To build the project, run the following command.

csx build

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

To deploy the project, run the following command.

csx deploy

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

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 typeDescription
Event IndexerAn indexer that indexes smart contract events on EVM blockchains
Snapshot Indexer EVMAn 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 HTTPSAn indexer that indexes data from an external API. You can call the GET Web API from this canister and store the result.
Snapshot Indexer ICPAn indexer that indexes data from other chainsight canisters. You can call other chainsight canister's function from this canister and store the result.
Algorithm IndexerAn indexer that indexes data from other canisters. You can call other chainsight canister's function from this canister and store the result.
Algorithm LensA 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.
RelayerA canister that relays data from other chainsight canisters to smart contracts on EVM blockchains.

In this project, we will use the following canisters.

Canister typeUsage
Snapshot Indexer EVMTo get the price of ETH from a mock chainlink contract on the Scroll Sepolia network
Algorithm LensTo get the price from the Snapshot Indexer EVM canister and convert it into one with 18 digits
RelayerTo relay the price to the smart contract on the Sepolia network

Scenario:

asq37o4csgj13m6xdj9l.png

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


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

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/*

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

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"
    }
  ]

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

And build the project again:

csx build

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!()
}


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);
    }
}

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



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

csx build

Deploy the project again

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

dfx stop
dfx start --clean

log:

$ dfx start --clean
Running dfx start for version 0.15.1

csx deploy

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

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 ..

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

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

You can see the address like this

$ dfx canister call sample_relayer get_ethereum_address
("0xe43e266dce52a16be2ae6ad6d10dacf59fc4d659")

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.

yqtt454ofds8rksvot6b.png

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