> ## 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 Adapters

> Modular oracle system providing USD-denominated pricing for all protocol assets

## Overview

Price adapters provide USD-denominated pricing for all assets in the Multiliquid Protocol. Each asset RWA has an associated price adapter that the MultiliquidSwap contract queries during swap calculations.

**Interface**: `src/interface/IPriceAdapter.sol`

## Price Adapter Interface

All price adapters implement a simple, standardized interface:

```solidity theme={null}
interface IPriceAdapter {
    /**
     * @notice Returns the USD price of the asset
     * @return price The price in 18-decimal WAD format (1e18 = $1 USD)
     */
    function getPrice() external view returns (uint256 price);
}
```

**Price Format**: 18-decimal WAD (1e18 = \$1.00 USD)

**Examples**:

* \$1.00 = `1000000000000000000` (1e18)
* \$1.05 = `1050000000000000000` (1.05e18)
* \$0.99 = `990000000000000000` (0.99e18)

## Adapter Types

### ULTRAAdapter

Fetches ULTRA (Delta Wellington Ultra Short Treasury On-Chain Fund) price from ULTRA's manager contract using the last set mint exchange rate.

**Location**: `src/PriceAdapters/ULTRAAdapter.sol`

**Implementation**:

```solidity theme={null}
interface IUltraManager {
    function lastSetMintExchangeRate() external view returns (uint256);
    function BPS_DENOMINATOR() external view returns (uint256);
}

contract ULTRAAdapter is IPriceAdapter {
    IUltraManager public ultraManager;

    error ZeroAddress();

    constructor(address _ultraManager) {
        if (_ultraManager == address(0)) revert ZeroAddress();
        ultraManager = IUltraManager(_ultraManager);
    }

    function getPrice() external view returns (uint256) {
        return ultraManager.lastSetMintExchangeRate() * 1e18
            / ultraManager.BPS_DENOMINATOR();
    }
}
```

**How It Works**:

* Reads the mint exchange rate from ULTRA's manager via `lastSetMintExchangeRate()`
* The exchange rate is denominated in basis points, so the adapter divides by `BPS_DENOMINATOR()` to normalize
* Multiplies by `1e18` first to produce a WAD-format USD price

**Dependencies**:

* ULTRA Manager contract must be deployed and accessible
* `lastSetMintExchangeRate()` must reflect the current exchange rate

***

### JTRSYAdapter

Fetches JTRSY (Janus Henderson Anemoy Treasury Fund) price from JTRSY's async vault contract.

**Location**: `src/PriceAdapters/JTRSYAdapter.sol`

**Implementation**:

```solidity theme={null}
interface IAsyncVault {
    function pricePerShare() external view returns (uint256);
}

contract JTRSYAdapter is IPriceAdapter {
    IAsyncVault public asyncVault;

    error ZeroAddress();

    constructor(address _asyncVault) {
        if (_asyncVault == address(0)) revert ZeroAddress();
        asyncVault = IAsyncVault(_asyncVault);
    }

    // JTRSY is a 6-decimal token, so scale to 18 decimals for WAD math
    function getPrice() external view returns (uint256) {
        return asyncVault.pricePerShare() * 1e12; // 1e12 = 1e(18-6)
    }
}
```

**How It Works**:

* Reads the share price from JTRSY's `IAsyncVault` via `pricePerShare()`
* JTRSY uses 6-decimal precision, so the adapter scales by `1e12` to produce an 18-decimal WAD price

**Dependencies**:

* JTRSY async vault contract must be deployed and accessible
* `pricePerShare()` must return the current share price in 6-decimal format

***

### USTBAdapter

Fetches USTB (Superstate Short Duration US Government Securities Fund) price from a Chainlink price feed.

**Location**: `src/PriceAdapters/USTBAdapter.sol`

**Implementation**:

```solidity theme={null}
contract USTBAdapter is IPriceAdapter {
    AggregatorV3Interface private immutable PRICE_FEED;

    error ZeroAddress();
    error InvalidPrice();

    constructor(address _priceFeed) {
        if (_priceFeed == address(0)) revert ZeroAddress();
        PRICE_FEED = AggregatorV3Interface(_priceFeed);
    }

    function getPrice() external view returns (uint256) {
        (, int256 answer,,,) = PRICE_FEED.latestRoundData();

        if (answer <= 0) revert InvalidPrice();

        return uint256(answer) * 1e12;
    }
}
```

