# OoO Implementation & Integration Guide

This guide will walk you through all the necessary steps to get a fully working (albeit simple) smart contract, which can obtain Price data from the Finchains Oracle of Oracles.

This guide will result in something similar to the Data Consumer Demo (opens new window).

The instructions will outline the steps required to deploy on the Rinkeby testnet, but will also work with mainnet.

IMPORTANT

You do not need to implement or deploy the Router.sol smart contract.

This is a smart contract deployed and maintained by the Unification Foundation and is the core of the xFUND Router network. Your smart contract will only import and build on the ConsumerBase.sol base smart contract, which in turn interacts with the Router smart contract.

# 1. Initialise your project & install dependencies

Note

if you are integrating into an existing project, or are already familiar with initialising NodeJS and Truffle projects, you can skip this section and move on to 1.2. Install the required dependencies.

# 1.1. Initialise your project

Create the directory, and initialise NPM - accept the defaults for the npm init command:

mkdir consumer_demo && cd consumer_demo
npm init

Install truffle, and initialise the Truffle project:

npm install truffle --save-dev
npx truffle init

You should now have a project structure as follows:

contracts
migrations
node_modules
package.json
package-lock.json
test
truffle-config.js

# 1.2. Install the required dependencies

We need to install some dependencies for the project - @unification-com/xfund-router:

npm install @unification-com/xfund-router

If you don't have them installed already, we also need dotenv and @truffle/hdwallet-provider, both of which will be used to aid deployment and interaction later:

npm install dotenv
npm install @truffle/hdwallet-provider --save-dev

# 2. Create the initial Contract

We'll start with a simple contract structure. With a text editor, create contracts/MyDataConsumer.sol with the following contents:

// SPDX-License-Identifier: MIT

pragma solidity >=0.7.0 <0.8.0;

contract MyDataConsumer {

    uint256 public price;

    constructor() {
        price = 0;
    }
}

This will be the basis for adding the OoO functionality from the xfund-router libraries.

The price variable is what we would like to be updated by OoO when we request data.

# 2.1 Import the xfund-router Library contracts

Next, we need to import the ConsumerBase.sol smart contract, which interacts with the Router.sol smart contract (which has been deployed and is maintained by the Unification Foundation). The ConsumerBase.sol smart contract contains required functions for interacting with the system. You only need to define a couple of functions in your own smart contract in order to use the OoO system, which override or extend the underlying ConsumerBase functions.

Note

You can view the functions implemented by ConsumerBase.sol in the Data Consumer smart contract API documentation. There are some additional helper functions which can be wrapped in functions in your own smart contract.

First, import the ConsumerBase.sol smart contract. After the pragma definition, add:

import "@unification-com/xfund-router/contracts/lib/ConsumerBase.sol";

Then, edit the contract definition, so that it extends ConsumerBase.sol:

