Skip to main content
In this tutorial, you’ll extend your knowledge by learning how to interact with Neutron’s unique modules and call other smart contracts. This is where Neutron’s power really shines.

What You’ll Learn

  • How to use Neutron’s custom modules (Oracle, Cron, ICQ)
  • Inter-contract communication patterns
  • Advanced message handling and responses
  • Working with Neutron-specific message types

Prerequisites

Neutron’s Unique Modules

Neutron provides several custom modules that enable powerful DeFi functionality:

Oracle

Get high-frequency price data for any asset

Cron

Schedule automated contract executions

ICQ

Query data from other IBC-connected chains

Enhanced Contract: Oracle Integration

Let’s extend our counter contract to use Neutron’s Oracle module. We’ll create a contract that tracks the price of ATOM and only allows increments when the price is above a certain threshold.

1. Update Dependencies

Add to your Cargo.toml:
[dependencies]
cosmwasm-std = "1.0"
cw-storage-plus = "1.0"
serde = { version = "1.0", default-features = false, features = ["derive"] }
neutron-sdk = "0.8"  # Add Neutron SDK

2. Enhanced State

Update src/state.rs:
use cosmwasm_std::{Uint128, Decimal};
use cw_storage_plus::Item;
use neutron_sdk::bindings::types::ProtobufAny;

// Storage
pub const COUNTER: Item<Uint128> = Item::new("counter");
pub const PRICE_THRESHOLD: Item<Decimal> = Item::new("price_threshold");
pub const LAST_PRICE: Item<Decimal> = Item::new("last_price");

// Configuration
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
pub struct Config {
    pub price_threshold: Decimal,
    pub oracle_base: String,
    pub oracle_quote: String,
}

3. Enhanced Messages

Update src/msg.rs:
use cosmwasm_std::{Uint128, Decimal};
use serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
pub struct InstantiateMsg {
    pub initial_count: Uint128,
    pub price_threshold: Decimal,
    pub oracle_base: String,
    pub oracle_quote: String,
}

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
pub enum ExecuteMsg {
    Increment { amount: Uint128 },
    UpdateThreshold { new_threshold: Decimal },
    CheckPriceAndIncrement { amount: Uint128 },
}

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
pub enum QueryMsg {
    GetCount {},
    GetConfig {},
    GetLastPrice {},
}

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
pub struct CountResponse {
    pub count: Uint128,
}

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
pub struct ConfigResponse {
    pub price_threshold: Decimal,
    pub oracle_base: String,
    pub oracle_quote: String,
}

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
pub struct PriceResponse {
    pub price: Decimal,
}

4. Oracle Integration

Update src/contract.rs:
use cosmwasm_std::{
    entry_point, to_binary, Binary, Deps, DepsMut, Env, MessageInfo, 
    Response, StdResult, Uint128, Decimal, StdError,
};
use neutron_sdk::bindings::msg::NeutronMsg;
use neutron_sdk::bindings::query::NeutronQuery;

use crate::msg::{ExecuteMsg, InstantiateMsg, QueryMsg, CountResponse, ConfigResponse, PriceResponse};
use crate::state::{COUNTER, PRICE_THRESHOLD, LAST_PRICE, Config};

#[entry_point]
pub fn instantiate(
    deps: DepsMut,
    _env: Env,
    _info: MessageInfo,
    msg: InstantiateMsg,
) -> StdResult<Response> {
    // Save initial state
    COUNTER.save(deps.storage, &msg.initial_count)?;
    PRICE_THRESHOLD.save(deps.storage, &msg.price_threshold)?;
    
    // Save oracle configuration
    let config = Config {
        price_threshold: msg.price_threshold,
        oracle_base: msg.oracle_base,
        oracle_quote: msg.oracle_quote,
    };
    
    Ok(Response::new()
        .add_attribute("action", "instantiate")
        .add_attribute("threshold", msg.price_threshold.to_string()))
}

#[entry_point]
pub fn execute(
    deps: DepsMut,
    env: Env,
    info: MessageInfo,
    msg: ExecuteMsg,
) -> StdResult<Response> {
    match msg {
        ExecuteMsg::Increment { amount } => increment(deps, amount),
        ExecuteMsg::UpdateThreshold { new_threshold } => {
            update_threshold(deps, info, new_threshold)
        }
        ExecuteMsg::CheckPriceAndIncrement { amount } => {
            check_price_and_increment(deps, env, amount)
        }
    }
}

fn increment(deps: DepsMut, amount: Uint128) -> StdResult<Response> {
    if amount > Uint128::from(100u128) {
        return Err(StdError::generic_err("Cannot increment by more than 100"));
    }
    
    let current = COUNTER.load(deps.storage)?;
    let new_count = current + amount;
    COUNTER.save(deps.storage, &new_count)?;
    
    Ok(Response::new()
        .add_attribute("action", "increment")
        .add_attribute("amount", amount.to_string())
        .add_attribute("new_count", new_count.to_string()))
}

fn check_price_and_increment(
    deps: DepsMut, 
    env: Env, 
    amount: Uint128
) -> StdResult<Response> {
    // Query current price from Oracle module
    let price_query = NeutronQuery::Oracle {
        base: "ATOM".to_string(),
        quote: "USD".to_string(),
    };
    
    let price_response: PriceResponse = deps.querier.query(&price_query.into())?;
    let current_price = price_response.price;
    
    // Save the price for future reference
    LAST_PRICE.save(deps.storage, &current_price)?;
    
    // Check if price meets threshold
    let threshold = PRICE_THRESHOLD.load(deps.storage)?;
    if current_price < threshold {
        return Err(StdError::generic_err(
            format!("Price {} is below threshold {}", current_price, threshold)
        ));
    }
    
    // Price is good, increment the counter
    increment(deps, amount)
}

