> ## Documentation Index
> Fetch the complete documentation index at: https://docs.multiliquid.xyz/llms.txt
> Use this file to discover all available pages before exploring further.

# Price Sources

> NAV pricing sources and oracle integration for asset valuation

## Overview

Price sources (NavData) provide USD-denominated pricing for all assets in the Multiliquid Program. Each asset can have 1-5 pricing sources, which are aggregated and validated during swap execution.

## NAV Data Types

The program supports three types of NAV pricing sources:

### U64FixedAddress

Read price from a fixed byte offset in an on-chain account.

```rust theme={null}
NavData::U64FixedAddress {
    nav_account_address: Pubkey,  // Account containing price data
    nav_price_offset: u16,        // Byte offset to read price (as u64)
    price_decimals: u8,           // Decimal places (0-9)
}
```

**How It Works**:

1. Read account data at `nav_account_address`
2. Read u64 at byte offset `nav_price_offset`
3. Interpret value with `price_decimals` decimal places

**Use Cases**:

* Custom price oracle accounts
* RWA issuer-published NAV accounts
* Any account storing price as u64 at known offset

**Example Configuration**:

```typescript theme={null}
// Read price from offset 0 in a custom oracle account
const navData = {
  u64FixedAddress: {
    navAccountAddress: oracleAccount,
    navPriceOffset: 0,      // Read from start of account data
    priceDecimals: 6,       // Price has 6 decimal places
  }
};

// Example: Price of 1050000 with 6 decimals = $1.05
```

***

### Hardcoded

Static price value for stable-value assets.

```rust theme={null}
NavData::Hardcoded {
    hardcoded_price: u64,   // Fixed price value
    price_decimals: u8,     // Decimal places (0-9)
}
```

**How It Works**:

1. Return `hardcoded_price` directly
2. Interpret with `price_decimals` decimal places

**Use Cases**:

* Dollar-pegged stablecoins (USDC, USDT)
* Assets with contractually fixed prices
* Testing and development environments

**Example Configuration**:

```typescript theme={null}
// Stablecoin pegged to $1.00
const navData = {
  hardcoded: {
    hardcodedPrice: new BN(1_000000),  // 1.000000
    priceDecimals: 6,
  }
};

// Example: Price of 1000000 with 6 decimals = $1.00
```

<Note>
  Hardcoded prices are ideal for dollar-pegged stablecoins that maintain a 1:1 USD value. They have zero external dependencies and minimal gas costs.
</Note>

***

### PythPush

Pyth Network oracle integration for market prices.

```rust theme={null}
NavData::PythPush {
    pyth_push_account_address: Pubkey,  // Pyth receiver price update account
    feed_id: [u8; 32],                  // Expected Pyth feed id
    max_age_secs: u64,                  // Maximum accepted price age
    max_conf_bps: u16,                  // Maximum accepted confidence ratio
}
```

**How It Works**:

1. Deserialize the configured Pyth receiver `PriceUpdateV2` account
2. Verify the account's feed id matches `feed_id`
3. Require the update to be no older than `max_age_secs`
4. Require confidence to be within `max_conf_bps`
5. Normalize the Pyth price and exponent into the program's 9-decimal NAV format

**Use Cases**:

* Market-priced assets with Pyth feeds
* Real-time price updates
* Cross-chain price consistency

**Example Configuration**:

```typescript theme={null}
// Pyth oracle for an RWA with market price
const navData = {
  pythPush: {
    pythPushAccountAddress: pythPriceAccount,
    feedId: Array.from(Buffer.from("40ac3329933a6b5b65cf31496018c5764ac0567316146f7d0de00095886b480d", "hex")),
    maxAgeSecs: new BN(86_400),
    maxConfBps: 100,
  }
};
```

<Warning>
  Pyth oracle integration requires the correct receiver account and feed id. Stale updates, excessive confidence ratios, or feed-id mismatches will reject the source.
</Warning>

***

## Price Aggregation

When multiple NAV sources are configured for an asset, the program performs price aggregation:

### Aggregation Process

```
┌─────────────────────────────────────────────────────────┐
│                  Price Aggregation                       │
├─────────────────────────────────────────────────────────┤
│  1. Read all NAV sources                                │
│     Source 1: $1.0500 (6 decimals)                     │
│     Source 2: $1.0510 (8 decimals)                     │
│     Source 3: $1.0495 (6 decimals)                     │
│                                                         │
│  2. Normalize to 9 decimals                            │
│     Source 1: 1_050_000_000                            │
│     Source 2: 1_051_000_000                            │
│     Source 3: 1_049_500_000                            │
│                                                         │
│  3. Validate divergence (e.g., 100 BPS = 1%)          │
│     Max: 1_051_000_000                                 │
│     Min: 1_049_500_000                                 │
│     Diff: 0.14% ✓ (within threshold)                   │
│                                                         │
│  4. Calculate average                                   │
│     Average: 1_050_166_666 (9 decimals)               │
│     = $1.050166666                                     │
└─────────────────────────────────────────────────────────┘
```

### Divergence Validation

The `price_difference_bps` setting controls maximum allowed price divergence:

```rust theme={null}
// In AssetConfig
pub price_difference_bps: u16,  // Maximum divergence in basis points
```

**Calculation**:

```
divergence_bps = (max_price - min_price) / max_price × 10000
```

**Behavior**:

* If `divergence_bps > price_difference_bps`: Return 0 (block swaps)
* If within threshold: Return average price

**Example**:

