[intent-coprocessor]: Price Discovery Protocol#684
Conversation
| /// The submitter's substrate account | ||
| pub submitter: AccountId, | ||
| /// The submitted price | ||
| pub price: U256, |
There was a problem hiding this comment.
Let the price be a bounded string, so we don't have to worry about decimals
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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, |
There was a problem hiding this comment.
Let's use the filler's evm address here
|
|
||
| // Check proof freshness | ||
| let threshold = ProofFreshnessThresholdValue::<T>::get(); | ||
| let proof_gap = commitment_h2.timestamp.saturating_sub(commitment_h1.timestamp); |
There was a problem hiding this comment.
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, |
There was a problem hiding this comment.
| pair_id: H256, | |
| entries: BoundedVec<PriceEntry>, |
|
|
||
| 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. |
There was a problem hiding this comment.
| 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.
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_priceextrinsic 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 optionalverificationparameter.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:
Two ISMP state proofs against the IntentGateway contract's
_filledmapping. 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.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.
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 inpallet-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
VerifiedPriceswith 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
UnverifiedPricesand capped per pair atMaxUnverifiedSubmissions. 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_initializehook runs every block and checks whether the current window has expired. When it has, it clearsUsedCommitments(safe because the freshness threshold independently rejects stale proofs) and resets aPricesClearedThisWindowflag 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_initializewhile 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_pairandremove_recognized_pairextrinsics. 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 millisecondsProofFreshnessThresholdValue— maximum allowed age/gap for state proofs in secondsMaxUnverifiedSubmissions— cap on unverified entries per pairUnverifiedSubmissionFee— fee charged for unverified submissions