BM-3017: Add market-price buffer to requestor auto-pricing#2018
Open
Wollac wants to merge 4 commits into
Open
Conversation
…pricing When a request is auto-priced without an explicit max_price, the gas portion of maxPrice already carries heavy headroom (2x the gas estimate), but the proof-cycle portion (min(p99, 2*p50) * cycles) had no cushion against the per-cycle market drifting upward between request submission and prover lock-in. If the market moves up, the request can sit unlocked until it expires. Add a market_price_buffer_multiplier_percentage knob (default 115 = +15%) applied only to the market-derived proof price. Explicit max_price, the configured max_price_per_cycle ceiling, the static default, and the gas portion are all left untouched. Configurable client-wide via OfferLayerConfig and per-request via OfferParams, which also exposes a --market-price-buffer-multiplier-percentage CLI flag.
willpote
reviewed
May 27, 2026
Comment on lines
+479
to
+480
| // Reconstructing an existing on-chain offer must not re-apply the buffer. | ||
| market_price_buffer_multiplier_percentage: None, |
Contributor
There was a problem hiding this comment.
This shouldn't really live on the OfferParams as it is intended to map cleanly to Offer on the contract.
The convention we have for stuff like this is to put it in the Config struct, e.g. OfferLayerConfig
…fig only OfferParams is a partial mirror of the on-chain Offer, so a pricing-strategy knob doesn't belong there (PR review). Drop the per-request field and resolve the buffer from OfferLayerConfig alone, alongside max_price_per_cycle.
Follow-up to the market-price buffer feature: - Make `market_price_buffer_multiplier_percentage` a plain `u16` (default 115) instead of `Option<u64>`, dropping the unreachable `None` arm and the read-site `unwrap_or(100)` that disagreed with the 115 default. - Use `saturating_mul` in `buffered_market_max`, matching the crate-wide percentage-scaling convention (e.g. apply_secondary_fulfillment_discount). - Make the price-provider e2e assertion deterministic: zero the gas estimates on both builds so maxPrice is exactly the proof portion, isolating the buffer from gas-price noise that previously dominated and could flip the comparison. - Fix with_price_provider docs (uses p50/p99, not p10; note the always-on market-pricing fallback).
willpote
approved these changes
May 27, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
When a request is auto-priced (no explicit
max_price), the gas part ofmaxPricealready gets a 2x cushion, but the proof part —min(p99, 2·p50) × cyclesfrom the indexer — gets none. That's just a snapshot of recent fills, so if the per-cycle market drifts up between submitting and a prover actually locking, the request can sit there unlocked until it expires. Every comparable "set a max, let someone fill it" system keeps some headroom here (ethers does 2x on gas, 1inch Fusion ~2.3% on the swap); we had zero on the proof side.This adds a configurable buffer on the market-derived max price, default +15%.
Heads-up, this is the default path, not opt-in.
ClientBuilder::build()always attaches a price provider (deployment indexer, or a built-in market-pricing fallback), so any auto-priced request without an explicit price now authorizes ~15% more than before. That's the intent — it's what lets requests lock through price drift — but worth flagging for spend expectations on the CLI default and order-generators without a--max-priceflag.A few choices worth calling out:
--max-price(taken verbatim, like today), the configuredmax_price_per_cycle(that's a deliberate ceiling — pushing it past what you said you'd pay is wrong), or the static fallback (already ~p99). The market estimate is the only piece that actually drifts.OfferLayerConfig, next tomax_price_per_cycle. It's a pricing-strategy knob, not anOfferfield, so it stays offOfferParams.Gas buffering, min price, timeouts and collateral are untouched.
Tests: unit tests for the buffer math, and the price-provider e2e asserts the default buffer makes
maxPriceexactly 115/100 of the un-buffered price.