```
price_difference_bps = 100 (1%)

Source 1: $1.05
Source 2: $1.06
Divergence: 0.95% ✓ Allowed

Source 1: $1.05
Source 2: $1.10
Divergence: 4.55% ✗ Blocked (returns 0)
```

***

## Decimal Normalization

All prices are normalized to 9 decimal places internally:

| Source Decimals | Raw Value    | Normalized (9 decimals) | USD Value |
| --------------- | ------------ | ----------------------- | --------- |
| 6               | 1\_050000    | 1\_050\_000\_000        | \$1.05    |
| 8               | 105\_000000  | 1\_050\_000\_000        | \$1.05    |
| 9               | 1\_050000000 | 1\_050\_000\_000        | \$1.05    |

**Normalization Formula**:

```
normalized = raw_value × 10^(9 - source_decimals)
```

***

## Configuration Examples

### Single Source: Dollar-Pegged Stablecoin

```typescript theme={null}
// USDC with fixed $1.00 price
const usdcNavData = [{
  hardcoded: {
    hardcodedPrice: new BN(1_000000),
    priceDecimals: 6,
  }
}];

await program.methods
  .initAssetConfigAccount(
    usdcNavData,
    0,  // No divergence check needed (single source)
    { stable: {} }
  )
  .accounts({ /* ... */ })
  .rpc();
```

### Single Source: RWA with Custom Oracle

```typescript theme={null}
// RWA with price published to custom account
const rwaNavData = [{
  u64FixedAddress: {
    navAccountAddress: issuerOracleAccount,
    navPriceOffset: 0,
    priceDecimals: 6,
  }
}];

await program.methods
  .initAssetConfigAccount(
    rwaNavData,
    0,  // Single source
    { rwa: {} }
  )
  .accounts({ /* ... */ })
  .rpc();
```

### Multiple Sources: RWA with Redundancy

```typescript theme={null}
// RWA with primary oracle + Pyth backup
const rwaNavData = [
  {
    u64FixedAddress: {
      navAccountAddress: primaryOracle,
      navPriceOffset: 0,
      priceDecimals: 6,
    }
  },
  {
    pythPush: {
      pythPushAccountAddress: pythAccount,
      feedId: Array.from(Buffer.from("40ac3329933a6b5b65cf31496018c5764ac0567316146f7d0de00095886b480d", "hex")),
      maxAgeSecs: new BN(86_400),
      maxConfBps: 100,
    }
  }
];

await program.methods
  .initAssetConfigAccount(
    rwaNavData,
    100,  // Allow 1% divergence between sources
    { rwa: {} }
  )
  .accounts({ /* ... */ })
  .rpc();
```

***

## Reading NAV Accounts in Swaps

When executing swaps, NAV source accounts must be passed as remaining accounts:

```typescript theme={null}
// Collect all NAV source accounts for both assets
const navAccounts = [
  // RWA NAV sources
  { pubkey: rwaOracleAccount, isWritable: false, isSigner: false },
  // Stablecoin NAV sources (if not hardcoded)
  // ... additional accounts as needed
];

await program.methods
  .swap(amount, minOut, null, { stableToAsset: {} }, { exactIn: {} })
  .accounts({ /* ... */ })
  .remainingAccounts(navAccounts)
  .rpc();
```

<Note>
  Hardcoded NAV sources don't require external accounts. Only `U64FixedAddress` and `PythPush` sources need their accounts passed.
</Note>

***

## Best Practices

### Choosing NAV Sources

| Asset Type               | Recommended Source | Rationale                          |
| ------------------------ | ------------------ | ---------------------------------- |
| Dollar-pegged stablecoin | Hardcoded          | No oracle dependency, fixed \$1.00 |
| NAV-accruing RWA         | U64FixedAddress    | Issuer publishes official NAV      |
| Market-priced asset      | PythPush           | Real-time market data              |
| Critical assets          | Multiple sources   | Redundancy and validation          |

### Divergence Thresholds

| Scenario                    | Recommended BPS | Notes                      |
| --------------------------- | --------------- | -------------------------- |
| Single source               | 0               | No divergence possible     |
| Similar sources (same feed) | 10-50           | Account for timing         |
| Different feeds             | 100-200         | Allow for feed differences |
| Volatile assets             | 200-500         | Wider tolerance needed     |

### Monitoring

* Track NAV source health and availability
* Alert on divergence events (swaps blocked)
* Monitor Pyth oracle staleness
* Verify issuer oracle updates regularly

***

## Error Handling

| Error                                   | Cause                                                  | Solution                                                        |
| --------------------------------------- | ------------------------------------------------------ | --------------------------------------------------------------- |
| `InvalidFeedId`                         | Pyth feed id is zeroed                                 | Provide the expected 32-byte feed id                            |
| `InvalidMaxAge` / `MaxAgeTooLarge`      | Pyth max age is invalid                                | Use a value from 1 second through 24 hours                      |
| `ConfBpsTooLarge`                       | Pyth max confidence setting exceeds the ceiling        | Use 1000 bps or lower                                           |
| `MissingPythAccount` / `FeedIdMismatch` | Pyth receiver account is missing or for the wrong feed | Pass the configured Pyth account in remaining accounts          |
| `ConfidenceTooLow`                      | Pyth confidence ratio exceeds `max_conf_bps`           | Investigate oracle health or widen the configured threshold     |
| Price returns 0                         | Divergence exceeded                                    | Check `price_difference_bps`                                    |
| `InvalidAccountPublicKey`               | Required NAV source account was not passed             | Include each non-hardcoded source account in remaining accounts |

***

<Card title="Next: Architecture Overview" icon="building" href="/svm/overview/architecture">
  Explore the program's modular design and account structure
</Card>
