Hey Fellow Devs đź‘‹
I've been working on a project called ChainSight for the past few months. It's a composable-oracle platform that allows you to create custom oracles for your EVM smart contracts. Using ChainSight, you can create oracles that provide data from any source, including other smart contracts, APIs, and even other blockchains.
Anyone can create an oracle and publish it on the platform. The oracle can be used by anyone who needs the data it provides.
From some points of view, Chainsight can be considered as a SDK for deploying smart contracts on the Internet Computer that interact with other smart contracts on other chains. These contracts are called "canisters" and they are deployed on the Internet Computer's blockchain. Using https-outcalls and threshold ECDSA, important features of the Internet Computer, Chainsight provides a set of tools for creating canisters and deploying them on the Internet Computer easily and securely.. For more details, check out the documentation.
Recently, I have added a new feature to the platform that allows you to execute not only an oracle operation but also any other smart contract execution. This feature allows you to create a little bit more complex use cases. The following are some examples of what you can do with it:
- An arbitrage bot: execute a swap on Uniswap when the price of a token is lower than the price on another exchange.
- A liquidation bot: execute a liquidation on Compound when the collateralization ratio of a loan is lower than a certain threshold.
- A multi-chain voting: submit a proposal on one chain and vote on it on another chain.
These are just a few examples of what you can do with this feature. I'm sure you can come up with many more. In this post, I will show you how to create a multi-chain voting in 30 minutes using ChainSight.
The complete code for this post can be found here.
On this post, I will show you how to create a multi-chain voting in 30 minutes using ChainSight. And the next post, I will deploy the canisters on the Internet Computer and show you how to use them.
What is a multi-chain voting I am talking about?
A multi-chain voting is a voting that takes place on multiple chains. The idea is to submit a proposal on one chain and vote on it on another chain. This is useful when you want to create a DAO that spans across multiple chains. For example, you can create a DAO on Ethereum and vote on proposals on Polygon. In this example, I will show you
How to create a multi-chain voting?
To create a multi-chain voting, you need to create two functions: one for submitting a proposal and one for voting on it. The first contract will be used to synchronize the proposal between the chains. The second one will be used to vote on the proposal.
And how can we synchronize proposals and votes between chains is the issue we are going to solve in this post. We will use ChainSight to do that. ChainSight provides a set of tools for creating canisters and deploying them on the Internet Computer.
Prerequisites
To try out this example, you may want to know a little bit about the Internet Computer and Chainsight. If you are not familiar with them, you can check out the following links:
I'll write about overview of Chainsight and hello world example in future posts. So, stay tuned.
Project Overview
The project consists of two parts: the solidity smart contracts and ICP smart contracts(aka canisters). The solidity smart contracts are used to submit proposals and vote on them. The ICP smart contracts are used to synchronize proposals and votes between chains.
├── components
├── interfaces
├── scripts
│── solidity
│ ├── contracts
|── src
Folder | Description |
---|---|
components | Chainsight components I will deploy on the Internet Computer |
interfaces | Interfaces Chainight components will use to interact with the Internet Computer |
scripts | Scripts for deploying Chainsight components on the Internet Computer local env |
solidity | Solidity smart contracts we will deploy on Sepolia and Scroll Sepolia |
src | User defined logics for Chainsight components |
Following contracts are deployed on each EVM chain.
Contract | Description |
---|---|
contracts/proposal/ProposalFactory.sol | Contract for creating a new proposal |
contracts/proposal/ProposalManager.sol | Contract for managing proposals and its votes |
contracts/proposal/ProposalSynchronizer.sol | Contract for synchronizing proposals from other chains via Chainsight |
contracts/voting/VotingSynchonizer.sol | Contract for synchronizing votes from other chains via Chainsight |
And following canisters are deployed on the Internet Computer as Chainsight components.
Component | Description |
---|---|
components/proposal_factory_event_indexer_sepolia.yaml | Event indexer for ProposalFactory contract events ProposalCreated  on Scroll Sepolia |
components/proposal_factory_event_indexer_scroll_sepolia.yaml | Event indexer for ProposalFactory contract events ProposalCreated  on Scroll Sepolia |
components/proposal_lens.yaml | Lens for ProposalManager contract to convert proposal event data into another format to be used by proposal relayers |
components/proposal_relayer_sepolia.yaml | Relayer for ProposalManager contract to relay proposal data from Scroll Sepolia to Sepolia |
components/proposal_relayer_scroll_sepolia.yaml | Relayer for ProposalManager contract to relay proposal data from Sepolia to Scroll Sepolia |
components/voting_event_indexer_sepolia.yaml | Event indexer for Voting contract events Voted  on Sepolia |
components/voting_event_indexer_scroll_sepolia.yaml | Event indexer for Voting contract events Voted  on Scroll Sepolia |
components/voting_lens.yaml | Lens for Voting contract to convert vote event data into another format to be used by voting relayers |
components/voting_relayer_sepolia.yaml | Relayer for Voting contract to relay vote data from Scroll Sepolia to Sepolia |
components/voting_relayer_scroll_sepolia.yaml | Relayer for Voting contract to relay vote data from Sepolia to Scroll Sepolia |
Overview of Chainsight components and EVM smart contracts are as follows.
- User submits a proposal on a chain.
- The proposal is indexed by the event indexer on the chain.
- The proposal is relayed to another chain by the proposal relayer by callingÂ
batchSynchronize
 function on the ProposalSynchonizer contract.- Voting on the proposal is started on each chain.