**How It Works**:

* Reads the latest price from a Chainlink `AggregatorV3Interface` price feed via `latestRoundData()`
* Chainlink feeds for this asset return prices with 6 decimals, so the adapter scales by `1e12` to produce an 18-decimal WAD price
* Reverts with `InvalidPrice()` if the feed returns a non-positive value, guarding against stale or corrupted data

**Dependencies**:

* A Chainlink-compatible price feed must be deployed and accessible
* The price feed must return a positive `int256` answer in 6-decimal format

***

### DollarPeggedAdapter

For RWAs with a NAV pegged to \$1 USD (e.g., BENJI, WTGXX).

**Implementation**:

```solidity theme={null}
contract DollarPeggedAdapter is IPriceAdapter {
    constructor() {}

    function getPrice() external pure returns (uint256) {
        return 1e18; // Always $1.00
    }
}
```

**Use Cases**:

* BENJI (Franklin OnChain US Gov Money Fund)
* WTGXX (WisdomTree US Dollar Digital Fund)
* Other RWAs with \$1.00 target NAV

**Characteristics**:

* No external dependencies
* Zero gas cost (pure function)
* Deterministic pricing

***

### ThirdPartySetterPriceAdapter

A generic, role-controlled price adapter for assets whose price is set off-chain by an authorized party. Used when no on-chain oracle or direct contract query is available for an asset's NAV.

**Location**: `src/PriceAdapters/ThirdPartySetterPriceAdapter.sol`

**Implementation**:

```solidity theme={null}
contract ThirdPartySetterPriceAdapter is IPriceAdapter, AccessControlEnumerable {
    bytes32 public constant PRICE_SETTER_ROLE = keccak256("PRICE_SETTER_ROLE");

    uint256 public _price;

    error PriceNotSet();
    error AdminCannotBeZeroAddress();

    event PriceUpdated(uint256 newPrice, address indexed setter);

    constructor(address admin) {
        if (admin == address(0)) revert AdminCannotBeZeroAddress();
        _grantRole(DEFAULT_ADMIN_ROLE, admin);
    }

    function getPrice() external view returns (uint256) {
        if (_price == 0) revert PriceNotSet();
        return _price;
    }

    function setPrice(uint256 newPrice) external onlyRole(PRICE_SETTER_ROLE) {
        _price = newPrice;
        emit PriceUpdated(newPrice, msg.sender);
    }
}
```

**How It Works**:

* An admin grants `PRICE_SETTER_ROLE` to one or more authorized addresses
* Authorized setters call `setPrice()` with a WAD-format USD price (18 decimals)
* `getPrice()` returns the most recently set price, reverting with `PriceNotSet()` if no price has been configured
* Emits a `PriceUpdated` event on every price change for off-chain monitoring

**Access Control**:

* `DEFAULT_ADMIN_ROLE`: Manages role assignments (grant/revoke `PRICE_SETTER_ROLE`)
* `PRICE_SETTER_ROLE`: Authorized to call `setPrice()`

**Use Cases**:

* VBILL (VanEck Treasury Fund) -- see below
* Any future RWA whose pricing relies on off-chain NAV reporting

***

### VBILLAdapter

VBILL (VanEck Treasury Fund) uses the `ThirdPartySetterPriceAdapter` described above. There is no dedicated `VBILLAdapter` contract; instead, a `ThirdPartySetterPriceAdapter` instance is deployed and configured with an authorized price setter that periodically updates the VBILL NAV on-chain.

**Characteristics**:

* NAV accrues to token (not pegged to \$1.00)
* Price is updated off-chain by an authorized `PRICE_SETTER_ROLE` address
* Reverts if queried before the initial price is set

***

<Card title="Next: Integration Guide" icon="code" href="/evm/guides/integration">
  Learn how to integrate Multiliquid into your application
</Card>
