Skip to content

[intent-coprocessor]: Price Discovery Protocol#684

Open
dharjeezy wants to merge 8 commits intomainfrom
dami/filler-price-pair-update
Open

[intent-coprocessor]: Price Discovery Protocol#684
dharjeezy wants to merge 8 commits intomainfrom
dami/filler-price-pair-update

Conversation

@dharjeezy
Copy link
Contributor

@dharjeezy dharjeezy commented Mar 11, 2026

Price Discovery Protocol

Overview

The intents system needs on-chain price data for token pairs to function correctly. The challenge is sourcing reliable prices without depending on external oracles, while still ensuring data availability when verified sources are temporarily scarce.

Protocol Overview

The submit_pair_price extrinsic on the intents coprocessor pallet accepts price submissions for governance-approved token pairs. It supports two confidence levels through a single entry point, distinguished by an optional verification parameter.

Verified Prices (High Confidence)

Fillers who have actually filled orders on the IntentGateway contract submit prices along with cryptographic proof of their fill. This path exists because fillers are the most trustworthy source of price data — they have real skin in the game and their prices reflect actual market activity.

A verified submission requires three things:

  1. Two ISMP state proofs against the IntentGateway contract's _filled mapping. A non-membership proof at height H1 shows the order was not yet filled, and a membership proof at height H2 shows it was filled. Together these bracket when the fill occurred and extract the filler's EVM address from storage.

  2. Proof freshness. Both the gap between H1 and H2, and the age of H2 relative to the current time, must fall within a governance-configured threshold. This check ensures we are getting verified price data from fillers that are actually active and have filled orders very recently.

  3. An EVM signature over keccak256(SCALE_encode(nonce, pair_id, price)). This proves the substrate account submitting the price actually controls the EVM account that performed the fill. The nonce is tracked on-chain per EVM address to prevent signature replay. This pattern follows the existing approach in pallet-ismp-relayer.

The 52-byte proof key binds verification to a specific gateway contract: the first 20 bytes are the gateway address (used by the EVM state machine client to locate the contract in the world state trie) and the last 32 bytes are the hashed storage slot for _filled[commitment].

Verified prices are stored in VerifiedPrices with no cap — the goal is to accumulate as many real data points as possible within each price window.

Unverified Prices (Low Confidence)

Anyone can submit a price without proofs by paying a fee in bridge tokens. This path exists to maintain price data availability even when verified submissions are sparse. The fee (transferred to the treasury) discourages spam while keeping the door open for market participants who have price information but haven't filled orders themselves.

Unverified prices are stored separately in UnverifiedPrices and capped per pair at MaxUnverifiedSubmissions. When the cap is reached, the oldest entry is replaced (FIFO). Both the fee and the cap must be configured via governance for unverified submissions to be accepted.

Why Two Confidence Levels

Keeping verified and unverified prices separate lets consumers make informed decisions. A DAPP might only trust verified prices for settlement, while a UI might display both to give users a fuller picture. Mixing them into a single pool would dilute the signal from proven fills.

Price Window and Data Lifecycle

Prices are organized into daily windows (governance-configurable via PriceWindowDurationValue).

The on_initialize hook runs every block and checks whether the current window has expired. When it has, it clears UsedCommitments (safe because the freshness threshold independently rejects stale proofs) and resets a PricesClearedThisWindow flag to false.

Prices from the previous window are not cleared immediately, they persist so that consumers can still read yesterday's data. On the first new submission in the new window (verified or unverified), all price entries across all pairs are cleared before the new entry is stored. This lazy clearing approach avoids the cost of iterating all pairs in on_initialize while ensuring stale data is replaced as soon as fresh data arrives.

Recognized Pairs

Only governance-approved token pairs can receive price submissions. This prevents spam for arbitrary token combinations and keeps storage growth under control. Governance manages pairs through add_recognized_pair and remove_recognized_pair extrinsics. Removing a pair also cleans up its associated price data.

RPC

The intents_getPairPrices(pair_id) RPC endpoint returns verified and unverified prices separately, allowing consumers to apply their own weighting or filtering based on confidence level.