- User votes on the proposal on a chain.
- The vote is indexed by the event indexer on the chain.
- The vote is relayed to another chain by the voting relayer by callingÂ
batchSynchronize
 function on the VotingSynchonizer contract.
Details of Chainsight components
Let's take a look at the details of Chainsight components with its code.
ProposalFactoryEventIndexerSepolia
This component indexes ProposalCreated
 events emitted by ProposalFactory
 contract on Scroll Sepolia. The indexer is deployed on Sepolia.
# yaml-language-server: $schema=https://raw.githubusercontent.com/horizonx-tech/chainsight-cli/main/resources/schema/event_indexer.json
version: v1
metadata:
label: Proposal Factory Indexer Sepolia
type: event_indexer
description: ""
tags:
- TODO
datasource:
id: 0xAEB9FBEE52B7272b5c75Bc64E4f82D24Ee2Aaa33 // ProposalFactory contract address on Sepolia
event:
identifier: ProposalCreated // Event name
interface: IProposalFactory.json // Interface of ProposalFactory contract. You can find it in interfaces/ folder.
network:
rpc_url: https://ethereum-sepolia.blockpi.network/v1/rpc/public
chain_id: 11155111
from: 2739538
contract_type: ERC-20
interval: ${INTERVAL} // Interval to check new events
cycles: null
When I need to index smart contract events, what I only have to do is to write a yaml file like this. I don't need to write any code.
ProposalFactoryEventIndexerSepolia
This is the same as ProposalFactoryEventIndexerSepolia
 but it indexes events emitted by ProposalFactory
 contract on Scroll Sepolia.
# yaml-language-server: $schema=https://raw.githubusercontent.com/horizonx-tech/chainsight-cli/main/resources/schema/event_indexer.json
version: v1
metadata:
label: Proposal Factory Indexer Scroll
type: event_indexer
description: ""
tags: - TODO
datasource:
id: 0xaA00080dd203701d94e6F262bb7101E19A0C8107
event:
identifier: ProposalCreated
interface: IProposalFactory.json
network:
rpc_url: https://sepolia-rpc.scroll.io
chain_id: 534351
from: 0
contract_type: ERC-20
interval: ${INTERVAL}
cycles: null
ProposalLens
This component converts ProposalCreated
 event indexed by ProposalFactoryEventIndexer
 into another format to be used by proposal relayers.
