Prices and the oracle
Outcome: your app reads current prices safely, shows stale or missing prices honestly, and sends Dynamic LOC transactions with a price update the contract accepts.
You need: a configured SDK and Hermes access, through your own Pyth API key or, for select partners, the Anvil Hermes proxy.
Anvil’s price oracle turns Pyth price feeds into an exchange rate between the collateral and credited tokens. It accounts for each token’s decimals, and it rejects unsupported feeds, prices at or below zero, and prices whose reported confidence interval is as large as the price itself or larger.
Price reads need Hermes access
The SDK fetches current prices from Pyth’s Hermes service, which rejects unauthenticated requests. Pick the access that fits your setup:
- Your own Pyth API key, on a server. Pass the key as
oracleConfig.hermesAccessToken. The SDK sends it as anAuthorization: Bearerheader on every Hermes request, to the default endpointhttps://hermes.pyth.networkunless you set another. - Your own Pyth API key, in a browser. Keep the key on your backend. Run a small proxy there
that forwards Hermes requests with the key attached, point
oracleConfig.hermesEndpointat the proxy’s absolute URL, and leavehermesAccessTokenunset. - The Anvil Hermes proxy. Select partners without a Pyth API key can request access to Anvil’s
Hermes proxy: reach out to the team through the contact form.
Point
oracleConfig.hermesEndpointat the URL the team gives you.
Reads that need no current price, such as sdk.loc.getLetterOfCredit, work without Hermes access.
Oracle pricing
Price-sensitive LOC operations take one of two price paths:
- use the price already stored in Pyth, when it’s recent enough; or
- submit a Pyth update with the transaction.
For buildConvertWorkflow and a single Dynamic LOC redemption, oraclePriceUpdate picks the path:
- Leave it out and the SDK fetches an update for you. If that service is unavailable, the SDK uses the onchain price, but only after confirming it’s still fresh.
- Pass
nullto skip the fetch and use the onchain price. - Pass an update object to submit that update, forwarded as it is. The object needs update data:
an object with empty (
'0x') data is invalid, andnullis how you ask for the onchain price.
Three freshness settings apply at different points:
oracleConfig.maxPriceAgeSecondsguards SDK reads. It compares each Hermes price’s publish time with the local clock, in either direction, and defaults to 300 seconds. Set it to positive infinity only when your app deliberately accepts Hermes data without this guard; the contracts accept what they accept either way.oracleConfig.updateBufferSecondsis a safety margin for transactions. When the SDK checks whether an update will stay usable through submission and confirmation, it subtracts this margin from the deployment’s onchain window. Hermes reads ignore it.- The LetterOfCredit deployment sets the onchain maximum price age. That limit has the final say for
a transaction, can differ between deployments, and can change through Anvil Governance. Read the
effective value, buffer included, with
sdk.pricing.getMaxPriceUpdateSecondsAgo()rather than hardcoding it.
When Hermes returns a price outside the SDK’s read window, pricing rejects with
StalePythPriceError. The service answered, but with data your configured policy treats as unsafe.
That’s a different error from PriceServerError, and sdk.pricing.getPrice() reports it as is,
without substituting the onchain price. Catch the typed error when your UI can ask the user to
retry:
const sdk = new AnvilSDK({ publicClient, profile: testnetProfile, oracleConfig: { maxPriceAgeSeconds: 300, updateBufferSeconds: 30, },});
try { await sdk.pricing.getUsdPrice({ token: '0x1111111111111111111111111111111111111111', });} catch (error) { if (error instanceof StalePythPriceError) { console.warn(`Stale Hermes feed: ${error.feedId}`); return; } throw error;}Price several assets for display
For screens that show several prices together, use sdk.pricing.getUsdPrices({ tokens }). Each
entry is a token address, or a direct { feedId, decimals, key? } feed entry for a value with no
ERC-20 address to find a feed from, such as native ETH. The
PricingModule reference shows both forms.
The SDK resolves the feed ids, combines duplicate feeds into one Hermes latest-price request, and
returns one outcome per entry in the order you supplied, duplicates included. Each outcome has a
kind ('address' or 'feed') and a status. For an address entry whose feed isn’t registered
onchain, feed discovery can fall back to a Hermes search per token; a feed entry skips that lookup.
freshandstalecarry aPriceand its publish time.stalemeans the publish time falls outsideoracleConfig.maxPriceAgeSeconds, so label it stale or treat it as unavailable.unavailablecarries a typedreason:unsupported-feed(no single feed resolves for the token; retrying won’t help),transport-failure(the token or feed lookup failed; retry), orinvalid-data(Hermes returned an entry the SDK can’t use).
Each entry stands alone, so one entry’s problem leaves its neighbors’ results intact. The call
rejects in two cases only: invalid arguments (tokens must be an array, and a feed entry’s
decimals an integer), before any read; and a failure of the Hermes request itself, with a
PriceServerError that keeps the underlying cause, so a query cache keeps the last good prices
through a brief outage. useTokenUsdValues turns only fresh outcomes into values. Transaction
updates stay strict: one unusable feed fails the whole update.
Pay the update fee
Pyth charges a fee to publish an update onchain, so a write that includes fresh update data also sends the quoted oracle fee as transaction value. The SDK fetches the update data and the fee together, and the transaction forwards both.
Where data comes from covers how these computed values relate to what the chain, the subgraph, and the SDK each report.
What the SDK handles, and what your app handles
- The SDK handles the oracle. It fetches and checks Hermes prices, applies your freshness settings, picks the price path for a transaction, and attaches the update data and fee.
- Your app owns Hermes access and the display. It keeps its own Pyth API key on a server or points the SDK at the Anvil Hermes proxy, chooses the freshness policy, and decides how to show a stale or unavailable price.