# Scallop Oracle

> Scallop oracle and price feed integration using Pyth Network. Use when user says "oracle", "price feed", "Pyth", "price update", "get price", "coin price", "USD price", "oracle update", "update prices", "Wormhole VAA", or asks about fetching asset prices, updating oracle prices in transactions, or integrating Pyth price feeds with Scallop on Sui.

- Skill: `scallop-io/scallop-oracle` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds@latest add scallop-io/scallop-oracle`
- Raw SKILL.md: https://api.skillmd.com/api/skills/scallop-io/scallop-oracle/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Integrations & APIs
- License: MIT
- Author: scallop-io (https://skillmd.com/u/scallop-io)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/scallop-io/scallop-oracle

---


# 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 `OracleClient` for 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

```python
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`:

```python
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

```python
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:

```python
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:

```python
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

```python
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](references/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:

```python
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

```python
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-sdk` v4.3.0 exports the package root plus `./client`, `./query`, `./builder`, `./types`, `./errors`, and `./logger` subpaths. Go through `scallop.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
// 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

```typescript
// Returns { primary, secondary } — two SuiObjectResponse handles for the policy tables
const policies = await query.getPriceUpdatePolicies();
```

### Switchboard On-Demand Aggregators

```typescript
// 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](../scallop-xoracle-integration/SKILL.md).

## References

- [Price Feed IDs](references/price-feeds.md) - Complete Pyth feed ID mappings
- [Oracle Integration](../../references/oracle-integration.md) - Protocol-level oracle docs
- [Supported Coins](../../references/supported-coins.md) - All supported assets
- [xoracle-integration Skill](../scallop-xoracle-integration/SKILL.md) - Multi-source oracle handling