# yaml-language-server: $schema=https://raw.githubusercontent.com/horizonx-tech/chainsight-cli/main/resources/schema/algorithm_lens.json
version: v1
metadata:
label: Proposal Lens
type: algorithm_lens
description: "TODO"
tags:
- TODO
datasource:
methods:
- id: proposal_factory_event_indexer_sepolia // This is the id of EventIndexerSepolia. But this canister can be used for Scroll Sepolia as well since the format of the event data is the same.
identifier: "events_from_to : (nat64, nat64) -> (vec record { nat64; vec ProposalCreated })" // This identifier is defined in artifacts/proposal_factory_event_indexer_sepolia.did
candid_file_path: null
with_args: true
cycles: null
We can write some conversion logic in src/logics/proposal_lens/lib.rs
 file.
Now, to use event data to synchronize proposals, we need to convert it into what ProposalSynchonizer
 contract expects. The format of the event data is as follows.
solidity/contracts/interfaces/IProposalSynchronizer.sol
/**
* @dev Batch synchronize proposals from another chain
* @param ids The ids of the proposals
* @param proposers proposers of the proposals
* @param chainIds The ids of the chain where the proposals are created
* @param startTimestamps The start timestamps of voting
* @param endTimestamps The end timestamps of voting
* @param proposedBlocks The block numbers of the proposals
* @notice The length of the arrays should be the same
**/
function batchSynchronize(
uint256[] calldata ids,
address[] calldata proposers,
uint256[] calldata chainIds,
uint256[] calldata startTimestamps,
uint256[] calldata endTimestamps,
uint256[] calldata proposedBlocks
) external;
So, we need to collect events and convert them into the format above. The following is the code for that.
src/logics/proposal_lens/lib.rs
pub type LensValue = ( // This is the format of the event data to be used by proposal relayers.
Vec<u128>,
Vec<String>,
Vec<u128>,
Vec<u128>,
Vec<u128>,
Vec<u128>,
);
pub type CalculateArgs = (u64, u64); // block number from, block number to. This is provided by the caller (in this case, a proposal relayer).
pub async fn calculate(targets: Vec<String>, args: CalculateArgs) -> LensValue {
let results = get_events_from_to_in_proposal_factory_event_indexer_sepolia(
targets.get(0usize).unwrap().clone(), // Request argments provided by the caller (in this case, a proposal relayer).
args,
)
.await
.unwrap();
results
.iter()
.map(|r| {
let block = r.0;
let events = &r.1;
let e: Vec<(u128, String, u128, u128, u128, u64)> = events
.iter()
.map(|e| {
(
e.id.value.parse().unwrap(),
e.proposer.clone(),
e.startTimestamp.value.parse().unwrap(),
e.endTimestamp.value.parse().unwrap(),
e.chainId.value.parse().unwrap(),
block,
)
})
.collect();
e
})
.flatten()
.map(|e| (e.0, e.1, e.2, e.3, e.4, e.5))
.fold(
(vec![], vec![], vec![], vec![], vec![], vec![]),
|mut acc, e| {
acc.0.push(e.0);
acc.1.push(e.1);
acc.2.push(e.2);
acc.3.push(e.3);
acc.4.push(e.4);
acc.5.push(e.5.into());
acc
},
)
}
ProposalRelayerSepolia
This component relays proposals from Scroll Sepolia to Sepolia.
# yaml-language-server: $schema=https://raw.githubusercontent.com/horizonx-tech/chainsight-cli/feature/anyrelayer/resources/schema/relayer.json
version: v1
metadata:
label: Voting Relayer Sepolia
type: relayer
description: "TODO"
tags:
- TODO
datasource:
location:
id: voting_lens
method:
identifier: "get_result : (LensArgs) -> (record { vec nat; vec text; vec nat; vec nat; vec nat; vec nat })" // This identifier is defined in artifacts/voting_lens.did
interface: null
args: []
destination:
network_id: 11155111
type: custom
oracle_address: 0xa9FAf4c08147f4Cbb022f3c5e666B51Fd7244c44 // ProposalSynchonizer contract address on Sepolia
rpc_url: https://ethereum-sepolia.blockpi.network/v1/rpc/public
interface: IVotingSynchronizer.json
method_name: batchSynchronize
interval: ${INTERVAL}
lens_targets:
identifiers:
- voting_event_indexer_scroll_sepolia // Event source. This canister relayes events from Scroll Sepolia to Sepolia.
cycles: null
One of the important things for relayers is to make sure that the data is not duplicated. To do that, we need to keep track of the last block number we have relayed. The following is the code for that.
src/logics/proposal_relayer_sepolia/lib.rs
use std::str::FromStr;
use ic_web3_rs::{ethabi::Address, types::U256};
mod types;
pub type CallCanisterResponse = types::ResponseType;
pub type LensArgs = proposal_relayer_sepolia_bindings::LensArgs;
thread_local! {
// TODO: handle edge case when tx is not relayed
static LAST_RELAYED: std::cell::RefCell<u64> = std::cell::RefCell::new(0);
}
// This is call arguments for the lens canister. In this case, we need to provide block number from and block number to we want to get.
// `LAST_RELAYED` is the last block number we have relayed. So, we need to get events from `LAST_RELAYED + 1` to `u64::MAX` to make sure that we don't relay duplicated events.
pub fn call_args() -> (u64, u64) {
LAST_RELAYED.with_borrow(|r| {
let last_relayed = r.clone();
(last_relayed + 1, u64::MAX)
})
}
// This is a filter function for the lens canister. We need to filter out empty results. If filtered, the canister doesn't call the contract method. In this case, we don't have to call the contract method if there is no event. So we filter out empty results.
pub fn filter(res: &CallCanisterResponse) -> bool {
if res.0.len() == 0 {
return false;
}
let last_relayed = res.4.iter().max().unwrap().clone();
LAST_RELAYED.with_borrow_mut(|r| {
*r = (last_relayed as u64).clone();
});
true
}
pub struct ContractCallArgs { // This is automatically generated from interfaces/IVotingSynchronizer.json. So what I only have to do is to implement `call_args`, `filter` and `convert` functions.
pub ids: Vec<U256>,
pub proposers: Vec<Address>,
pub chainIds: Vec<U256>,
pub startTimestamps: Vec<U256>,
pub endTimestamps: Vec<U256>,
pub proposedBlocks: Vec<U256>,
}
// Convert the result of the lens canister into the arguments for the contract method. In this case, we need to convert the result into the format the contract method expects.
pub fn convert(res: &CallCanisterResponse) -> ContractCallArgs {
let ids = res
.0
.clone()
.iter()
.map(|x| U256::from(x.clone()))
.collect::<Vec<U256>>();
let proposers = res
.1
.clone()
.iter()
.map(|x| Address::from_str(x).unwrap())
.collect::<Vec<Address>>();
let chain_id = res
.2
.clone()
.iter()
.map(|x| U256::from(x.clone()))
.collect::<Vec<U256>>();
let start_timestamps = res
.3
.clone()
.iter()
.map(|x| U256::from(x.clone()))
.collect::<Vec<U256>>();
let end_timestamps = res
.4
.clone()
.iter()
.map(|x| U256::from(x.clone()))
.collect::<Vec<U256>>();
let proposed_blocks = res
.5
.clone()
.iter()
.map(|x| U256::from(x.clone()))
.collect::<Vec<U256>>();
ContractCallArgs {
ids,
proposers,
chainIds: chain_id,
startTimestamps: start_timestamps,
endTimestamps: end_timestamps,
proposedBlocks: proposed_blocks,
}
}
ProposalRelayerScrollSepolia
This component relays proposals from Sepolia to Scroll Sepolia. This is the same as ProposalRelayerSepolia
 but it relays proposals from Sepolia to Scroll Sepolia.
