Skip to main content
This guide shows you how to use existing Molpha feeds in your smart contracts on EVM chains.

Overview

The IFeed interface provides access to oracle data feeds. To use a feed in your contract, you need to:
  1. Subscribe to the feed (if it’s not free)
  2. Read data from the feed using IFeed functions
  3. Decode the bytes data to your desired format

Prerequisites

Before integrating, you’ll need:
  • The feed contract address
  • The SubscriptionRegistry address (for paid feeds)
  • An understanding of the data format stored in the feed

Step 1: Subscribe to a Feed

For paid feeds, you must subscribe before your contract can read data. Subscription is done via the SubscriptionRegistry contract.

Subscribing from Your Contract

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.29;

import {ISubscriptionRegistry} from "./interfaces/ISubscriptionRegistry.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";

contract MyContract {
    ISubscriptionRegistry public subscriptionRegistry;
    IERC20 public usdc;
    address public feedAddress;
    
    constructor(
        address _subscriptionRegistry,
        address _usdc,
        address _feedAddress
    ) {
        subscriptionRegistry = ISubscriptionRegistry(_subscriptionRegistry);
        usdc = IERC20(_usdc);
        feedAddress = _feedAddress;
    }
    
    function subscribeToFeed(uint256 duration) external {
        // Calculate subscription end time
        uint256 dueTime = block.timestamp + duration;
        
        // Get price for subscription duration
        uint256 pricePerSecond = subscriptionRegistry.getConsumerPricePerSecondScaled(feedAddress);
        uint256 totalPrice = pricePerSecond * duration;
        
        // Approve USDC spending
        usdc.approve(address(subscriptionRegistry), totalPrice);
        
        // Subscribe (this contract will be the consumer)
        address[] memory consumers = new address[](1);
        consumers[0] = address(this);
        
        subscriptionRegistry.subscribe(feedAddress, dueTime, consumers);
    }
}

Subscribing from Frontend

Alternatively, you can subscribe from your frontend before deploying your contract:
// JavaScript/TypeScript example
import { ethers } from 'ethers';
import SubscriptionRegistryABI from './abis/SubscriptionRegistry.json';

const subscribeToFeed = async (feedAddress, duration, consumerAddress) => {
  const subscriptionRegistry = new ethers.Contract(
    SUBSCRIPTION_REGISTRY_ADDRESS,
    SubscriptionRegistryABI,
    signer
  );
  
  const pricePerSecond = await subscriptionRegistry.getConsumerPricePerSecondScaled(feedAddress);
  const totalPrice = pricePerSecond.mul(duration);
  
  // Approve USDC
  await usdcContract.approve(SUBSCRIPTION_REGISTRY_ADDRESS, totalPrice);
  
  // Subscribe
  await subscriptionRegistry.subscribe(
    feedAddress,
    Math.floor(Date.now() / 1000) + duration,
    [consumerAddress]
  );
};

Step 2: Read Data from Feed

Once subscribed (or if the feed is free), you can read data using the IFeed interface.

Import the Interface

import {IFeed} from "./interfaces/IFeed.sol";

Reading Latest Data

The most common way to get feed data is using getLatest():
contract FeedConsumer {
    IFeed public feed;
    
    constructor(address _feedAddress) {
        feed = IFeed(_feedAddress);
    }
    
    function getLatestPrice() external view returns (bytes memory value, uint256 timestamp) {
        (value, timestamp) = feed.getLatest();
        return (value, timestamp);
    }
    
    // Get latest timestamp
    function getLastUpdate() external view returns (uint256) {
        return feed.getLastUpdated();
    }
}

Reading Historical Data

You can access historical data by index:
function getHistoricalPrice(uint256 index) external view returns (bytes memory value, uint256 timestamp) {
    (value, timestamp) = feed.getEntry(index);
    return (value, timestamp);
}
For compatibility with existing Chainlink integrations, feeds also implement Chainlink’s AggregatorV3Interface:
import {AggregatorV3Interface} from "./interfaces/chainlink/AggregatorV3Interface.sol";

contract ChainlinkCompatibleConsumer {
    AggregatorV3Interface public feed;
    
    constructor(address _feedAddress) {
        feed = AggregatorV3Interface(_feedAddress);
    }
    
    function getLatestRoundData() external view returns (
        uint80 roundId,
        int256 answer,
        uint256 startedAt,
        uint256 updatedAt,
        uint80 answeredInRound
    ) {
        return feed.latestRoundData();
    }
    
    function getLatestAnswer() external view returns (int256) {
        return feed.latestAnswer();
    }
    
    function getLatestTimestamp() external view returns (uint256) {
        return feed.latestTimestamp();
    }
}
Note: Chainlink-compatible functions assume the feed data fits in int256. For larger or complex data, use getLatest() instead.

Step 3: Decode Data

Feed data is returned as bytes. You need to decode it based on your feed’s data format.

Decoding Numeric Values

For uint256 values:

function getLatestAsUint256() external view returns (uint256 value, uint256 timestamp) {
    (bytes memory data, uint256 ts) = feed.getLatest();
    
    require(data.length >= 32, "Invalid data length");
    
    // Convert bytes to uint256
    assembly {
        value := mload(add(data, 32))
    }
    
    return (value, ts);
}

For int256 values:

function getLatestAsInt256() external view returns (int256 value, uint256 timestamp) {
    (bytes memory data, uint256 ts) = feed.getLatest();
    
    require(data.length >= 32, "Invalid data length");
    
    // Convert bytes to int256
    assembly {
        value := mload(add(data, 32))
    }
    
    return (value, ts);
}

For uint128 values:

function getLatestAsUint128() external view returns (uint128 value, uint256 timestamp) {
    (bytes memory data, uint256 ts) = feed.getLatest();
    
    require(data.length >= 16, "Invalid data length");
    
    uint256 temp;
    assembly {
        temp := mload(add(data, 32))
        // Mask to get first 16 bytes
        temp := and(temp, 0xffffffffffffffffffffffffffffffff)
    }
    
    return (uint128(temp), ts);
}

Decoding Complex Data Structures

For complex data (e.g., structs), use ABI decoding:
struct PriceData {
    uint256 price;
    uint256 volume;
    uint256 timestamp;
}

function getLatestAsStruct() external view returns (PriceData memory data, uint256 timestamp) {
    (bytes memory rawData, uint256 ts) = feed.getLatest();
    
    // Decode struct from bytes
    data = abi.decode(rawData, (PriceData));
    
    return (data, ts);
}

Safe Decoding Helper

Here’s a reusable helper library for safe decoding:
library FeedDataDecoder {
    error InvalidDataLength();
    
    function toUint256(bytes memory data) internal pure returns (uint256) {
        if (data.length < 32) revert InvalidDataLength();
        uint256 value;
        assembly {
            value := mload(add(data, 32))
        }
        return value;
    }
    
    function toInt256(bytes memory data) internal pure returns (int256) {
        if (data.length < 32) revert InvalidDataLength();
        int256 value;
        assembly {
            value := mload(add(data, 32))
        }
        return value;
    }
    
    function toUint128(bytes memory data) internal pure returns (uint128) {
        if (data.length < 16) revert InvalidDataLength();
        uint256 temp;
        assembly {
            temp := mload(add(data, 32))
            temp := and(temp, 0xffffffffffffffffffffffffffffffff)
        }
        return uint128(temp);
    }
}

// Usage
contract MyConsumer {
    using FeedDataDecoder for bytes;
    
    IFeed public feed;
    
    function getPrice() external view returns (uint256) {
        (bytes memory data,) = feed.getLatest();
        return data.toUint256();
    }
}

Complete Example

Here’s a complete example contract that subscribes to a feed and uses price data:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.29;

import {IFeed} from "./interfaces/IFeed.sol";
import {ISubscriptionRegistry} from "./interfaces/ISubscriptionRegistry.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";

contract PriceFeedConsumer {
    IFeed public immutable feed;
    ISubscriptionRegistry public immutable subscriptionRegistry;
    IERC20 public immutable usdc;
    
    uint256 public lastPrice;
    uint256 public lastUpdate;
    
    event PriceUpdated(uint256 price, uint256 timestamp);
    
    constructor(
        address _feed,
        address _subscriptionRegistry,
        address _usdc
    ) {
        feed = IFeed(_feed);
        subscriptionRegistry = ISubscriptionRegistry(_subscriptionRegistry);
        usdc = IERC20(_usdc);
    }
    
    /// @notice Subscribe this contract to the feed
    /// @param duration Subscription duration in seconds
    function subscribe(uint256 duration) external {
        uint256 dueTime = block.timestamp + duration;
        uint256 pricePerSecond = subscriptionRegistry.getConsumerPricePerSecondScaled(address(feed));
        uint256 totalPrice = pricePerSecond * duration;
        
        require(usdc.approve(address(subscriptionRegistry), totalPrice), "Approve failed");
        
        address[] memory consumers = new address[](1);
        consumers[0] = address(this);
        
        subscriptionRegistry.subscribe(address(feed), dueTime, consumers);
    }
    
    /// @notice Get the latest price from the feed
    /// @return price The latest price as uint256
    /// @return timestamp The timestamp of the price update
    function getLatestPrice() external view returns (uint256 price, uint256 timestamp) {
        (bytes memory data, uint256 ts) = feed.getLatest();
        
        require(data.length >= 32, "Invalid data");
        
        assembly {
            price := mload(add(data, 32))
        }
        
        return (price, ts);
    }
    
    /// @notice Update stored price (only callable by contract itself or EOA)
    function updatePrice() external {
        (uint256 price, uint256 timestamp) = this.getLatestPrice();
        
        lastPrice = price;
        lastUpdate = timestamp;
        
        emit PriceUpdated(price, timestamp);
    }
    
    /// @notice Check if subscription is still valid
    function isSubscribed() external view returns (bool) {
        uint256 lastUpdateTime = feed.getLastUpdated();
        // If feed was updated recently and we can read it, subscription is active
        // This is a simple check - you may want more sophisticated validation
        try feed.getLatest() returns (bytes memory, uint256) {
            return true;
        } catch {
            return false;
        }
    }
}

