Skip to main content

Handler contract

info

You need to deploy a smart contract to fetch data with our oracle. In this section, we will instruct you on how to build this contract, what functions, interfaces and variables should be on it.

Demo consumer contract

Here is an oracle consumer sample contract. Find a detail explanation of the code just below this example.

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.20;

import "@openzeppelin/contracts/access/Ownable.sol";

interface IERC20 {
function increaseAllowance(
address spender,
uint256 addedValue
) external returns (bool);
}

interface IHandler {
function requestOracleDataWhitelisted(
bytes32 requestData
) external returns (bytes32);
function requestOracleDataPaid(
bytes32 requestData
) external returns (bytes32);
}

contract DemoConsumer is Ownable {
// This variable will effectively be modified by the data provider.
// Must be a uint256
uint256 private price;
address public handler;

mapping(bytes32 => uint8) private activeRequests;

// Will be called when data provider has sent data to the recieveData function
event PriceDiff(
bytes32 requestId,
uint256 oldPrice,
uint256 newPrice,
int256 diff
);
event AllowanceAdded(uint256 amout);

constructor(address _handler) Ownable(msg.sender) {
handler = _handler;
}

// Will be used by handler contract for fee pay
function sendBone(uint256 amount) external returns (bool status) {
require(msg.sender == handler, "Only handler can call this function");
require(address(this).balance >= amount, "Insufficient balance");

(status, ) = payable(handler).call{value: amount}("");
return status;
}

function requestData(bytes32 _data) external onlyOwner returns (bytes32) {
bytes32 requestId = IHandler(handler).requestOracleDataWhitelisted(
_data
);
activeRequests[requestId] = 1;
return requestId;
}

function requestDataBONE(
bytes32 _data
) external onlyOwner returns (bytes32) {
bytes32 requestId = IHandler(handler).requestOracleDataPaid(_data);
activeRequests[requestId] = 1;
return requestId;
}

function receiveData(uint256 _price, bytes32 _requestId) external {
require(msg.sender == address(handler), "only Handler can call");
require(activeRequests[_requestId] == 1, "request not active");
delete activeRequests[_requestId];
// optionally, do something and emit an event to the logs
int256 diff = int256(_price) - int256(price);
emit PriceDiff(_requestId, price, _price, diff);

// set the new price as sent by the provider
price = _price;
}

/**
* @dev getPrice returns the current price
* @return price uint256
*/
function getPrice() external view returns (uint256) {
return price;
}

// Function to simulate the hex conversion like web3's `asciiToHex`
function convertStringToHex(
string memory str
) public pure returns (bytes32) {
require(bytes(str).length <= 32, "String is too long!");
return bytes32(bytes(str));
}

// Convert a 32-byte hex back to a string
function hexToString(bytes32 hexValue) public pure returns (string memory) {
bytes memory bytesArray = new bytes(32);
uint256 charCount = 0;

// Extract the characters until we hit a null byte (0x00)
for (uint256 i = 0; i < 32; i++) {
bytes1 char = hexValue[i];
if (char != 0) {
bytesArray[charCount] = char;
charCount++;
}
}

// Return only the valid part of the string (non-zero characters)
bytes memory result = new bytes(charCount);
for (uint256 i = 0; i < charCount; i++) {
result[i] = bytesArray[i];
}

return string(result);
}

// Allow the contract to receive Ether
receive() external payable {}

}

Contract details

1. Interfaces

The interface IERC20 includes an increaseAllowance function which is an extension to the basic ERC20 standard and is used to safely increase the allowance that a token holder has set for a spender.

Interface IHandler will be used to communicate with the oracle, as its functions are used to request data from it. It is composed of 2 functions:

  • requestOracleDataWhitelisted: used to request data from users that have a subscription to the oracle service.
  • requestOracleDataPaid: used for builders paying for each call to the oracle.

2. Contract, events and constructor

The contract should be initialized with a price variable, representing the value of the token which will be requested in the code. The handler variable, which is used as a parameter in the constructor of this contract is actually the connection of this contract with our oracle. The handler address will be the contract address provided by the Developer Portal, on the oracle section.

3. Functions

sendBone is the function used to fund the contract, as the calls to the oracle are paid with BONE.

requestData and requestDataBONE are the functions that implement the IHandler interface. As we explained before, the difference between them is that requestData is used by builders who have an oracle subscription, while requestDataBONE is a service where builders pay by each call they make to the oracle.

receiveData handles the data received from the oracle by updating the variable price with its given value.

getPrice returns the token price.