# yaml-language-server: $schema=https://raw.githubusercontent.com/horizonx-tech/chainsight-cli/feature/anyrelayer/resources/schema/relayer.json
version: v1
metadata:
label: Proposal Relayer Scroll Sepolia
type: relayer
description: "TODO"
tags:
- TODO
datasource:
location:
id: proposal_lens
method: We've created Chainsight components for synchronizing proposals between chains. Now, let's take a look at how to create Chainsight components for synchronizing votes between chains.
identifier: "get_result : (LensArgs) -> (record { vec nat; vec text; vec nat; vec nat; vec nat; vec nat })"
interface: null
args: []
destination:
network_id: 534351
type: custom
oracle_address: 0x400b306fF82EEBac84946F73826B68241421EA6F
rpc_url: https://sepolia-rpc.scroll.io
interface: IProposalSynchronizer.json
method_name: batchSynchronize
interval: ${INTERVAL}
lens_targets:
identifiers:
- proposal_factory_event_indexer_sepolia
cycles: null
We've need to write some logics in src/logics/proposal_relayer_sepolia/lib.rs
 file. But its' completely the same as what we have to write in ProposalRelayerSepolia
. And we don't have to write them once again. We can reuse the code by using ProposalRelayerSepolia
 as a template. The following is the code for that.
