Skip to main content

Pricing

Every price-bearing call carries a signed Pyth Lazer payload that the price verifier validates and decodes into a PriceData value. The trading contract uses that value for entry, exit, mark, and liquidation.

Price Attached at Submission

Calls that require user authorization (open_market, close_position, modify_collateral) sign over every argument except the price blob (require_auth_for_args covers the rest). A backend submitting the transaction can attach a different, fresher Pyth payload between signing and inclusion. Two reasons this matters:

  • Slow signing flow. The cryptography itself is fast, but the user interaction wrapped around it (password entry, button presses on a hardware wallet, biometric confirmation for a passkey) takes seconds. The market keeps moving during that time, so locking the price at signing time would force stale fills or repeated re-signing.
  • Submission timing. A backend co-located with a fast RPC node lands transactions sooner than a user's browser ever could. Letting that backend refresh the price means the latency advantage shows up as a fresher fill for the user.

The verifier still checks signature, confidence, staleness, and feed-ID match on whatever payload the backend attaches, so the backend can only pick among valid signed prices, not invent one.

User-Signed Bounds

The backend's freedom to swap prices has limits. Without further protection, the user's signature on its own no longer pins the trade to a moment in time or to a price range. A backend holding the signed transaction could delay submission until the market has moved against the user, or attach the worst valid price within the staleness window. Soroban auth entries are nonce-protected and single-use, so replay is not possible, but the submission window granted by the auth entry's own signatureExpirationLedger still leaves room for this kind of timing griefing. Two bounds the user signs over close that gap:

BoundApplies toEffect
expiration_ledgeropen_market, close_positionReverts with Expired (760) once the current ledger exceeds the user's deadline
price_boundopen_market, close_positionReverts with PriceSlippage (712) when the fill price falls outside the user's direction-aware bound

price_bound is always oriented to protect the user against an unfavorable move:

CallDirectionBound typeReverts when
open_marketLongCeiling (upper)fill_price > price_bound
open_marketShortFloor (lower)fill_price < price_bound
close_positionLongFloor (lower)fill_price < price_bound
close_positionShortCeiling (upper)fill_price > price_bound

When the user is paying for size (open long, close short) the bound is a ceiling. When the user is receiving (open short, close long) the bound is a floor.

modify_collateral carries neither expiration_ledger nor price_bound. The only price-dependent check is the margin requirement on a withdrawal, which fails one-sidedly, so a slippage bound is not needed. The user also signs an absolute target collateral rather than a price-dependent fill, so a backend delaying submission within the Soroban auth window cannot degrade the outcome: the position lands at the target value regardless of when the transaction is included. The Soroban auth entry's own signatureExpirationLedger is the only deadline that applies.

Both bounds accept 0 to disable the check. The opt-out exists for internal callers (smart-account batches, deploy-time scaffolding) that have their own intent-binding mechanisms.