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 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:
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.
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
- Chainsight
- Internet Computer
- Chain key criptography
- [Chainsight (Twitter)] (https://twitter.com/Chainsight_)
- [My personal Twitter account] (https://twitter.com/hide_yoshi)