Oracle & Price Feed Integration
Fetch real-time asset prices and update on-chain oracle data using Pyth Network integration.
Overview
Scallop uses Pyth Network as its oracle for asset pricing. The SDK provides:
- Price Queries: Get current USD prices for any supported coin
- Transaction Price Updates: Automatically refresh on-chain prices before price-sensitive operations (borrow, liquidate, withdraw collateral)
- Direct Oracle Access: Full
OracleClientfor advanced price feed operations
Price updates are required before any operation that checks collateral ratios. The *_quick methods handle this automatically, but manual control is available for complex transactions.
Quick Start: Get a Coin Price
from sui_scallop_sdk import ScallopClient
client = ScallopClient(secret_key="...", network="mainnet")
query = client.create_query()
# Get current USD price (returns Decimal for precision)
sui_price = query.get_coin_price("sui")
if sui_price:
print(f"SUI: ${sui_price:.4f}")
OracleClient
For direct access to Pyth price feeds, use OracleClient:
from sui_scallop_sdk import OracleClient
# Create oracle client from ScallopClient internals
oracle = OracleClient(client.rpc_client, client.addresses)
try:
# Get USD price as float
price = oracle.get_price_usd("sui")
print(f"SUI: ${price:.4f}")
# Get detailed price data for multiple coins
prices = oracle.get_latest_prices(["sui", "usdc", "eth"])
for coin_name, data in prices.items():
usd = float(data.price) * (10 ** data.expo)
print(f"{coin_name.upper()}: ${usd:.4f} (conf: {data.conf})")
finally:
oracle.close()
OracleClient Methods
| Method | Returns | Purpose |
|---|---|---|
get_price_usd(coin_name) |
float | None |
Quick USD price for one coin |
get_latest_prices(coin_names) |
dict[str, PriceData] |
Detailed price data for multiple coins |
get_price_feed_id(coin_name) |
str | None |
Pyth feed ID for a coin |
get_feed_object_id(feed_id) |
str | None |
On-chain PriceInfoObject ID |
fetch_price_update_data(coin_names) |
dict[str, bytes] |
Raw VAA bytes from Hermes API |
fetch_accumulator_update(coin_names) |
tuple[bytes, bytes] | None |
Wormhole VAA + accumulator bytes |
extract_vaa_from_accumulator(accumulator_bytes) |
bytes |
Static method: extract VAA from accumulator response |
close() |
None |
Release HTTP connections |
PriceData Fields
from sui_scallop_sdk import PriceData
# PriceData is a dataclass with these fields:
# price_feed_id: str - Pyth feed ID (0x-prefixed hex)
# price: int - Fixed-point price value
# conf: int - Confidence interval
# expo: int - Exponent (e.g., -8 means divide by 10^8)
# publish_time: int - Unix timestamp of price publication
# ema_price: int - Exponential moving average price
# ema_conf: int - EMA confidence interval
# Convert to human-readable USD:
usd_price = float(data.price) * (10 ** data.expo)
Updating Prices in Transactions
Automatic (via Quick Methods)
The *_quick methods call update_asset_prices_quick() internally:
tx = builder.create_tx_block()
# Oracle update happens automatically inside borrow_quick
borrowed = tx.borrow_quick(
amount=100_000_000,
coin_name="usdc",
obligation_id=obligation_id,
obligation_key=obligation_key,
)
tx.transfer_objects([borrowed], wallet_address)
result = builder.sign_and_send_tx_block(tx)
Methods that auto-update prices: borrow_quick(), liquidate_quick(), withdraw_collateral_quick().
Manual Price Update
For multi-asset operations or fine-grained control:
tx = builder.create_tx_block()
# Explicitly update prices for all assets involved
tx.update_asset_prices_quick(["sui", "usdc", "weth"])
# Now perform operations that need fresh prices
tx.borrow(obligation_id, obligation_key, 100_000_000, "usdc")
withdrawn = tx.withdraw_collateral(obligation_id, obligation_key, 5_000_000_000, "sui")
tx.transfer_objects([withdrawn], wallet_address)
result = builder.sign_and_send_tx_block(tx)
This is useful when you want to batch multiple price-sensitive operations in a single transaction, since calling update_asset_prices_quick once for all coins is more gas-efficient than letting each quick method update individually.
Three-Phase Oracle Architecture
Under the hood, update_asset_prices_quick and add_oracle_price_update_to_tx execute a three-phase process:
Phase A: Parse Wormhole VAA
Hermes API → accumulator bytes + VAA
→ wormhole::vaa::parse_and_verify()
→ pyth::create_authenticated_price_infos_using_accumulator()
→ Returns HotPotatoVector<PriceInfo>
Phase B: Update Each Price Feed
For each unique feed:
→ Split 1 MIST fee from gas
→ pyth::update_single_price_feed(hot_potato, feed_object, fee)
→ Chain hot_potato through each call
→ Destroy hot_potato after all feeds updated
Phase C: X-Oracle Price Confirmation
For each coin:
→ request = x_oracle::price_update_request<CoinType>(x_oracle)
→ pyth_rule::rule::set_price_as_primary<CoinType>(&mut request, pyth_state,
price_info_object, pyth_registry, clock)
→ x_oracle::confirm_price_update_request<CoinType>(x_oracle, request, clock)
Advanced: Direct Oracle Transaction Building
from sui_scallop_sdk.oracle import OracleClient, add_oracle_price_update_to_tx
oracle = OracleClient(client.rpc_client, client.addresses)
try:
tx = builder.create_tx_block()
# Add oracle commands directly to transaction
add_oracle_price_update_to_tx(tx, ["sui", "usdc"], oracle)
# Continue building transaction...
tx.borrow(obligation_id, obligation_key, amount, "usdc")
result = builder.sign_and_send_tx_block(tx)
finally:
oracle.close()
Pyth Price Feed Mappings
The SDK maps coin names to Pyth feed IDs. 18 distinct feeds cover the 34 lending assets, since assets tracking the same underlying price share one:
| Coin Group | Pyth Feed |
|---|---|
sui, afsui, hasui, vsui, scasui |
SUI/USD feed |
wbtc, sbwbtc, xbtc, zwbtc |
BTC/USD feed |
usdc, wusdc, suiusde, usdsui |
USDC/USD feed |
wal, wwal, hawal |
WAL/USD feed |
weth, sbeth |
ETH/USD feed |
wusdt, sbusdt |
USDT/USD feed |
fud and blub have no Pyth feed — handle the missing case rather than assuming every coin
resolves.
Full per-asset table with feed IDs and feed objects:
price-feeds.md. At runtime, use
sui_scallop_sdk.oracle.PYTH_PRICE_FEEDS and PYTH_FEED_OBJECTS.
Obligation Price Queries
Get obligations with fresh USD valuations:
query = client.create_query()
# Without prices (raw on-chain amounts only)
obligation = query.get_obligation(obligation_id)
# With fresh oracle prices (includes USD values + accurate risk level)
obligation = query.get_obligation_with_prices(obligation_id)
print(f"Total Debt: ${obligation.total_debt_usd:.2f}")
print(f"Total Collateral: ${obligation.total_collateral_usd:.2f}")
print(f"Risk Level: {obligation.risk_level:.4f}")
print(f"Liquidatable: {obligation.is_liquidatable}")
Error Handling
from sui_scallop_sdk.exceptions import OraclePriceError, ScallopError
try:
price = oracle.get_price_usd("unknown_coin")
except OraclePriceError as e:
print(f"Price error for {e.coin_name}: {e}")
except ScallopError as e:
print(f"General error: {e}")
| Error | Cause | Solution |
|---|---|---|
OraclePriceError |
Feed unavailable or fetch failed | Check coin name, verify network connectivity |
OraclePriceStale |
On-chain price outdated | Use update_asset_prices_quick() or quick methods |
Returns None |
Coin not in PYTH_PRICE_FEEDS |
Check supported coins list |
Required Protocol Addresses
The oracle module uses these addresses from ScallopAddresses:
| Field | Purpose |
|---|---|
wormhole_package |
Wormhole VAA parsing |
wormhole_state |
Wormhole state object |
pyth_package |
Pyth native package |
pyth_state |
Pyth state object |
pyth_registry |
Pyth price registry |
x_oracle_package |
X Oracle package |
x_oracle |
X Oracle object |
pyth_oracle_package |
Scallop's Pyth adapter |
These are pre-configured for mainnet and testnet — no manual setup needed.
Multi-Source Oracle (xOracle: Pyth + Supra + Switchboard)
Scallop's on-chain oracle layer (sui-x-oracle) aggregates Pyth as the primary feed and Switchboard as a secondary feed. Both SDKs expose the inspector queries — TS via ScallopQuery (below), Python via query.get_asset_oracles() and query.get_price_update_policies() (sui-scallop-sdk >= 0.3.0a1). What is genuinely TS-only is building non-Pyth price-update transactions: the Python SDK's transaction path covers Pyth only.
SDK note:
@scallop-io/sui-scallop-sdkv4.3.0 exports the package root plus./client,./query,./builder,./types,./errors, and./loggersubpaths. Go throughscallop.client.query.*rather than importing internal paths — those are not part of the public API and break on package consumers.
Inspect Per-Asset Oracle Rules
// TypeScript - which oracle sources are registered for each coin
const scallopSDK = new Scallop({ networkType: 'mainnet' });
await scallopSDK.init();
const query = scallopSDK.client.query;
// Returns Record<coinName, { primary: SupportOracleType[], secondary: SupportOracleType[] }>
const oracles = await query.getAssetOracles();
// oracles.sui => { primary: ['pyth'], secondary: ['switchboard'] }
Inspect Price-Update Policy
// Returns { primary, secondary } — two SuiObjectResponse handles for the policy tables
const policies = await query.getPriceUpdatePolicies();
Switchboard On-Demand Aggregators
// Takes string[], returns string[] (aggregator object IDs in the same order)
const aggIds = await query.getSwitchboardOnDemandAggregatorObjectIds(['sui', 'usdc']);
// aggIds[0] => Switchboard aggregator object ID for SUI/USD
Architecture (sui-x-oracle)
sui-x-oracle/
├── x_oracle/ core: price_feed, x_oracle, price_update_policy
├── pyth_rule/ Pyth adapter + registry
└── switchboard_rule/ Switchboard adapter + registry
Each on-chain price update goes through x_oracle::price_update_request<CoinType> → one or more rule modules (Pyth always, Supra/Switchboard when configured) → confirm_price_update_request<CoinType>. The Pyth-only update_asset_prices_quick() path documented above is sufficient for every coin currently routed through pyth_rule.
For a dedicated walkthrough of the multi-source flow, see the xoracle-integration skill.
References
- Price Feed IDs - Complete Pyth feed ID mappings
- Oracle Integration - Protocol-level oracle docs
- Supported Coins - All supported assets
- xoracle-integration Skill - Multi-source oracle handling