Skip to main content

Interchain Queries (ICQ)

The Interchain Query (ICQ) feature enables seamless cross-chain interactions by querying the state of one blockchain from another. This feature allows developers to create intent-based actions that trigger specific behaviors based on the queried data. By utilizing interchain queries, you can automate decision-making processes across multiple blockchains, providing an efficient way to orchestrate complex, multi-chain applications.

How It Works

Interchain queries allow you to access the key-value store of a different blockchain by providing a specific key to query. IBC relayers submit query responses. This queried state can then be used to determine what actions should be triggered, based on predefined conditions. For instance, you can check the balance of a particular account on one blockchain and then execute a corresponding action on another chain, such as staking, transferring, or adjusting governance proposals.

Supported Types and Proto Interfaces

You can query various data types from the state of other chains. There are two primary methods to accomplish this:

  1. Supported Types: You can use one of the supported types, which are predefined and commonly used data types within the Cosmos ecosystem.
  2. Registered Proto Interfaces: Alternatively, you can utilize the registered protocol buffer interfaces that adhere to Cosmos SDK standards. These provide a more flexible way to query and interpret the state, allowing you to work with complex data structures.

Using TwapRecord in Intento Flows

TwapRecord provides accumulated pricing data for AMM pools, enabling flows to make time-weighted average price (TWAP)-based decisions. This is particularly useful for automated trading strategies, streaming swaps, and DCA execution.

Key Fields and Usage

FieldDescriptionFlow ModeUse Case
p0_arithmetic_twap_accumulatorAccumulated numerator for arithmetic TWAP of asset0Difference-modeCompute average price over a fixed interval for DCA, autocompounding, or trend detection.
p1_arithmetic_twap_accumulatorAccumulated numerator for asset1Difference-modeSame as above for the paired asset.
geometric_twap_accumulatorAccumulated value for geometric/log TWAPDifference-modeVolatility-aware or growth-rate strategies.
p0_last_spot_priceLatest spot price of asset0Current valueSpot-based triggers, arbitrage checks, or interpolation if exact interval boundaries are missing.
p1_last_spot_priceLatest spot price of asset1Current valueSame as above for the paired asset.
timeTimestamp of the recordContext-dependentAnchors for interpolation or freshness checks.
heightBlock height of the recordDifference-modeAnchors for delta calculations.
last_error_timeTime of last spot price errorCurrent valueFail-safe: flows should ignore TWAP values after this timestamp.

Got it. Let’s rewrite the ICQ documentation with the realistic math.Dec operands, the actual enum operator names, and more context for users on why/when to use these strategies. I’ll keep it technical but make sure it’s framed so builders immediately see how to plug this into their flows.

🔗 ICQ + TWAP in Intento Flows

With ICQ, flows can now pull on-chain state directly from another chain (e.g. Osmosis) and use it in execution conditions. The most powerful use case is TWAP (Time-Weighted Average Price), which allows strategies to react to price averages instead of just the latest spot price.

This helps you:

  • Smooth out volatility (avoid one-off spikes).
  • Detect real trends rather than noise.
  • Build guardrails around execution.
  • Create arbitrage-like conditions between spot and TWAP.

How ICQ is used in conditions

  • Attach icq_config to a Comparison or FeedbackLoop.

  • Define the query (query_type, query_key) → the Intento Portal can generate the right key for you.

  • The response_key tells the flow which field inside the store object (e.g. p0_arithmetic_twap_accumulator) to read.

  • operand is always a math.Dec string (high precision decimal).

    • Example: "0.000000000000000100".
  • operator must use the enum names:

    • SMALLER_THAN, LARGER_THAN, GREATER_EQUAL, etc.
  • Use difference_mode = true for accumulators → otherwise you’re comparing absolute counter values, not the delta.

Examples

1. TWAP-based DCA

Why: Spread buys over time, only execute if TWAP shows asset is undervalued. How: Compare TWAP delta against a static threshold.

"comparisons": [
{
"response_key": "p0_arithmetic_twap_accumulator",
"value_type": "osmosistwapv1beta1.TwapRecord",
"operator": "SMALLER_THAN",
"operand": "0.000000000000000100",
"difference_mode": true,
"icq_config": {
"connection_id": "connection-0",
"chain_id": "osmosis-1",
"query_type": "store/twap/key",
"query_key": "<generated_twap_key>"
}
}
]

2. Trend Detection

Why: Avoid buying in chop. Only act when TWAP delta is positive (uptrend) or negative (downtrend). How: Compare TWAP delta against zero.

"comparisons": [
{
"response_key": "p0_arithmetic_twap_accumulator",
"value_type": "osmosistwapv1beta1.TwapRecord",
"operator": "LARGER_THAN",
"operand": "0.000000000000000000",
"difference_mode": true,
"icq_config": {
"connection_id": "connection-0",
"chain_id": "osmosis-1",
"query_type": "store/twap/key",
"query_key": "<generated_twap_key>"
}
}
]

3. Spot vs TWAP Arbitrage

