How to Migrate to dAPIs - A Developers Guide on Switching Your Oracle

Written by api3 | Published 2023/04/13
Tech Story Tags: api3 | guide | how-to | switching-your-oracle | dapi | dapis | developer's-guide | good-company

TLDRAPI3 has developed an oracle node that is operated by the API Provider. This removes the intermediary node layer, or middleman. API3 Market provides the ability to manage these data feeds while giving users access to a range of first-party data feeds. In the future, additional data feed services such as capturing [Oracle Extracted Value] will be accessible once a dAPI has been integrated.via the TL;DR App

Oracles provide a secure and reliable way for smart contracts to access real-time market data, which is essential for various DeFi applications.

API3 has developed an oracle node that is operated by the API Provider, removing the intermediary node layer, or middleman. This change in oracle architecture creates a scalable and transparent solution that enables first-party oracles to be aggregated according to user requirements. DeFi protocols looking to utilize first-party oracles do so by integrating dAPIs to their smart contracts.

With developers in mind, dAPIs have been designed with a simple integration process that abstracts away the technical implementation of accessing data feeds. The API3 Market provides the ability to manage these data feeds while giving users access to a range of first-party data feeds. In the future, additional data feed services, such as capturing Oracle Extracted Value (OEV) will be accessible once a dAPI has been integrated.

This tutorial will demonstrate how easy it is for developers to switch from Chainlink data feeds to API3’s first-party oracles.

Choosing the Contract

To demonstrate how easy it is to port over from Chainlink to API3, we are going to port over the DeFi options contract from this Chainlink tutorial.

We don’t need to go over the entire contract because we will only be modifying a small section of the contract to port it over, but in summary, the contract enables users to create, buy, exercise, and cancel options within the contract using Ethereum (ETH) and Chainlink (LINK) tokens. It makes use of Chainlink’s aggregator interface for obtaining price feeds while exercising options. For more of an in-depth explanation, you can check out the full guide.

Now let’s look at the code we want to modify:

pragma solidity ^0.8.17;

import "https://github.com/smartcontractkit/chainlink/blob/develop/evm-contracts/src/v0.6/interfaces/LinkTokenInterface.sol";
import "https://github.com/smartcontractkit/chainlink/blob/master/evm-contracts/src/v0.6/interfaces/AggregatorV3Interface.sol";

contract chainlinkOptions {
    //Pricefeed interfaces
    AggregatorV3Interface internal ethFeed;
    AggregatorV3Interface internal linkFeed;
    //Interface for LINK token functions
    LinkTokenInterface internal LINK;
    uint ethPrice;
    uint linkPrice;
    //Precomputing hash of strings
    bytes32 ethHash = keccak256(abi.encodePacked("ETH"));
    bytes32 linkHash = keccak256(abi.encodePacked("LINK"));
    address payable contractAddr;
    
    //Options stored in arrays of structs
    struct option {
        uint strike; //Price in USD (18 decimal places) option allows buyer to purchase tokens at
        uint premium; //Fee in contract token that option writer charges
        uint expiry; //Unix timestamp of expiration time
        uint amount; //Amount of tokens the option contract is for
        bool exercised; //Has option been exercised
        bool canceled; //Has option been canceled
        uint id; //Unique ID of option, also array index
        uint latestCost; //Helper to show last updated cost to exercise
        address payable writer; //Issuer of option
        address payable buyer; //Buyer of option
    }
    option[] public ethOpts;
    option[] public linkOpts;

    //Kovan feeds: https://docs.chain.link/docs/reference-contracts
    constructor() public {
        //ETH/USD Kovan feed
        ethFeed = AggregatorV3Interface(0x9326BFA02ADD2366b30bacB125260Af641031331);
        //LINK/USD Kovan feed
        linkFeed = AggregatorV3Interface(0x396c5E36DD0a0F5a5D33dae44368D4193f69a1F0);
        //LINK token address on Kovan
        LINK = LinkTokenInterface(0xa36085F69e2889c224210F603D836748e7dC0088);
        contractAddr = payable(address(this));
    }

    //Returns the latest ETH price
    function getEthPrice() public view returns (uint) {
        (
            uint80 roundID, 
            int price,
            uint startedAt,
            uint timeStamp,
            uint80 answeredInRound
        ) = ethFeed.latestRoundData();
        // If the round is not complete yet, timestamp is 0
        require(timeStamp > 0, "Round not complete");
        //Price should never be negative thus cast int to unit is ok
        //Price is 8 decimal places and will require 1e10 correction later to 18 places
        return uint(price);
    }
    
    //Returns the latest LINK price
    function getLinkPrice() public view returns (uint) {
        (
            uint80 roundID, 
            int price,
            uint startedAt,
            uint timeStamp,
            uint80 answeredInRound
        ) = linkFeed.latestRoundData();
        // If the round is not complete yet, timestamp is 0
        require(timeStamp > 0, "Round not complete");
        //Price should never be negative thus cast int to unit is ok
        //Price is 8 decimal places and will require 1e10 correction later to 18 places
        return uint(price);
    }
    
    //Updates prices to latest
    function updatePrices() internal {
        ethPrice = getEthPrice();
        linkPrice = getLinkPrice();
    }
    
    -----------------------
    -----------------------
    
    
}