#[entry_point]
pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult<Binary> {
    match msg {
        QueryMsg::GetCount {} => {
            let count = COUNTER.load(deps.storage)?;
            let response = CountResponse { count };
            to_binary(&response)
        }
        QueryMsg::GetLastPrice {} => {
            let price = LAST_PRICE.may_load(deps.storage)?.unwrap_or_default();
            let response = PriceResponse { price };
            to_binary(&response)
        }
        QueryMsg::GetConfig {} => {
            let threshold = PRICE_THRESHOLD.load(deps.storage)?;
            let response = ConfigResponse {
                price_threshold: threshold,
                oracle_base: "ATOM".to_string(),
                oracle_quote: "USD".to_string(),
            };
            to_binary(&response)
        }
    }
}

Inter-Contract Communication

Contracts can call other contracts using the WasmMsg::Execute message type. Here’s how to call another contract:
use cosmwasm_std::{WasmMsg, CosmosMsg, to_binary};

pub fn call_other_contract(
    contract_addr: String,
    msg: OtherContractMsg,
) -> StdResult<Response> {
    let wasm_msg = WasmMsg::Execute {
        contract_addr,
        msg: to_binary(&msg)?,
        funds: vec![], // Send tokens if needed
    };
    
    Ok(Response::new()
        .add_message(CosmosMsg::Wasm(wasm_msg))
        .add_attribute("action", "call_other_contract"))
}

Using the Cron Module

Schedule automated executions using Neutron’s Cron module:
use neutron_sdk::bindings::msg::NeutronMsg;

pub fn schedule_periodic_increment(
    deps: DepsMut,
    env: Env,
) -> StdResult<Response> {
    let cron_msg = NeutronMsg::AddSchedule {
        name: "daily_increment".to_string(),
        period: 86400, // 24 hours in seconds
        msgs: vec![
            WasmMsg::Execute {
                contract_addr: env.contract.address.to_string(),
                msg: to_binary(&ExecuteMsg::Increment { 
                    amount: Uint128::from(1u128) 
                })?,
                funds: vec![],
            }.into()
        ],
    };
    
    Ok(Response::new()
        .add_message(cron_msg)
        .add_attribute("action", "schedule_created"))
}

Advanced Patterns

1. Contract Factory Pattern

Create contracts that can instantiate other contracts:
use cosmwasm_std::WasmMsg;

pub fn create_child_contract(
    code_id: u64,
    init_msg: ChildContractMsg,
    label: String,
) -> StdResult<Response> {
    let instantiate_msg = WasmMsg::Instantiate {
        admin: None,
        code_id,
        msg: to_binary(&init_msg)?,
        funds: vec![],
        label,
    };
    
    Ok(Response::new()
        .add_message(instantiate_msg)
        .add_attribute("action", "create_child"))
}

2. Cross-Chain Operations

Use Interchain Queries to get data from other chains:
use neutron_sdk::interchain_queries::v045::types::QueryRequest;

pub fn register_balance_query(
    connection_id: String,
    address: String,
) -> StdResult<Response> {
    let icq_msg = NeutronMsg::RegisterInterchainQuery {
        query_type: QueryRequest::Balance {
            address,
            denom: "uatom".to_string(),
        },
        transactions_filter: "".to_string(),
        connection_id,
        update_period: 10,
    };
    
    Ok(Response::new()
        .add_message(icq_msg)
        .add_attribute("action", "register_icq"))
}

Testing Your Enhanced Contract

1. Build and Deploy

# Build the enhanced contract
cargo wasm
docker run --rm -v "$(pwd)":/code \
  --mount type=volume,source="$(basename "$(pwd)")_cache",target=/code/target \
  --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \
  cosmwasm/rust-optimizer:0.12.13

# Deploy with oracle configuration
neutrond tx wasm instantiate CODE_ID \
  '{"initial_count":"0","price_threshold":"10.0","oracle_base":"ATOM","oracle_quote":"USD"}' \
  --from your-key \
  --label "oracle-counter" \
  --chain-id neutron-testing-1 \
  --gas auto \
  --node http://localhost:26657 \
  --yes

2. Test Oracle Integration

# Check price and increment (only works if ATOM > $10)
neutrond tx wasm execute CONTRACT_ADDRESS \
  '{"check_price_and_increment":{"amount":"5"}}' \
  --from your-key \
  --chain-id neutron-testing-1 \
  --gas auto \
  --node http://localhost:26657 \
  --yes

# Query last price
neutrond query wasm contract-state smart CONTRACT_ADDRESS \
  '{"get_last_price":{}}' \
  --node http://localhost:26657

Key Concepts Learned

  • Module Integration: Using Neutron’s custom modules in contracts
  • Inter-Contract Calls: Calling other smart contracts
  • Oracle Queries: Getting real-time price data
  • Cron Scheduling: Automating contract executions
  • Cross-Chain Queries: Accessing data from other blockchains
  • Advanced Patterns: Factory contracts and complex workflows

Best Practices

  1. Error Handling: Always handle Oracle query failures gracefully
  2. Gas Limits: Be mindful of gas costs when calling multiple contracts
  3. State Management: Cache frequently accessed data to reduce costs
  4. Security: Validate all external data and implement proper access controls

Next Steps

Continue to Part 3: Building a Web App to learn how to create a frontend that interacts with your enhanced smart contract.

Troubleshooting

Ensure the Oracle module is running and the currency pair exists:
neutrond query oracle params
Verify the target contract address and message format are correct.
Check that the Cron module is enabled and your schedule is valid:
neutrond query cron schedules
I