Skip to main content

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.

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. The program supports three types of NAV pricing sources:

U64FixedAddress

Read price from a fixed byte offset in an on-chain account.
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:
// 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.
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:
// 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
Hardcoded prices are ideal for dollar-pegged stablecoins that maintain a 1:1 USD value. They have zero external dependencies and minimal gas costs.

PythPush

Pyth Network oracle integration for market prices.
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:
// 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,
  }
};
Pyth oracle integration requires the correct receiver account and feed id. Stale updates, excessive confidence ratios, or feed-id mismatches will reject the source.

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:
// 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 DecimalsRaw ValueNormalized (9 decimals)USD Value
61_0500001_050_000_000$1.05
8105_0000001_050_000_000$1.05
91_0500000001_050_000_000$1.05
Normalization Formula:
normalized = raw_value × 10^(9 - source_decimals)

Configuration Examples

Single Source: Dollar-Pegged Stablecoin

// 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

// 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

// 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:
// 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();
Hardcoded NAV sources don’t require external accounts. Only U64FixedAddress and PythPush sources need their accounts passed.

Best Practices

Choosing NAV Sources

Asset TypeRecommended SourceRationale
Dollar-pegged stablecoinHardcodedNo oracle dependency, fixed $1.00
NAV-accruing RWAU64FixedAddressIssuer publishes official NAV
Market-priced assetPythPushReal-time market data
Critical assetsMultiple sourcesRedundancy and validation

Divergence Thresholds

ScenarioRecommended BPSNotes
Single source0No divergence possible
Similar sources (same feed)10-50Account for timing
Different feeds100-200Allow for feed differences
Volatile assets200-500Wider 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

ErrorCauseSolution
InvalidFeedIdPyth feed id is zeroedProvide the expected 32-byte feed id
InvalidMaxAge / MaxAgeTooLargePyth max age is invalidUse a value from 1 second through 24 hours
ConfBpsTooLargePyth max confidence setting exceeds the ceilingUse 1000 bps or lower
MissingPythAccount / FeedIdMismatchPyth receiver account is missing or for the wrong feedPass the configured Pyth account in remaining accounts
ConfidenceTooLowPyth confidence ratio exceeds max_conf_bpsInvestigate oracle health or widen the configured threshold
Price returns 0Divergence exceededCheck price_difference_bps
InvalidAccountPublicKeyRequired NAV source account was not passedInclude each non-hardcoded source account in remaining accounts

Next: Architecture Overview

Explore the program’s modular design and account structure