The contract initializes the AggregatorV3Interfaces via the constructor for both the Eth and Link datafeeds; these can later be used to fetch the price. The two functions getEthPrice() and getLinkPrice()use the AggregatorV3Interface to fetch the price and return it. The options struct defines how options are stored when they are created, along with the ethOpts and linkOpts array to store the created options.

Porting it Over

Porting over the contract to use API3’s dAPIs in this options contract can be done in 3 easy steps:

  1. Fetching the proxy address of the “ETH/USD” and “LINK/USD” dAPIs from the API3 Market
  2. Import the proxy interface from API3 repo and set the proxy addresses in the constructor
  3. Replace the getEthPrice() and the getLinkPrice()logic with a .read() function call to the proxy interface

And that's it. You do not need to hold any special type of token to be able to read from the oracle, it is completely free to read.

Over 100+ dAPIs are currently available on the api3 market and work on a self-funded basis i.e you can top up the gas wallets to start reading from the oracle. If the dAPI is already funded you just need to copy the proxy address as seen below:

Here’s the same DeFi options contract updated to use API3 dAPIs

pragma solidity ^0.8.17;

import "@api3/contracts/v0.8/interfaces/IProxy.sol";

contract Api3Options {
    //Pricefeed proxies
    address public ethProxy;
    address public linkProxy;
    uint ethPrice;
    uint linkPrice;
    //Precomputing hash of strings
    bytes32 ethHash = keccak256(abi.encodePacked("ETH"));
    bytes32 linkHash = keccak256(abi.encodePacked("LINK"));
    address payable contractAddr;
    
    //Options stored in arrays of structs
    struct option {
        uint strike; //Price in USD (18 decimal places) option allows buyer to purchase tokens at
        uint premium; //Fee in contract token that option writer charges
        uint expiry; //Unix timestamp of expiration time
        uint amount; //Amount of tokens the option contract is for
        bool exercised; //Has option been exercised
        bool canceled; //Has option been canceled
        uint id; //Unique ID of option, also array index
        uint latestCost; //Helper to show last updated cost to exercise
        address payable writer; //Issuer of option
        address payable buyer; //Buyer of option
    }
    option[] public ethOpts;
    option[] public linkOpts;

    //Kovan feeds: https://docs.chain.link/docs/reference-contracts
    constructor(address _ethProxy, address _linkProxy) public {
        //ETH/USD Proxy on Goerli
        ethProxy = _ethProxy
        //LINK/USD Proxy on Goerli
        linkProxy = _linkProxy
        contractAddr = payable(address(this));
    }

    //Returns the latest ETH price
    function getEthPrice() public view returns (uint) {
        (int224 value,uint32 timestamp) = IProxy(ethProxy).read();
        // if the data feed is being updated with a one day-heartbeat
        // interval, you may want to check for that.
        require(
            timestamp + 1 days > block.timestamp,
            "Timestamp older than one day"
        );
        //Price should never be negative thus cast int to unit is ok
        //Price is 18 decimal places
        return uint(uint224(value));
    }
    
    //Returns the latest LINK price
    function getLinkPrice() public view returns (uint) {
        (int224 value,uint32 timestamp) = IProxy(linkProxy).read();
        // if the data feed is being updated with a one day-heartbeat
        // interval, you may want to check for that.
        require(
            timestamp + 1 days > block.timestamp,
            "Timestamp older than one day"
        );
        //Price should never be negative thus cast int to unit is ok
        //Price is 18 decimal places
        return uint(uint224(value));
    }
    
    //Updates prices to latest
    function updatePrices() internal {
        ethPrice = getEthPrice();
        linkPrice = getLinkPrice();
    }
    
    -----------------------
    -----------------------
    
    
}

As you can see, we only needed to call .read() on the IProxy(ethProxy) interface to start reading from the datafeed. We ended up with lesser lines of code and a much simpler reading interface. You can try running the contract yourself on REMIX. (Note: The remix version only allows you to open and close options in ETH for simplicity).

Why use dAPIs ?

dAPIs have been designed to abstract away the technical implementation of data feeds. Once a dAPI has been imported to oracle contracts, the API3 DAO can redirect the dAPI mapping upon user requests. This means that data feeds can be upgraded from self-funded to managed dAPIs, or directed to read alternate reference data with zero technical implementation.

Any update to data feeds, or a lack thereof, can create opportunities for OEV, such as arbitrage and liquidations. During each of these interactions value is leaking from the dApp users to both searchers and validators. Once a dAPI has been integrated, DeFi protocols will be able to capture Oracle Extractable Value without any further technical implementation.

Additionally, switching from Chainlink to API3 data feeds means no major alterations to smart contracts. This mitigates the need for audits while keeping battle-tested code intact.


Written by api3 | First-party oracle that securely & compliantly connects smart contracts to real world data
Published by HackerNoon on 2023/04/13