Governance Parameters

All key parameters are stored on-chain and updatable via governance extrinsics:

  • PriceWindowDurationValue — length of the price window in milliseconds
  • ProofFreshnessThresholdValue — maximum allowed age/gap for state proofs in seconds
  • MaxUnverifiedSubmissions — cap on unverified entries per pair
  • UnverifiedSubmissionFee — fee charged for unverified submissions
  • Recognized token pairs — which pairs accept submissions

@dharjeezy dharjeezy changed the title [intent-coprocessor]: allow fillers to sumbmit pair price using membership and non membership proofs [intent-coprocessor]: allow fillers to sumbmit pair price using membership and non membership filled order proofs Mar 11, 2026
@dharjeezy dharjeezy requested a review from Wizdave97 March 11, 2026 14:14
/// The submitter's substrate account
pub submitter: AccountId,
/// The submitted price
pub price: U256,
Copy link
Member

@Wizdave97 Wizdave97 Mar 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let the price be a bounded string, so we don't have to worry about decimals

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can leave this, we can assume that all prices are 18 decimals

/// An individual price submission stored on-chain
#[derive(Clone, Debug, Encode, Decode, DecodeWithMemTracking, TypeInfo, PartialEq, Eq)]
pub struct PriceEntry<AccountId> {
/// The submitter's substrate account
Copy link
Member

@Wizdave97 Wizdave97 Mar 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, the price entry should specify a range denominated in the base token. Fillers would have different rates for different price ranges, e.g., for the pair USDC/CNGN 0 - 999 -> cNGN 1414, 1000 - 5000 -> cNGN 1420

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's also assume 18 decimals for the price range here

#[derive(Clone, Debug, Encode, Decode, DecodeWithMemTracking, TypeInfo, PartialEq, Eq)]
pub struct PriceEntry<AccountId> {
/// The submitter's substrate account
pub submitter: AccountId,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's use the filler's evm address here

@dharjeezy dharjeezy changed the title [intent-coprocessor]: allow fillers to sumbmit pair price using membership and non membership filled order proofs [intent-coprocessor]: Price Submission Protocol Mar 12, 2026

// Check proof freshness
let threshold = ProofFreshnessThresholdValue::<T>::get();
let proof_gap = commitment_h2.timestamp.saturating_sub(commitment_h1.timestamp);
Copy link
Member

@Wizdave97 Wizdave97 Mar 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To complete the validation that this commitment is fresh enough, we also need to do ensure!(commitment_h2.timestamp.saturating_sub(now) <= threshold, Error::<T>::ProofNotFresh );

/// EVM signature verification.
fn submit_verified_price(
submitter: T::AccountId,
pair_id: H256,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
pair_id: H256,
entries: BoundedVec<PriceEntry>,

@Wizdave97 Wizdave97 changed the title [intent-coprocessor]: Price Submission Protocol [intent-coprocessor]: Price Discovery Protocol Mar 13, 2026

1. **Two ISMP state proofs** against the IntentGateway contract's `_filled` mapping. A non-membership proof at height H1 shows the order was not yet filled, and a membership proof at height H2 shows it was filled. Together these bracket when the fill occurred and extract the filler's EVM address from storage.

2. **Proof freshness.** Both the gap between H1 and H2, and the age of H2 relative to the current time, must fall within a governance-configured threshold. This prevents stale or manufactured proofs from being accepted.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
2. **Proof freshness.** Both the gap between H1 and H2, and the age of H2 relative to the current time, must fall within a governance-configured threshold. This prevents stale or manufactured proofs from being accepted.
2. **Proof freshness.** Both the gap between H1 and H2, and the age of H2 relative to the current time, must fall within a governance-configured threshold. This check ensures we are getting verified price data from fillers that are actually active and have filled orders very recently. Allowing us offer high confidence rates to users

…in the ProxyModule to passively build a VerifiedFillers map, enabling a third price

  submission path that requires only an EVM signature instead of full ISMP state proofs.
@dharjeezy dharjeezy requested a review from Wizdave97 March 14, 2026 05:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants