Skip to content

[intent-coprocessor]: Price Discovery Protocol#684

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

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

Conversation

@dharjeezy
Copy link
Copy Markdown
Contributor

@dharjeezy dharjeezy commented Mar 11, 2026

Overview

This PR introduces a deposit-based price submission protocol for the intents coprocessor. The intents system needs on-chain price data for token pairs to function correctly. Rather than depending on external oracles, this protocol allows anyone to submit prices for governance-approved token pairs by putting up a one-time deposit. Subsequent updates to the same pair are free, and the deposit can be reclaimed through a two-phase withdrawal process.

The implementation spans the pallet, RPC, SDK, and simplex filler.

Pallet

The submit_pair_price extrinsic accepts price submissions for governance-approved token pairs. Each submission contains one or more price entries specifying a base token amount range and the corresponding price (all encoded as U256 scaled by 10^18). On the first submission per (account, pair), a configurable deposit is reserved from the submitter's balance. Further updates to the same pair are free.

Deposits are withdrawn through a two-phase process via withdraw_price_deposit. The first call records the unlock block (current block + governance-configured lock duration in blocks). The second call, after the unlock block is reached, unreserves the tokens and removes the deposit record. Calling too early fails with DepositStillLocked.

Prices are organized into time-based windows. When a window expires, existing price data is lazily cleared on the first new submission rather than in on_initialize.

Governance extrinsics control recognized token pairs, deposit amounts, lock duration, and window duration.

RPC

The intents_getPairPrices(pair_id) endpoint returns all price entries for a given pair. On-chain U256 values (scaled by 10^18) are converted to human-readable decimal strings with fractional precision preserved (e.g. 1414500000000000000000 becomes "1414.5").

SDK

IntentsCoprocessor in @hyperbridge/sdk exposes submitPairPrice(pairId, entries) and withdrawPriceDeposit(pairId) for interacting with the pallet from TypeScript. The signAndSendExtrinsic helper checks for dispatch errors within the isInBlock/isFinalized status handler so that on-chain failures are correctly reported.

Simplex filler integration

A PriceUpdateService in @hyperbridge/simplex handles periodic price submission on a configurable interval (default 5 minutes). It iterates through all configured pairs and calls submitPairPrice for each one. The IntentFiller starts this service automatically when a [priceUpdates] section is present in the TOML config, with human-readable amounts converted to 18-decimal format at parse time.

@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
@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
@Wizdave97 Wizdave97 changed the title [intent-coprocessor]: Price Submission Protocol [intent-coprocessor]: Price Discovery Protocol Mar 13, 2026
…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
Ok(entries) => Ok(entries
.into_iter()
.map(|(range_start, range_end, price, timestamp)| RpcPriceEntry {
range_start: range_start.to_string(),
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Let's divide the prices by 10**18 here to cnvert it to human readable value before returning

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Preserve the decimals during this conversion

<T as Config>::Currency::reserve(&submitter, deposit_amount)
.map_err(|_| Error::<T>::InsufficientBalance)?;

PriceDeposits::<T>::insert(&submitter, &pair_id, (deposit_amount, now));
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The deposit lock duration is only relevant to withdrawals.

When a Filler wants to withdraw, they submit an extrinsic to initiate it, then we note the block at which the tokens can be unreserved using the configured pricelockduration, The filler can complete the withdrawal only after that block has elapsed

@dharjeezy dharjeezy requested a review from Wizdave97 March 17, 2026 06:59

Submissions are batched. Each call accepts up to `MaxPriceEntries` entries (a compile-time constant configurable per runtime), where each entry specifies a base token amount range and the corresponding price of the base token in terms of the quote token. This makes it possible to quote different rates for different order sizes in a single transaction. For example, a submitter might quote USDC/CNGN at 1414 for orders between 0 and 999, and 1420 for orders between 1000 and 5000.

Each price entry contains three fields. The `range_start` field is the lower bound of the base token amount range (inclusive). The `range_end` field is the upper bound (also inclusive). The `price` field is the price of the base token in terms of the quote token. The pallet validates that `range_start` is less than or equal to `range_end` for every entry and rejects empty submissions. When stored on-chain, each entry becomes a `PriceEntry` that also includes the submission timestamp.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
Each price entry contains three fields. The `range_start` field is the lower bound of the base token amount range (inclusive). The `range_end` field is the upper bound (also inclusive). The `price` field is the price of the base token in terms of the quote token. The pallet validates that `range_start` is less than or equal to `range_end` for every entry and rejects empty submissions. When stored on-chain, each entry becomes a `PriceEntry` that also includes the submission timestamp.
Each price entry contains three fields. The `range_start` field is the lower bound of the base token amount range (inclusive). The `range_end` field is the upper bound (also inclusive). The `price` field is the cost of one unit of the base token in terms of the quote token. The pallet validates that `range_start` is less than or equal to `range_end` for every entry and rejects empty submissions. When stored on-chain, each entry becomes a `PriceEntry` that also includes the submission timestamp.

Price updates are configured by adding a `[priceUpdates]` section to the filler TOML configuration file.

```toml
[priceUpdates]
Copy link
Copy Markdown
Member

@Wizdave97 Wizdave97 Mar 17, 2026

Choose a reason for hiding this comment

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

We do not need this, the fx strategy config already has a price policy, we should just use the values specified there to submit price updates

@dharjeezy dharjeezy requested a review from Wizdave97 March 19, 2026 10:52
@Wizdave97 Wizdave97 requested a review from seunlanlege March 19, 2026 11:09
Copy link
Copy Markdown
Member

@Wizdave97 Wizdave97 left a comment

Choose a reason for hiding this comment

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

Looks good.

@Wizdave97 Wizdave97 marked this pull request as ready for review March 19, 2026 11:14
…om:polytope-labs/hyperbridge into dami/filler-price-pair-update
@seunlanlege
Copy link
Copy Markdown
Member

We should support different prices for bids/asks. Also no reason to restrict pairs to governance

@dharjeezy dharjeezy requested a review from Wizdave97 March 20, 2026 12:51
@Wizdave97
Copy link
Copy Markdown
Member

We should support different prices for bids/asks. Also no reason to restrict pairs to governance

Yeah we do, the pairId for bids is keccak256("[stable]/[exotic]") while that for asks is keccak256("[excotic]/[stable]")

FIllers submit prices for both

@Wizdave97
Copy link
Copy Markdown
Member

We should support different prices for bids/asks. Also no reason to restrict pairs to governance

Would you rather people pay to add recognized pairs? We don't want spam because we need to clear the pair prices at intervals, we can't have it growing endlessly.

@dharjeezy dharjeezy requested a review from Wizdave97 March 20, 2026 15:52
Copy link
Copy Markdown
Member

@Wizdave97 Wizdave97 left a comment

Choose a reason for hiding this comment

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

Much Simpler @seunlanlege take another look.

* @returns Array of Quote objects, one per filler who has price entries for this pair
*/
async getQuotes(pairId: HexString, amount: number): Promise<Quote[]> {
const entries: RpcPriceEntry[] = await (this.api as any)._rpcCore.provider.send(
Copy link
Copy Markdown
Member

@Wizdave97 Wizdave97 Mar 24, 2026

Choose a reason for hiding this comment

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

We should filter out price entries that are older than 24 hours

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.

3 participants