src/logics/proposal_relayer_scroll_sepolia/lib.rs
mod types;
pub type CallCanisterResponse = types::ResponseType;
pub type LensArgs = proposal_relayer_sepolia_bindings::LensArgs;
pub fn call_args() -> (u64, u64) {
proposal_relayer_scroll_sepolia::call_args()
}
pub type ContractCallArgs = proposal_relayer_scroll_sepolia::ContractCallArgs;
pub fn convert(res: &CallCanisterResponse) -> ContractCallArgs {
proposal_relayer_scroll_sepolia::convert(
&proposal_relayer_scroll_sepolia::CallCanisterResponse {
0: res.0.clone(),
1: res.1.clone(),
2: res.2.clone(),
3: res.3.clone(),
4: res.4.clone(),
5: res.5.clone(),
},
)
}
pub fn filter(res: &CallCanisterResponse) -> bool {
proposal_relayer_scroll_sepolia::filter(
&proposal_relayer_scroll_sepolia::CallCanisterResponse {
0: res.0.clone(),
1: res.1.clone(),
2: res.2.clone(),
3: res.3.clone(),
4: res.4.clone(),
5: res.5.clone(),
},
)
}
That's it.
VotingEventIndexerSepolia
This component indexes Voted
 events emitted by Voting
 contract on Scroll Sepolia. The indexer is deployed on Sepolia. The difference between VotingEventIndexerSepolia
 and ProposalFactoryEventIndexerSepolia
 is kind of event they index and the contract address they use.
# yaml-language-server: $schema=https://raw.githubusercontent.com/horizonx-tech/chainsight-cli/main/resources/schema/event_indexer.json
version: v1
metadata:
label: Voting Event Indexer Scroll
type: event_indexer
description: ""
tags:
- TODO
datasource:
id: 0x1A0ceb79B5B2bD56c68C3E23A8a41639a7c12ac6
event:
identifier: Voted
interface: IProposalManager.json
network:
rpc_url: https://sepolia-rpc.scroll.io
chain_id: 534351
from: 0
contract_type: ERC-20
interval: ${INTERVAL}
cycles: null
No code is required to write.
VotingEventIndexerScrollSepolia
This is the same as VotingEventIndexerSepolia
 but it indexes events emitted by Voting
 contract on Scroll Sepolia.
# yaml-language-server: $schema=https://raw.githubusercontent.com/horizonx-tech/chainsight-cli/main/resources/schema/event_indexer.json
version: v1
metadata:
label: Voting Event Indexer Scroll Sepolia
type: event_indexer
description: ""
tags:
- TODO
datasource:
id: 0x1A0ceb79B5B2bD56c68C3E23A8a41639a7c12ac6
event:
identifier: Voted
interface: IProposalManager.json
network:
rpc_url: https://sepolia-rpc.scroll.io
chain_id: 534351
from: 0
contract_type: ERC-20
interval: ${INTERVAL}
cycles: null
VotingLens
This component converts Voted
 event indexed by VotingEventIndexer
 into another format to be used by voting relayers.
# yaml-language-server: $schema=https://raw.githubusercontent.com/horizonx-tech/chainsight-cli/main/resources/schema/algorithm_lens.json
version: v1
metadata:
label: Voting Lens
type: algorithm_lens
description: "TODO"
tags:
- TODO
datasource:
methods:
- id: voting_event_indexer_sepolia
identifier: "events_from_to : (nat64, nat64) -> (vec record { nat64; vec Voted })"
candid_file_path: null
with_args: true
cycles: null
We can write some conversion logic in src/logics/voting_lens/lib.rs
 file. The concept is the same as ProposalLens
. But the format of the event data is different. The following is the code for that.
use voting_lens_accessors::*;
pub type LensValue = (
Vec<u128>,
Vec<String>,
Vec<bool>,
Vec<u128>,
Vec<u128>,
Vec<u64>,
);
pub type CalculateArgs = (u64, u64);
pub async fn calculate(targets: Vec<String>, args: CalculateArgs) -> LensValue {
let r = get_events_from_to_in_voting_event_indexer_sepolia(
targets.get(0usize).unwrap().clone(),
args,
)
.await;
if r.is_err() {
ic_cdk::println!("error: {:?}", r);
}
let results = r.unwrap();
results
.iter()
.map(|r| {
let block = r.0;
let events = &r.1;
let e: Vec<(u128, String, bool, u128, u128, u64)> = events
.iter()
.map(|e| {
(
e.id.value.parse().unwrap(),
e.voter.clone(),
e.support,
e.power.value.parse().unwrap(),
e.chainId.value.parse().unwrap(),
block,
)
})
.collect();
e
})
.flatten()
.map(|e| (e.0, e.1, e.2, e.3, e.4, e.5))
.fold(
(vec![], vec![], vec![], vec![], vec![], vec![]),
|mut acc, e| {
acc.0.push(e.0);
acc.1.push(e.1);
acc.2.push(e.2);
acc.3.push(e.3);
acc.4.push(e.4);
acc.5.push(e.5.into());
acc
},
)
}
VotingRelayerSepolia
This component relays votes from Scroll Sepolia to Sepolia.
# yaml-language-server: $schema=https://raw.githubusercontent.com/horizonx-tech/chainsight-cli/feature/anyrelayer/resources/schema/relayer.json
version: v1
metadata:
label: Voting Relayer Sepolia
type: relayer
description: "TODO"
tags:
- TODO
datasource:
location:
id: voting_lens
method:
identifier: "get_result : (LensArgs) -> (record { vec nat; vec text; vec nat; vec nat; vec nat; vec nat })"
interface: null
args: []
destination:
network_id: 11155111
type: custom
oracle_address: 0xa9FAf4c08147f4Cbb022f3c5e666B51Fd7244c44
rpc_url: https://ethereum-sepolia.blockpi.network/v1/rpc/public
interface: IVotingSynchronizer.json
method_name: batchSynchronize
interval: ${INTERVAL}
lens_targets:
identifiers:
- voting_event_indexer_scroll_sepolia
cycles: null
We've need to write some logics in src/logics/voting_relayer_sepolia/lib.rs
 file by the same reason as ProposalRelayerSepolia
