Skip to content

RAAC NFT Vault Adapter V2

Overview

RAACNFTVaultAdapterV2 is an ERC‑721 vault adapter optimized for scalable NAV accounting. Instead of recomputing NFT portfolio value via loops, it maintains a running sum of all deposited NFTs’ prices in USD and syncs in real time with the on‑chain house price oracle.

  • Real‑time NAV tracking via running sum in USD
  • Syncs on each price update through the House Prices “syncer” interface
  • Converts USD → crvUSD on demand when the vault queries totalValue()
  • Prevents deposits/withdrawals while a price update for a specific NFT is pending

Why a running sum?

Iterating over all deposited NFTs to compute NAV is not scalable. By keeping nftsValue as a running USD total and updating it on deposit/withdraw and on oracle price updates, the adapter supports O(1) NAV reads.


Architecture

flowchart TD
    H[RAACHousePrices] -->|notify tokenId, old, new| A[RAACNFTVaultAdapterV2]
    A -->|nftsValue += Δ| S[nftsValue USD]
    A -->|totalValue| V[RWAVault]
    H -->|crvUSDToUSDPrice| A
  • nftsValue stores the running USD sum for deposited NFTs
  • notify adjusts nftsValue when an NFT price changes and the NFT is currently deposited
  • totalValue converts USD → crvUSD using crvUSDToUSDPrice() from RAACHousePrices

Mechanics

How it stays in sync (at a glance)

sequenceDiagram
  participant User
  participant Vault
  participant Adapter as RAACNFTVaultAdapterV2
  participant Prices as RAACHousePrices

  User->>Vault: deposit NFT
  Vault->>Adapter: deposit(data, from)
  Adapter->>Prices: isPendingRequest(tokenId)?
  Prices-->>Adapter: false
  Adapter->>Adapter: nftsValue += usdPrice(tokenId)
  Adapter-->>Vault: value (crvUSD)

  Prices-->>Adapter: notify(tokenId, oldUSD, newUSD)
  Adapter->>Adapter: nftsValue = nftsValue - oldUSD + newUSD

  Vault->>Adapter: totalValue()
  Adapter->>Prices: crvUSDToUSDPrice()
  Adapter-->>Vault: nftsValue (USD) -> crvUSD

Trigger → Effect reference

Trigger Guard/Check State Update Notes
deposit(data, from) Reject if isPendingRequest(tokenId) nftsValue += usdPrice(tokenId) Base ERC721 deposit, value added in USD
withdraw(data, to) Reject if isPendingRequest(tokenId) nftsValue = max(0, nftsValue - usdPrice(tokenId)) Base ERC721 withdraw, value removed in USD
notify(tokenId, old, new) Only from RAACHousePrices; ignore if NFT not deposited nftsValue = nftsValue - old + new Keeps running sum aligned with oracle
totalValue() Returns (nftsValue * 1e18) / crvUSDToUSDPrice() Converts USD running sum to crvUSD on read

Details

  • Deposits/withdrawals are blocked for a specific NFT while its price update is pending to avoid inconsistent NAV snapshots.
  • The USD running sum (nftsValue) removes the need to iterate over all NFTs, enabling O(1) NAV reads.
  • Conversion to crvUSD happens on demand using the FX rate surfaced by RAACHousePrices.

Key Functions

deposit

Item Details
Purpose Deposit an ERC‑721 into the vault via adapter and update NAV running sum (USD)
Guards Reverts if isPendingRequest(tokenId) is true
State nftsValue += usdPrice(tokenId); base ERC721 deposit logic executes
Returns value – deposit value in crvUSD as per base adapter
Source code
function deposit(bytes calldata data, address from)
  public
  virtual
  override
  onlyVault
  returns (uint256 value)
{
    uint256 tokenId = abi.decode(data, (uint256));
    if (IRAACHousePrices(address(priceOracle)).isPendingRequest(tokenId)) revert HousePricePending();

    value = super.deposit(data, from);
    (uint256 usdPrice,) = IRAACHousePrices(address(priceOracle)).getRawPrice(tokenId);
    nftsValue += usdPrice;
    return value;
}

withdraw

Item Details
Purpose Withdraw an ERC‑721 from the vault and decrement NAV running sum (USD)
Guards Reverts if isPendingRequest(tokenId) is true
State nftsValue = max(0, nftsValue - usdPrice(tokenId)); base ERC721 withdraw executes
Returns value – withdrawal value in crvUSD as per base adapter
Source code
function withdraw(bytes calldata data, address to)
  public
  virtual
  override
  onlyVault
  returns (uint256 value)
{
    uint256 tokenId = abi.decode(data, (uint256));
    if (IRAACHousePrices(address(priceOracle)).isPendingRequest(tokenId)) revert HousePricePending();

    (uint256 usdValue,) = IRAACHousePrices(address(priceOracle)).getRawPrice(tokenId);
    if (nftsValue < usdValue) {
        nftsValue = 0;
    } else {
        nftsValue -= usdValue;
    }
    return super.withdraw(data, to);
}

notify (Price Syncer)

Item Details
Purpose Keep NAV running sum aligned with latest house prices
Caller RAACHousePrices only
State If NFT is deposited: nftsValue = nftsValue - previousPrice + newPrice
Source code
function notify(uint256 tokenId, uint256 previousPrice, uint256 newPrice) public {
    if (msg.sender != address(priceOracle)) revert NotHousePrices();
    if (!isDeposited(tokenId)) return;

    nftsValue = nftsValue - previousPrice + newPrice;
    emit PriceSynced(tokenId, previousPrice, newPrice);
}

totalValue

Item Details
Purpose Report adapter NAV in crvUSD for the vault
Calc (nftsValue (USD) * 1e18) / crvUSDToUSDPrice()
Notes nftsValue kept in USD; FX conversion is on-demand
Source code
function totalValue() public view override returns (uint256) {
    uint256 crvUSDToUSDPrice = IRAACHousePrices(address(priceOracle)).crvUSDToUSDPrice();
    return (nftsValue * 1e18) / crvUSDToUSDPrice;
}

Interfaces & Compatibility

  • Inherits ERC721VaultAdapter
  • Implements IRAACHousePriceSyncer and supports ERC165
  • Works with RWAVault via standard adapter interface (deposit, withdraw, totalValue)

Operational Notes

  • Pending price updates block deposit/withdraw for the affected NFT to avoid NAV inconsistencies
  • nftsValue is kept in USD for precision and portability; conversion to crvUSD happens at read time
  • As a registered syncer, this adapter should be added via RAACHousePrices.addSyncer(adapter) so it receives notify callbacks

Syncer registration

Ensure the adapter is registered as a syncer on RAACHousePrices. Without notify callbacks, nftsValue will not reflect live price changes.