contract MyDataConsumer is ConsumerBase {

Finally, modify the constructor function to call the ConsumerBase.sol's constructor, passing the contract addresses for Router and xFUND:

    constructor(address _router, address _xfund)
    public ConsumerBase(_router, _xfund) {
        price = 0;
    }

The full MyDataConsumer.sol contract code should now look like this:

// SPDX-License-Identifier: MIT

pragma solidity ^0.6.0;

import "@unification-com/xfund-router/contracts/lib/ConsumerBase.sol";

contract MyDataConsumer is ConsumerBase {

    uint256 public price;

    constructor(address _router, address _xfund)
    public ConsumerBase(_router, _xfund) {
        price = 0;
    }
}

# 3. Define the required receiveData smart contract function

receiveData will be called by the Data Provider (indirectly - it is actually proxied via the Router smart contract) in order to fulfil a data request and send data to our smart contract. It should override the abstract receiveData function defined in the ConsumerBase.sol base smart contract, and must have the following parameters:

uint256 _price - the price data the provider is sending
bytes32 _requestId - the ID of the request being fulfilled. This is passed in case your contract needs to do some further processing with the request ID.

Add the following function definition to your MyDataConsumer.sol contract:

    function receiveData(uint256 _price, bytes32 _requestId)
    internal override {
        price = _price;
    }

You can optionally also add an event to the function, for example:

Define a new event in the contract:

contract MyDataConsumer is ConsumerBase {
    ...
    event GotSomeData(bytes32 requestId, uint256 price);

and emit within the receiveData function:

    function receiveData( ...
        ...
        emit GotSomeData(_requestId, _price);

# 4. Define a function to initialise a data request

Next, you'll need a function to request data. This needs to call the ConsumerBase's _requestData function, which will forward the request to the Router:

    function getData(address _provider, uint256 _fee, bytes32 _data) external returns (bytes32) {
        return _requestData(_provider, _fee, _data);
    }

# 5. Add a function to allow Router to transfer fees

Finally, you'll need a function that calls ConsumerBase's _increaseRouterAllowance function. This function will increase the Routers xFUND allowance, allowing it to pay data request fees on behalf of your smart contract:

    function increaseRouterAllowance(uint256 _amount) external {
        require(_increaseRouterAllowance(_amount));
    }

Note

This function should be protected by a library such as OpenZeppelin's Ownable, and have the onlyOwner modifier applied such that only your contract's owner can all the function!

The final MyDataConsumer.sol code should now look something like this:

// SPDX-License-Identifier: MIT

pragma solidity ^0.6.0;

import "@unification-com/xfund-router/contracts/lib/ConsumerBase.sol";

contract MyDataConsumer is ConsumerBase {

    uint256 public price;

    event GotSomeData(bytes32 requestId, uint256 price);

    constructor(address _router, address _xfund)
    public ConsumerBase(_router, _xfund) {
        price = 0;
    }
    
    // Optionally protect with a modifier to limit who can call
    function getData(address _provider, uint256 _fee, bytes32 _data) external returns (bytes32) {
        return _requestData(_provider, _fee, _data);
    }
    
    // Todo - protect with a modifier to limit who can call!
    function increaseRouterAllowance(uint256 _amount) external {
        require(_increaseRouterAllowance(_amount));
    }

    // ConsumerBase ensures only the Router can call this
    function receiveData(uint256 _price, bytes32 _requestId)
    internal override {
        price = _price;
        // optionally emit an event to the logs
        emit GotSomeData(_requestId, _price);
    }
}

Finally, compile your contract:

npx truffle compile

# 6. Set up the deployment .env and truffle-config.js

Ensure that you have:

  1. an Infura (opens new window) account and API key
  2. a test wallet private key and address with Test ETH on Sepolia (opens new window) testnet

# 6.1 .env

Note

See Contract Addresses for the latest Rinkeby contract address required for the ROUTER_ADDRESS and XFUND_ADDRESS variables.

Create a .env file in the root of your project with the following and set each value accordingly:

# Private key for wallet used to deploy. This will be the contract owner
# Most functions in ConsumerBase.sol can only be called by the owner
ETH_PKEY=
# Infura API key - used for deployment
INFURA_PROJECT_ID=
# Contract address of the xFUND Router
ROUTER_ADDRESS=
# Contract address of xFUND
XFUND_ADDRESS=

# 6.2 truffle-config.js

Edit the truffle-config.js file in the root of your project with the following, set up for Rinkeby testnet:

require("dotenv").config()
const HDWalletProvider = require('@truffle/hdwallet-provider');

const {
  ETH_PKEY,
  INFURA_PROJECT_ID,
} = process.env

module.exports = {
  networks: {
    develop: {
      host: "127.0.0.1",
      port: 8545,
      network_id: "*",
    },
    sepolia: {
      provider: () =>
        new HDWalletProvider({
          privateKeys: [ETH_PKEY],
          providerOrUrl: `https://sepolia.infura.io/v3/${INFURA_PROJECT_ID}`
        }),
      network_id: "5",
      gas: 10000000,
      gasPrice: 10000000000,
      skipDryRun: true,
    }
  },
  compilers: {
    solc: {
      version: "0.6.12",
      settings: {
        optimizer: {
          enabled: true,
          runs: 200
        }
      }
    }
  }
};

# 7. Set up the Truffle migrations scripts

create the following Truffle migration script in migrations/2_deploy.js:

require("dotenv").config()
const MyDataConsumer = artifacts.require("MyDataConsumer")

const { ROUTER_ADDRESS, XFUND_ADDRESS } = process.env

module.exports = function(deployer) {
  deployer.deploy(MyDataConsumer, ROUTER_ADDRESS, XFUND_ADDRESS)
}

This will deploy your contract with the required parameters.

# 8. Deploy your contract

Finally, deploy your contract with the following command:

npx truffle migrate --network=sepolia

That's it! You're now ready to initialise and interact with your OoO enabled smart contract.

On to interaction.