Access Control

Free Feeds

If a feed has consumerPricePerSecondScaled == 0, it’s free and can be read by anyone (EOAs or contracts). For paid feeds, access is controlled:
  1. EOAs (Externally Owned Accounts): Can read feeds if the EOA has an active subscription
  2. Contracts: Can read feeds only if:
    • The contract address has an active subscription, OR
    • The feed is free
Important: When a contract calls feed.getLatest(), the access check uses msg.sender, which is your contract address. Make sure your contract is subscribed, not the user’s wallet.

Best Practices

1. Check Data Freshness

Always verify the timestamp to ensure data is recent:
uint256 STALE_THRESHOLD = 1 hours;

function getFreshPrice() external view returns (uint256 price) {
    (bytes memory data, uint256 timestamp) = feed.getLatest();
    
    require(block.timestamp - timestamp < STALE_THRESHOLD, "Data too stale");
    
    // Decode and return
    assembly {
        price := mload(add(data, 32))
    }
}

2. Handle Empty Feeds

Check if feed has data before decoding:
function getPriceSafely() external view returns (uint256 price, bool hasData) {
    (bytes memory data, uint256 timestamp) = feed.getLatest();
    
    if (timestamp == 0 || data.length == 0) {
        return (0, false);
    }
    
    assembly {
        price := mload(add(data, 32))
    }
    
    return (price, true);
}

3. Validate Data Length

Always check data length matches your expected format:
function getPriceWithValidation() external view returns (uint256) {
    (bytes memory data,) = feed.getLatest();
    
    require(data.length == 32, "Invalid price data format");
    
    uint256 price;
    assembly {
        price := mload(add(data, 32))
    }
    
    return price;
}

4. Use Try-Catch for Graceful Errors

Handle subscription expiration gracefully:
function tryGetPrice() external view returns (uint256 price, bool success) {
    try feed.getLatest() returns (bytes memory data, uint256 timestamp) {
        if (data.length >= 32) {
            assembly {
                price := mload(add(data, 32))
            }
            success = true;
        }
    } catch {
        success = false;
    }
}

5. Store Feed Metadata

Store important feed information for easier access:
struct FeedInfo {
    address feedAddress;
    uint256 frequency;
    uint256 minSignatures;
    IFeed.FeedType feedType;
}

FeedInfo public feedInfo;

function initializeFeed(address _feedAddress) external {
    IFeed feed = IFeed(_feedAddress);
    
    feedInfo = FeedInfo({
        feedAddress: _feedAddress,
        frequency: feed.getFrequency(),
        minSignatures: feed.getMinSignaturesThreshold(),
        feedType: feed.getFeedType()
    });
}

Common Patterns

Oracle Pattern with Circuit Breaker

contract OracleConsumer {
    IFeed public feed;
    uint256 public maxPriceChange = 10e18; // 10x max change
    uint256 public lastPrice;
    
    function updatePrice() external {
        (uint256 price, uint256 timestamp) = getLatestPrice();
        
        require(block.timestamp - timestamp < 1 hours, "Stale data");
        
        if (lastPrice > 0) {
            uint256 change = price > lastPrice 
                ? (price * 1e18) / lastPrice
                : (lastPrice * 1e18) / price;
            
            require(change <= maxPriceChange, "Price change too large");
        }
        
        lastPrice = price;
    }
}

Price Aggregation from Multiple Feeds

contract PriceAggregator {
    IFeed[] public feeds;
    
    function getAveragePrice() external view returns (uint256 average) {
        uint256 sum = 0;
        uint256 count = 0;
        
        for (uint256 i = 0; i < feeds.length; i++) {
            try feeds[i].getLatest() returns (bytes memory data, uint256) {
                if (data.length >= 32) {
                    uint256 price;
                    assembly {
                        price := mload(add(data, 32))
                    }
                    sum += price;
                    count++;
                }
            } catch {
                // Skip failed feeds
            }
        }
        
        require(count > 0, "No feeds available");
        return sum / count;
    }
}

Troubleshooting

Error: “Not consumer”

Cause: Your contract address doesn’t have an active subscription. Solution:
  • Subscribe your contract address (not the user’s wallet)
  • Check subscription expiration time
  • Verify USDC approval and payment

Error: “Invalid data length”

Cause: The feed data format doesn’t match your decoding logic. Solution:
  • Check the feed’s data format in metadata
  • Verify data length before decoding
  • Use try-catch to handle format mismatches

Error: “Past timestamp” or “Future timestamp”

Cause: You’re trying to use historical data incorrectly, or the feed has timestamp issues. Solution:
  • Use getLatest() for current data
  • Use getEntry(index) for historical data
  • Check feed update frequency

Next Steps

Support

For integration help:
  • Check feed metadata on IPFS
  • Contact the feed owner
  • Join the Molpha community Discord