. The following is the code for that.
mod types;
use std::str::FromStr;
use ic_web3_rs::{ethabi::Address, types::U256};
pub type CallCanisterResponse = types::ResponseType;
pub type LensArgs = voting_relayer_sepolia_bindings::LensArgs;
thread_local! {
// TODO: handle edge case when tx is not relayed
static LAST_RELAYED: std::cell::RefCell<u64> = std::cell::RefCell::new(0);
}
pub fn call_args() -> (u64, u64) {
LAST_RELAYED.with_borrow(|r| {
let last_relayed = r.clone();
(last_relayed + 1, u64::MAX)
})
}
#[derive(Clone, Debug)]
pub struct ContractCallArgs {
pub ids: Vec<U256>,
pub voters: Vec<Address>,
pub _supports: Vec<bool>,
pub votingPowers: Vec<U256>,
pub chainIds: Vec<U256>,
}
pub fn convert(res: &CallCanisterResponse) -> ContractCallArgs {
let ids = res
.0
.clone()
.iter()
.map(|x| U256::from(x.clone()))
.collect::<Vec<U256>>();
let voters = res
.1
.clone()
.iter()
.map(|x| Address::from_str(x).unwrap())
.collect::<Vec<Address>>();
let _supports = res.2.clone().iter().map(|x| x > &0).collect::<Vec<bool>>();
let voting_powers = res
.3
.clone()
.iter()
.map(|x| U256::from(x.clone()))
.collect::<Vec<U256>>();
let chain_ids = res
.4
.clone()
.iter()
.map(|x| U256::from(x.clone()))
.collect::<Vec<U256>>();
ContractCallArgs {
ids,
voters,
_supports,
votingPowers: voting_powers,
chainIds: chain_ids,
}
}
pub fn filter(res: &CallCanisterResponse) -> bool {
if res.0.len() == 0 {
return false;
}
let last_relayed = res.5.iter().max().unwrap().clone();
LAST_RELAYED.with_borrow_mut(|r| {
*r = (last_relayed as u64).clone();
});
true
}
VotingRelayerScrollSepolia
This component relays votes from Sepolia to Scroll Sepolia. This is the same as VotingRelayerSepolia
 but it relays votes from Sepolia to Scroll Sepolia.
# yaml-language-server: $schema=https://raw.githubusercontent.com/horizonx-tech/chainsight-cli/feature/anyrelayer/resources/schema/relayer.json
version: v1
metadata:
label: Voting Relayer Scroll Sepolia
type: relayer
description: "TODO"
tags:
- TODO
datasource:
location:
id: voting_lens
method:
identifier: "get_result : (LensArgs) -> (record { vec nat; vec text; vec nat; vec nat; vec nat; vec nat })"
interface: null
args: []
destination:
network_id: 534351
type: custom
oracle_address: 0xE13E832e673dF3588d9a71F67C69826ECC76Cad5
rpc_url: https://sepolia-rpc.scroll.io
interface: IVotingSynchronizer.json
method_name: batchSynchronize
interval: ${INTERVAL}
lens_targets:
identifiers:
- voting_event_indexer_scroll_sepolia
cycles: null
We've need to write some logics in src/logics/voting_relayer_scroll_sepolia/lib.rs
 file by the same reason as ProposalRelayerSepolia
. The following is the code for that.
mod types;
pub type CallCanisterResponse = types::ResponseType;
pub type LensArgs = voting_relayer_scroll_sepolia_bindings::LensArgs;
pub fn call_args() -> (u64, u64) {
voting_relayer_sepolia::call_args()
}
pub type ContractCallArgs = voting_relayer_sepolia::ContractCallArgs;
pub fn convert(res: &CallCanisterResponse) -> ContractCallArgs {
voting_relayer_sepolia::convert(&voting_relayer_sepolia::CallCanisterResponse {
0: res.0.clone(),
1: res.1.clone(),
2: res.2.clone(),
3: res.3.clone(),
4: res.4.clone(),
5: res.5.clone(),
})
}
pub fn filter(res: &CallCanisterResponse) -> bool {
voting_relayer_sepolia::filter(&voting_relayer_sepolia::CallCanisterResponse {
0: res.0.clone(),
1: res.1.clone(),
2: res.2.clone(),
3: res.3.clone(),
4: res.4.clone(),
5: res.5.clone(),
})
}
That's it.
The part I'd like to emphasize is that we don't have to write any code for transparency, security and scalability. We can focus on writing business logics. Usually we have to write a lot of code for that, especially specific cases like multi-chain voting. But using Chainsight and deploying canisters on the Internet Computer, we can focus on writing business logics.
In this post, I've written about how to create Chainsight components for synchronizing proposals and votes between chains. In the next post, I will deploy the canisters on the Internet Computer and show you how to use them.