Learn how to build smart contracts that query data from other blockchains using Neutron’s ICQ module
cargo generate --git https://github.com/CosmWasm/cw-template.git --name neutron-icq-example
cd neutron-icq-example
Cargo.toml
file to include the necessary dependencies:
[package]
name = "neutron-icq-example"
version = "0.1.0"
authors = ["Your Name <[email protected]>"]
edition = "2021"
[lib]
crate-type = ["cdylib", "rlib"]
[profile.release]
opt-level = 3
debug = false
rpath = false
lto = true
debug-assertions = false
codegen-units = 1
panic = "abort"
incremental = false
overflow-checks = true
[features]
backtraces = ["cosmwasm-std/backtraces"]
library = []
[dependencies]
cosmwasm-std = "1.5.0"
cosmwasm-schema = "1.5.0"
cw-storage-plus = "1.1.0"
schemars = "0.8.12"
serde = { version = "1.0.171", default-features = false, features = ["derive"] }
thiserror = "1.0.40"
base64 = "0.21.2"
prost = "0.12.1"
# Neutron specific dependencies
neutron-sdk = { version = "0.10.0", features = ["async"] }
src/
bin/
schema.rs
contract.rs
error.rs
helpers.rs
lib.rs
msg.rs
state.rs
queries/ # For our interchain query definitions
mod.rs
cosmos_staking.rs
state.rs
to store information about registered queries and their results:
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use cosmwasm_std::{Addr, Coin, Uint128};
use cw_storage_plus::{Item, Map};
// Store registered interchain queries
pub const INTERCHAIN_QUERIES: Map<String, QueryInfo> = Map::new("interchain_queries");
// Store the results of queries
pub const QUERY_RESULTS: Map<String, QueryResult> = Map::new("query_results");
// Configuration for the contract
pub const CONFIG: Item<Config> = Item::new("config");
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct Config {
pub owner: Addr,
pub connection_id: String,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct QueryInfo {
pub id: String,
pub connection_id: String,
pub chain_id: String,
pub query_type: QueryType,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum QueryType {
// The Key-Value store query
KV {
key: Vec<u8>,
path: String,
},
// The transaction query
TX {
height: u64,
hash: String,
},
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct QueryResult {
pub id: String,
pub result_type: ResultType,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum ResultType {
// KV query result
KV {
value: Vec<u8>,
height: u64,
},
// TX query result
TX {
tx_hash: String,
height: u64,
tx_result: TransactionResult,
},
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct TransactionResult {
pub code: u64,
pub log: String,
pub events: Vec<Event>,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct Event {
pub r#type: String,
pub attributes: Vec<Attribute>,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct Attribute {
pub key: String,
pub value: String,
}
// For storing delegator information from Cosmos staking queries
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct DelegatorInfo {
pub delegator: String,
pub validator: String,
pub amount: Coin,
}
msg.rs
:
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use crate::state::{DelegatorInfo, QueryResult, QueryType};
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct InstantiateMsg {
pub connection_id: String,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum ExecuteMsg {
// Register a new interchain query
RegisterQuery {
query_id: String,
chain_id: String,
query_type: QueryType,
update_period: u64,
},
// Remove an existing interchain query
RemoveQuery {
query_id: String,
},
// Update an existing interchain query
UpdateQuery {
query_id: String,
new_update_period: u64,
},
// Query Cosmos staking delegation for a specific delegator and validator
QueryDelegation {
query_id: String,
chain_id: String,
delegator: String,
validator: String,
update_period: u64,
},
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum QueryMsg {
// Get information about a registered query
GetQuery {
query_id: String,
},
// Get the result of a query
GetQueryResult {
query_id: String,
},
// Get delegator information
GetDelegatorInfo {
query_id: String,
},
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct GetQueryResponse {
pub query_id: String,
pub registered: bool,
pub query_type: Option<QueryType>,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct GetQueryResultResponse {
pub query_id: String,
pub result: Option<QueryResult>,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct GetDelegatorInfoResponse {
pub query_id: String,
pub delegator_info: Option<DelegatorInfo>,
}
queries/cosmos_staking.rs
:
use cosmwasm_std::{Coin, StdResult};
use prost::Message;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use crate::error::ContractError;
// Define proto structures
// These match the Cosmos SDK's proto definitions for staking queries
#[derive(Clone, PartialEq, Message)]
pub struct QueryDelegationRequest {
#[prost(string, tag = "1")]
pub delegator_addr: String,
#[prost(string, tag = "2")]
pub validator_addr: String,
}
#[derive(Clone, PartialEq, Message)]
pub struct QueryDelegationResponse {
#[prost(message, optional, tag = "1")]
pub delegation_response: Option<DelegationResponse>,
}
#[derive(Clone, PartialEq, Message)]
pub struct DelegationResponse {
#[prost(message, optional, tag = "1")]
pub delegation: Option<Delegation>,
#[prost(message, optional, tag = "2")]
pub balance: Option<CosmosCoin>,
}
#[derive(Clone, PartialEq, Message)]
pub struct Delegation {
#[prost(string, tag = "1")]
pub delegator_address: String,
#[prost(string, tag = "2")]
pub validator_address: String,
#[prost(string, tag = "3")]
pub shares: String,
}
#[derive(Clone, PartialEq, Message)]
pub struct CosmosCoin {
#[prost(string, tag = "1")]
pub denom: String,
#[prost(string, tag = "2")]
pub amount: String,
}
// Helper function to create a staking delegation query key
pub fn create_delegation_key(delegator: &str, validator: &str) -> Vec<u8> {
// Encode according to Cosmos SDK storage patterns
let request = QueryDelegationRequest {
delegator_addr: delegator.to_string(),
validator_addr: validator.to_string(),
};
let mut buf = Vec::new();
buf.extend_from_slice(b"delegations/");
buf.extend_from_slice(delegator.as_bytes());
buf.extend_from_slice(b"/");
buf.extend_from_slice(validator.as_bytes());
buf
}
// Parse delegation response from raw bytes
pub fn parse_delegation_response(data: &[u8]) -> StdResult<Coin> {
let response = QueryDelegationResponse::decode(data)
.map_err(|e| ContractError::Std(cosmwasm_std::StdError::ParseErr {
target_type: "QueryDelegationResponse".to_string(),
msg: e.to_string(),
}))?;
let balance = response
.delegation_response
.ok_or_else(|| ContractError::Std(cosmwasm_std::StdError::GenericErr {
msg: "No delegation response".to_string(),
}))?
.balance
.ok_or_else(|| ContractError::Std(cosmwasm_std::StdError::GenericErr {
msg: "No balance in delegation response".to_string(),
}))?;
Ok(Coin {
denom: balance.denom,
amount: balance.amount.parse().map_err(|e| ContractError::Std(cosmwasm_std::StdError::ParseErr {
target_type: "Uint128".to_string(),
msg: e.to_string(),
}))?,
})
}
queries/mod.rs
:
pub mod cosmos_staking;
error.rs
:
use cosmwasm_std::StdError;
use thiserror::Error;
#[derive(Error, Debug)]
pub enum ContractError {
#[error("{0}")]
Std(#[from] StdError),
#[error("Unauthorized")]
Unauthorized {},
#[error("Query not found: {query_id}")]
QueryNotFound { query_id: String },
#[error("Query result not found: {query_id}")]
QueryResultNotFound { query_id: String },
#[error("Failed to decode query result: {msg}")]
DecodeError { msg: String },
#[error("Custom Error: {msg}")]
CustomError { msg: String },
}
contract.rs
:
use cosmwasm_std::{
entry_point, to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult, Coin,
from_binary, Addr, WasmMsg, Reply, SubMsg, Uint128,
};
use neutron_sdk::{
bindings::{
msg::NeutronMsg,
query::NeutronQuery,
types::Height,
},
interchain_queries::{
helpers,
register_queries::{
new_register_staking_delegation_query_msg,
register_queries_with_remotechain_id,
update_period_with_chain_id,
new_register_bank_total_balance_query_msg,
update_period,
remove_query,
},
types::{QueryPayload, QueryType as NeutronQueryType},
v045::staking::delegations::DelegationsResponse,
},
sudo::msg::SudoMsg,
NeutronResult, NeutronError,
};
use base64::{engine::general_purpose::STANDARD as BASE64_STANDARD, Engine};
use crate::error::ContractError;
use crate::msg::{
ExecuteMsg, GetDelegatorInfoResponse, GetQueryResponse, GetQueryResultResponse, InstantiateMsg,
QueryMsg,
};
use crate::state::{
Config, DelegatorInfo, INTERCHAIN_QUERIES, QUERY_RESULTS, CONFIG, QueryInfo, QueryResult,
QueryType, ResultType, TransactionResult, Event, Attribute,
};
use crate::queries::cosmos_staking::{create_delegation_key, parse_delegation_response};
// Constants for reply IDs
const REGISTER_QUERY_REPLY_ID: u64 = 1;
#[cfg_attr(not(feature = "library"), entry_point)]
pub fn instantiate(
deps: DepsMut,
_env: Env,
info: MessageInfo,
msg: InstantiateMsg,
) -> Result<Response, ContractError> {
// Save the configuration
let config = Config {
owner: info.sender.clone(),
connection_id: msg.connection_id.clone(),
};
CONFIG.save(deps.storage, &config)?;
Ok(Response::new()
.add_attribute("method", "instantiate")
.add_attribute("owner", info.sender)
.add_attribute("connection_id", msg.connection_id))
}
#[cfg_attr(not(feature = "library"), entry_point)]
pub fn execute(
deps: DepsMut<NeutronQuery>,
env: Env,
info: MessageInfo,
msg: ExecuteMsg,
) -> Result<Response, ContractError> {
match msg {
ExecuteMsg::RegisterQuery {
query_id,
chain_id,
query_type,
update_period,
} => execute_register_query(deps, env, info, query_id, chain_id, query_type, update_period),
ExecuteMsg::RemoveQuery { query_id } => {
execute_remove_query(deps, env, info, query_id)
}
ExecuteMsg::UpdateQuery {
query_id,
new_update_period,
} => execute_update_query(deps, env, info, query_id, new_update_period),
ExecuteMsg::QueryDelegation {
query_id,
chain_id,
delegator,
validator,
update_period,
} => execute_query_delegation(
deps,
env,
info,
query_id,
chain_id,
delegator,
validator,
update_period,
),
}
}
// Register a generic interchain query
fn execute_register_query(
deps: DepsMut<NeutronQuery>,
_env: Env,
info: MessageInfo,
query_id: String,
chain_id: String,
query_type: QueryType,
update_period: u64,
) -> Result<Response, ContractError> {
// Check authorization (owner only)
let config = CONFIG.load(deps.storage)?;
if info.sender != config.owner {
return Err(ContractError::Unauthorized {});
}
// Prepare the registration message based on query type
let (register_msg, query_info) = match query_type.clone() {
QueryType::KV { key, path } => {
// Register KV query
let register_msg = register_queries_with_remotechain_id(
config.connection_id.clone(),
chain_id.clone(),
NeutronQueryType::KV,
QueryPayload {
data: key.clone(),
path: path.clone(),
prove: true,
},
update_period,
vec![],
)?;
let query_info = QueryInfo {
id: query_id.clone(),
connection_id: config.connection_id.clone(),
chain_id: chain_id.clone(),
query_type: QueryType::KV { key, path },
};
(register_msg, query_info)
}
QueryType::TX { height, hash } => {
// Register TX query
let register_msg = register_queries_with_remotechain_id(
config.connection_id.clone(),
chain_id.clone(),
NeutronQueryType::Tx,
QueryPayload {
data: hash.clone().into_bytes(),
path: format!("tx/height/{}", height),
prove: false,
},
update_period,
vec![],
)?;
let query_info = QueryInfo {
id: query_id.clone(),
connection_id: config.connection_id.clone(),
chain_id: chain_id.clone(),
query_type: QueryType::TX { height, hash },
};
(register_msg, query_info)
}
};
// Save query info in contract state
INTERCHAIN_QUERIES.save(deps.storage, query_id.clone(), &query_info)?;
// Register the query via Neutron's interchain_queries module
let submsg = SubMsg::reply_on_success(register_msg, REGISTER_QUERY_REPLY_ID);
Ok(Response::new()
.add_submessage(submsg)
.add_attribute("action", "register_query")
.add_attribute("query_id", query_id)
.add_attribute("query_type", match query_type {
QueryType::KV { .. } => "kv".to_string(),
QueryType::TX { .. } => "tx".to_string(),
}))
}
// Remove a registered interchain query
fn execute_remove_query(
deps: DepsMut<NeutronQuery>,
_env: Env,
info: MessageInfo,
query_id: String,
) -> Result<Response, ContractError> {
// Check authorization (owner only)
let config = CONFIG.load(deps.storage)?;
if info.sender != config.owner {
return Err(ContractError::Unauthorized {});
}
// Ensure the query exists
let query_info = INTERCHAIN_QUERIES.may_load(deps.storage, query_id.clone())?;
let query_info = match query_info {
Some(info) => info,
None => return Err(ContractError::QueryNotFound { query_id }),
};
// Query the interchain query ID from Neutron
let neutron_query_id = helpers::get_registered_query_id_full(
deps.as_ref(),
&query_info.connection_id,
match query_info.query_type {
QueryType::KV { ref key, ref path } => (
NeutronQueryType::KV,
&QueryPayload {
data: key.clone(),
path: path.clone(),
prove: true,
},
),
QueryType::TX { height, ref hash } => (
NeutronQueryType::Tx,
&QueryPayload {
data: hash.clone().into_bytes(),
path: format!("tx/height/{}", height),
prove: false,
},
),
},
)?;
// Remove the query using Neutron's interchain_queries module
let remove_msg = remove_query(neutron_query_id)?;
// Remove from contract state
INTERCHAIN_QUERIES.remove(deps.storage, query_id.clone());
// Also remove any query results
QUERY_RESULTS.remove(deps.storage, query_id.clone());
Ok(Response::new()
.add_message(remove_msg)
.add_attribute("action", "remove_query")
.add_attribute("query_id", query_id))
}
// Update an existing query's parameters
fn execute_update_query(
deps: DepsMut<NeutronQuery>,
_env: Env,
info: MessageInfo,
query_id: String,
new_update_period: u64,
) -> Result<Response, ContractError> {
// Check authorization (owner only)
let config = CONFIG.load(deps.storage)?;
if info.sender != config.owner {
return Err(ContractError::Unauthorized {});
}
// Ensure the query exists
let query_info = INTERCHAIN_QUERIES.may_load(deps.storage, query_id.clone())?;
let query_info = match query_info {
Some(info) => info,
None => return Err(ContractError::QueryNotFound { query_id }),
};
// Query the interchain query ID from Neutron
let neutron_query_id = helpers::get_registered_query_id_full(
deps.as_ref(),
&query_info.connection_id,
match query_info.query_type {
QueryType::KV { ref key, ref path } => (
NeutronQueryType::KV,
&QueryPayload {
data: key.clone(),
path: path.clone(),
prove: true,
},
),
QueryType::TX { height, ref hash } => (
NeutronQueryType::Tx,
&QueryPayload {
data: hash.clone().into_bytes(),
path: format!("tx/height/{}", height),
prove: false,
},
),
},
)?;
// Update the query using Neutron's interchain_queries module
let update_msg = if query_info.chain_id.is_empty() {
update_period(neutron_query_id, new_update_period)?
} else {
update_period_with_chain_id(neutron_query_id, new_update_period, query_info.chain_id)?
};
Ok(Response::new()
.add_message(update_msg)
.add_attribute("action", "update_query")
.add_attribute("query_id", query_id)
.add_attribute("new_update_period", new_update_period.to_string()))
}
// Register a cosmos staking delegation query
fn execute_query_delegation(
deps: DepsMut<NeutronQuery>,
_env: Env,
info: MessageInfo,
query_id: String,
chain_id: String,
delegator: String,
validator: String,
update_period: u64,
) -> Result<Response, ContractError> {
// Check authorization (owner only)
let config = CONFIG.load(deps.storage)?;
if info.sender != config.owner {
return Err(ContractError::Unauthorized {});
}
// Create the delegation key
let key = create_delegation_key(&delegator, &validator);
// Create a query info for storage
let query_info = QueryInfo {
id: query_id.clone(),
connection_id: config.connection_id.clone(),
chain_id: chain_id.clone(),
query_type: QueryType::KV {
key: key.clone(),
path: "store/staking/key".to_string(),
},
};
// Register the query with Neutron
let register_msg = register_queries_with_remotechain_id(
config.connection_id.clone(),
chain_id,
NeutronQueryType::KV,
QueryPayload {
data: key,
path: "store/staking/key".to_string(),
prove: true,
},
update_period,
vec![],
)?;
// Save query info
INTERCHAIN_QUERIES.save(deps.storage, query_id.clone(), &query_info)?;
// Submit the registration
let submsg = SubMsg::reply_on_success(register_msg, REGISTER_QUERY_REPLY_ID);
Ok(Response::new()
.add_submessage(submsg)
.add_attribute("action", "query_delegation")
.add_attribute("query_id", query_id)
.add_attribute("delegator", delegator)
.add_attribute("validator", validator))
}
// Handle the sudo callbacks from the Neutron blockchain
#[cfg_attr(not(feature = "library"), entry_point)]
pub fn sudo(deps: DepsMut, _env: Env, msg: SudoMsg) -> Result<Response, ContractError> {
match msg {
// Process KV query results
SudoMsg::KVQueryResult { query_id, kv_results } => {
process_kv_result(deps, query_id, kv_results)
}
// Process TX query results
SudoMsg::TxQueryResult {
query_id,
tx_results,
} => process_tx_result(deps, query_id, tx_results),
// Ignore other messages
_ => Ok(Response::default()),
}
}
// Process KV query results from the Neutron chain
fn process_kv_result(
deps: DepsMut,
query_id: u64,
kv_results: neutron_sdk::interchain_queries::types::KVResult,
) -> Result<Response, ContractError> {
// Find the query in our storage that corresponds to this Neutron query ID
let mut found_query_id = None;
// Iterate through all registered queries
for item in INTERCHAIN_QUERIES.range(deps.storage, None, None, cosmwasm_std::Order::Ascending) {
let (local_query_id, query_info) = item?;
// Get the Neutron query ID for this query
let neutron_id = match query_info.query_type {
QueryType::KV { ref key, ref path } => helpers::get_registered_query_id_full(
deps.as_ref(),
&query_info.connection_id,
(
NeutronQueryType::KV,
&QueryPayload {
data: key.clone(),
path: path.clone(),
prove: true,
},
),
),
QueryType::TX { height, ref hash } => helpers::get_registered_query_id_full(
deps.as_ref(),
&query_info.connection_id,
(
NeutronQueryType::Tx,
&QueryPayload {
data: hash.clone().into_bytes(),
path: format!("tx/height/{}", height),
prove: false,
},
),
),
};
// Check if this query matches the Neutron query ID
match neutron_id {
Ok(id) if id == query_id => {
found_query_id = Some(local_query_id);
break;
}
_ => continue,
}
}
// If we found a matching query, process the result
if let Some(query_id) = found_query_id {
// Make sure we have a result
if let Some(result) = kv_results.result {
// Create and store a query result
let query_result = QueryResult {
id: query_id.clone(),
result_type: ResultType::KV {
value: result.value,
height: result.revision_height,
},
};
QUERY_RESULTS.save(deps.storage, query_id.clone(), &query_result)?;
return Ok(Response::new()
.add_attribute("action", "process_kv_result")
.add_attribute("query_id", query_id));
}
}
// If we get here, we either didn't find a matching query or there was no result
Ok(Response::new()
.add_attribute("action", "process_kv_result")
.add_attribute("status", "no_matching_query_or_no_result")
.add_attribute("neutron_query_id", query_id.to_string()))
}
// Process TX query results from the Neutron chain
fn process_tx_result(
deps: DepsMut,
query_id: u64,
tx_results: neutron_sdk::interchain_queries::types::TxResult,
) -> Result<Response, ContractError> {
// Find the query in our storage that corresponds to this Neutron query ID
let mut found_query_id = None;
// Iterate through all registered queries
for item in INTERCHAIN_QUERIES.range(deps.storage, None, None, cosmwasm_std::Order::Ascending) {
let (local_query_id, query_info) = item?;
// Get the Neutron query ID for this query
let neutron_id = match query_info.query_type {
QueryType::KV { ref key, ref path } => helpers::get_registered_query_id_full(
deps.as_ref(),
&query_info.connection_id,
(
NeutronQueryType::KV,
&QueryPayload {
data: key.clone(),
path: path.clone(),
prove: true,
},
),
),
QueryType::TX { height, ref hash } => helpers::get_registered_query_id_full(
deps.as_ref(),
&query_info.connection_id,
(
NeutronQueryType::Tx,
&QueryPayload {
data: hash.clone().into_bytes(),
path: format!("tx/height/{}", height),
prove: false,
},
),
),
};
// Check if this query matches the Neutron query ID
match neutron_id {
Ok(id) if id == query_id => {
found_query_id = Some(local_query_id);
break;
}
_ => continue,
}
}
// If we found a matching query, process the result
if let Some(query_id) = found_query_id {
// Make sure we have a result
if let Some(result) = tx_results.result {
// Extract the transaction hash
let tx_hash = if let Some(hash) = result.hash {
hash
} else {
"unknown".to_string()
};
// Extract events
let events = result.tx_result.events.iter().map(|e| {
Event {
r#type: e.r#type.clone(),
attributes: e.attributes.iter().map(|a| {
Attribute {
key: String::from_utf8_lossy(&a.key).to_string(),
value: String::from_utf8_lossy(&a.value).to_string(),
}
}).collect(),
}
}).collect();
// Create and store a query result
let query_result = QueryResult {
id: query_id.clone(),
result_type: ResultType::TX {
tx_hash: tx_hash.clone(),
height: result.height,
tx_result: TransactionResult {
code: result.tx_result.code,
log: result.tx_result.log,
events,
},
},
};
QUERY_RESULTS.save(deps.storage, query_id.clone(), &query_result)?;
return Ok(Response::new()
.add_attribute("action", "process_tx_result")
.add_attribute("query_id", query_id)
.add_attribute("tx_hash", tx_hash));
}
}
// If we get here, we either didn't find a matching query or there was no result
Ok(Response::new()
.add_attribute("action", "process_tx_result")
.add_attribute("status", "no_matching_query_or_no_result")
.add_attribute("neutron_query_id", query_id.to_string()))
}
// Handle replies from SubMsgs
#[cfg_attr(not(feature = "library"), entry_point)]
pub fn reply(_deps: DepsMut, _env: Env, msg: Reply) -> Result<Response, ContractError> {
match msg.id {
REGISTER_QUERY_REPLY_ID => {
// Just acknowledge the registration
Ok(Response::new().add_attribute("action", "query_registered"))
}
_ => Err(ContractError::CustomError {
msg: format!("Unknown reply ID: {}", msg.id),
}),
}
}
// Query handler
#[cfg_attr(not(feature = "library"), entry_point)]
pub fn query(deps: Deps<NeutronQuery>, _env: Env, msg: QueryMsg) -> StdResult<Binary> {
match msg {
QueryMsg::GetQuery { query_id } => to_binary(&query_get_query(deps, query_id)?),
QueryMsg::GetQueryResult { query_id } => to_binary(&query_get_result(deps, query_id)?),
QueryMsg::GetDelegatorInfo { query_id } => to_binary(&query_get_delegator_info(deps, query_id)?),
}
}
// Query for information about a registered query
fn query_get_query(deps: Deps<NeutronQuery>, query_id: String) -> StdResult<GetQueryResponse> {
let query_info = INTERCHAIN_QUERIES.may_load(deps.storage, query_id.clone())?;
match query_info {
Some(info) => Ok(GetQueryResponse {
query_id,
registered: true,
query_type: Some(info.query_type),
}),
None => Ok(GetQueryResponse {
query_id,
registered: false,
query_type: None,
}),
}
}
// Query for the result of a registered query
fn query_get_result(deps: Deps<NeutronQuery>, query_id: String) -> StdResult<GetQueryResultResponse> {
let result = QUERY_RESULTS.may_load(deps.storage, query_id.clone())?;
Ok(GetQueryResultResponse {
query_id,
result,
})
}
// Query for delegator information from a staking query
fn query_get_delegator_info(deps: Deps<NeutronQuery>, query_id: String) -> StdResult<GetDelegatorInfoResponse> {
// Check if the query exists
let query_info = match INTERCHAIN_QUERIES.may_load(deps.storage, query_id.clone())? {
Some(info) => info,
None => return Ok(GetDelegatorInfoResponse {
query_id,
delegator_info: None,
}),
};
// Check if we have a result for this query
let result = match QUERY_RESULTS.may_load(deps.storage, query_id.clone())? {
Some(result) => result,
None => return Ok(GetDelegatorInfoResponse {
query_id,
delegator_info: None,
}),
};
// Make sure this is a KV query result
match result.result_type {
ResultType::KV { value, .. } => {
// Try to parse the staking delegation response
match parse_delegation_response(&value) {
Ok(amount) => {
// Extract delegator and validator from the query type
if let QueryType::KV { key, .. } = query_info.query_type {
// The key contains the delegator and validator information
// We'd need to parse it from the key, but for simplicity,
// let's assume we can derive them from the key or use placeholder values
// In a real implementation, you'd extract these from the key
let delegator = String::from_utf8_lossy(&key[12..key.len()-33]).to_string();
let validator = String::from_utf8_lossy(&key[key.len()-32..]).to_string();
Ok(GetDelegatorInfoResponse {
query_id,
delegator_info: Some(DelegatorInfo {
delegator,
validator,
amount,
}),
})
} else {
Ok(GetDelegatorInfoResponse {
query_id,
delegator_info: None,
})
}
}
Err(_) => Ok(GetDelegatorInfoResponse {
query_id,
delegator_info: None,
}),
}
}
_ => Ok(GetDelegatorInfoResponse {
query_id,
delegator_info: None,
}),
}
}
lib.rs
to include all modules:
pub mod contract;
pub mod error;
pub mod helpers;
pub mod msg;
pub mod state;
pub mod queries;
pub use crate::error::ContractError;
neutrond tx wasm instantiate <code-id> '{"connection_id":"connection-0"}' \
--from wallet \
--label "ICQ Example" \
--admin $(neutrond keys show wallet -a) \
--gas-prices 0.025untrn \
--gas auto \
--gas-adjustment 1.3 \
-b block
neutrond tx wasm execute <contract-address> \
'{"query_delegation": {"query_id": "my_delegation_query", "chain_id": "cosmoshub-4", "delegator": "cosmos1...", "validator": "cosmosvaloper1...", "update_period": 600}}' \
--from wallet \
--gas-prices 0.025untrn \
--gas auto \
--gas-adjustment 1.3 \
-b block
neutrond query wasm contract-state smart <contract-address> \
'{"get_delegator_info": {"query_id": "my_delegation_query"}}' \
--output json
update_period
for your use case.