Why: Capture inefficiencies between current spot and averaged price. How: Spot smaller than TWAP → asset is cheap compared to average.

"comparisons": [
{
"response_key": "p0_last_spot_price",
"value_type": "osmosistwapv1beta1.TwapRecord",
"operator": "SMALLER_THAN",
"operand": "osmosistwapv1beta1.TwapRecord.GeometricTwapAccumulator",
"difference_mode": false,
"icq_config": {
"connection_id": "connection-0",
"chain_id": "osmosis-1",
"query_type": "store/twap/key",
"query_key": "<generated_twap_key>"
}
}
]

4. Safe Compounding (Guardrail)

Why: Avoid autocompounding when markets are unstable. How: Require TWAP drift below a threshold before execution continues.

"comparisons": [
{
"response_key": "p0_arithmetic_twap_accumulator",
"value_type": "osmosistwapv1beta1.TwapRecord",
"operator": "LESS_EQUAL",
"operand": "0.000000000000000050",
"difference_mode": true,
"icq_config": {
"connection_id": "connection-0",
"chain_id": "osmosis-1",
"query_type": "store/twap/key",
"query_key": "<generated_twap_key>"
}
}
]

Quick Recap for Builders

  • Use difference_mode = true when querying TWAP accumulators.
  • Operators are enums (SMALLER_THAN, LARGER_THAN, etc.), not symbols.
  • Operands are high-precision decimals (math.Dec), not floats.
  • For Spot vs TWAP, the operand can be another TWAP field → no static threshold required.

This makes it possible to write DCA bots, arbitrage triggers, or safe autocompound flows directly on-chain using Intento.

Feedback Loops and Comparisons

Once the queried data is retrieved, it can be used for comparisons or to establish feedback loops. For example, if a queried balance exceeds a certain threshold, you can trigger an action to stake the excess funds. Likewise, if a validator's status on one chain changes, you can automatically adjust delegations or governance votes on another chain.

This capability unlocks numerous possibilities for cross-chain workflows, simplifying multi-chain dApp logic and empowering developers to build more dynamic and responsive applications in the interchain ecosystem.

Integrating ICQs

You can use the ICQ feature by attaching an ICQConfig into comparisons and feedback loops. In the ICQConfig, you specify what to query, where to query it, and how to handle a timeout scenario.

FieldTypeDescription
connection_idstringThe ID of the connection to use for the interchain query.
chain_idstringThe ID of the blockchain to query.
timeout_policyintento.interchainquery.v1.TimeoutPolicyThe policy to apply when a timeout occurs.
timeout_durationgoogle.protobuf.DurationThe duration to wait before a timeout is triggered.
query_typestringThe type of query to perform (e.g., store/bank/key, store/staking/key).
query_keystringThe key in the store to query (e.g., stakingtypes.GetValidatorKey(validatorAddressBz)).

For example, the query_type can be store/bank/key or store/staking/key. The query_key is the key in the store to query, such as stakingtypes.GetValidatorKey(validatorAddressBz). The generation of query keys is abstracted in the Intento Portal frontend.

// Config for using interchain queries
message ICQConfig {
string connection_id = 1;
string chain_id = 2;
intento.interchainquery.v1.TimeoutPolicy timeout_policy = 3;
google.protobuf.Duration timeout_duration = 4 [
(gogoproto.nullable) = false,
(gogoproto.stdduration) = true
];
string query_type = 5;
string query_key = 6;
}

If SaveResponses in the Flow Configuration is set to true, query responses are added to the Flow History. Check out the Supported Types page or the Intento Portal Flow Builder for some example queries.

Here’s a tutorial for integratingICQConfig for querying balances and adding connectionId, hostConnectionId, as well as a TrustlessAgentConfig in a new submitFlow.

Example: Conditional Transfers with ICQ

A common use case for Interchain Queries is to execute conditional transfers based on account balances. For example, you might want to automatically transfer funds only when an account's balance exceeds a certain threshold.

We've created a detailed tutorial that walks through implementing this scenario:

Conditional Transfers with ICQ - Learn how to create an automated flow that:

  • Queries an account's balance using ICQ
  • Checks if the balance exceeds a specified amount
  • Only executes a transfer if the condition is met
  • Uses feedback loops to dynamically set transfer amounts

This tutorial provides complete, working code examples that you can adapt for your own use cases.

ICQ module details - x/interchainqueries

SubmitQueryResponse is used to return the query responses. It is used by IBC Relayers. As we use the Stride implementation, it is the same as with Stride. In your relayer config please set allow_ccq to true or leave unconfigured. Also the event_source fields have to be set to push.

message MsgSubmitQueryResponse {
string chain_id = 1;
string query_id = 2;
bytes result = 3;
tendermint.crypto.ProofOps proof_ops = 4;
int64 height = 5;
string from_address = 6;
}

Query PendingQueries lists all queries that have been requested (i.e. emitted but have not had a response submitted yet)

message QueryPendingQueriesRequest {}