Tabi Link Oracle
Introduction
Tabi Link includes Oracle and VRF, where Oracle represents an oracle that enables smart contracts to retrieve data from the outside world. VRF, also known as a verifiable random function, is a provably fair and verifiable random number generator (RNG) that enables smart contracts to access random values without affecting security or availability.
I. Oracle
Working principle:
Consumer Address: 0xdD325193a3195b4654b12EaCFE0fE548C7761590
Oracle address: 0x590311669252DCF34bfbF3981747D13Cf09ec19A
Existing jobs are as follows:
Get > Uint256 - (TOML) : 5b507ee5e7af477ebf31d4efaa5ba85b
Get > Int256 - (TOML) : bcb8bc009e6042dd96727b61f4bd0238
Get > Bool - (TOML) : ecacc544321640c399ddec5e99d6197f
Get > String : a109c25f143d4adf9a5258628ad88bb2
Get > Bytes : 264423c8ec534be6af0bb24ec8b1fdfa
multi-word (TOML) : c7116b94377d41cb94b4fe9ea38e1d3a
Consumer contracts are as follows:
// SPDX-License-Identifier: MIT
pragma solidity ^0.7.6;
import {Chainlink, ChainlinkClient} from "./ChainlinkClient.sol";
import {ConfirmedOwner} from "../shared/access/ConfirmedOwner.sol";
/**
* THIS IS AN EXAMPLE CONTRACT THAT USES UN-AUDITED CODE.
* DO NOT USE THIS CODE IN PRODUCTION.
*/
contract ATestnetConsumer is ChainlinkClient, ConfirmedOwner {
using Chainlink for Chainlink.Request;
uint256 public currentPrice;
event RequestEthereumPriceFulfilled(
bytes32 indexed requestId,
uint256 indexed price
);
constructor() ConfirmedOwner(msg.sender) { }
function requestEthereumPrice(
address _oracle,
string memory _jobId
) public onlyOwner {
Chainlink.Request memory req = buildChainlinkRequest(
stringToBytes32(_jobId),
address(this),
this.fulfillEthereumPrice.selector
);
req.add(
"get",
"https://min-api.cryptocompare.com/data/price?fsym=ETH&tsyms=USD"
);
req.add("path", "USD");
req.addInt("times", 100);
sendChainlinkRequestTo(_oracle, req, 0);
}
function fulfillEthereumPrice(
bytes32 _requestId,
uint256 _price
) public recordChainlinkFulfillment(_requestId) {
emit RequestEthereumPriceFulfilled(_requestId, _price);
currentPrice = _price;
}
function cancelRequest(
bytes32 _requestId,
uint256 _payment,
bytes4 _callbackFunctionId,
uint256 _expiration
) public onlyOwner {
cancelChainlinkRequest(
_requestId,
_payment,
_callbackFunctionId,
_expiration
);
}
function stringToBytes32(
string memory source
) private pure returns (bytes32 result) {
bytes memory tempEmptyStringTest = bytes(source);
if (tempEmptyStringTest.length == 0) {
return 0x0;
}
assembly {
// solhint-disable-line no-inline-assembly
result := mload(add(source, 32))
}
}
}
Request code:
import { ethers } from "hardhat";
async function main() {
const ATestnetConsumer = await ethers.deployContract("ATestnetConsumer");
await ATestnetConsumer.waitForDeployment();
console.log("ATestnetConsumer:", ATestnetConsumer.target);
const Operator = "0x590311669252DCF34bfbF3981747D13Cf09ec19A"
const job = "5b507ee5e7af477ebf31d4efaa5ba85b"
const result = await (await ATestnetConsumer.requestEthereumPrice(Operator, job)).wait()
// @ts-ignore
const ChainlinkRequested = ATestnetConsumer.interface.parseLog(result.logs[0])
console.log("ChainlinkRequested:", ChainlinkRequested, result?.hash)
// @ts-ignore
await ATestnetConsumer.once("RequestEthereumPriceFulfilled", (_requestId, _price) => {
console.log("_requestId:", _requestId, " price:", _price)
})
}
// We recommend this pattern to be able to use async/await everywhere
// and properly handle errors.
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});
The results are as follows:
npx hardhat run scripts/deploy_ATestnetConsumer.ts --network tabi
ATestnetConsumer: 0xdD325193a3195b4654b12EaCFE0fE548C7761590
request tx hash: 0x8b3270449f3690e0592458ff836afdbed9955cdcbda20ac7c82535b66636c3bf
_requestId: 0xa95ad018d2d2eb8f728b2cabe8bdcc38c2a89dba422c88b5af0aaa868a6d74ff price: 342234n
Detailed code can be found in tabi-oracle\
II. VRF
The specific process is shown in the figure.
VRF Consumer Address: 0xb484B5F803F912C074Ac204dC66114F06aBc2100
VRF Coordinator Address: 0x9492b270EdA7d4046D6aa5e3F15c24deD2c8BD25
The contract code for VRFConsumer.sol is as follows:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
import "./VRFConsumerBase.sol";
import "./VRFCoordinator.sol";
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
contract VRFConsumer is VRFConsumerBase, OwnableUpgradeable
{
event RequestSent(uint256 requestId, uint32 numWords);
event RequestFulfilled(
uint256 requestId,
uint256[] randomWords
);
error InsufficientFunds(uint256 balance, uint256 paid);
error RequestNotFound(uint256 requestId);
struct RequestStatus {
bool fulfilled; // whether the request has been successfully fulfilled
uint256[] randomWords;
}
mapping(uint256 => RequestStatus)
public s_requests; /* requestId --> requestStatus */
// past requests Id.
uint256[] public requestIds;
uint256 public lastRequestId;
VRFCoordinator public VrfCoordinator;
constructor(
address vrfCoordinator
) VRFConsumerBase(vrfCoordinator)
{
VrfCoordinator = VRFCoordinator(vrfCoordinator);
initialize();
}
function initialize() public initializer {
__Ownable_init();
}
function requestRandomWords(
uint32 _callbackGasLimit,
uint32 _numWords
) external onlyOwner returns (uint256 requestId) {
(uint16 requestConfirmations, , bytes32[] memory keyHash) = VrfCoordinator.getRequestConfig();
requestId = VrfCoordinator.requestRandomWords(keyHash[0], requestConfirmations, _callbackGasLimit, _numWords);
s_requests[requestId] = RequestStatus({
randomWords: new uint256[](0),
fulfilled: false
});
requestIds.push(requestId);
lastRequestId = requestId;
emit RequestSent(requestId, _numWords);
return requestId;
}
function fulfillRandomWords(
uint256 _requestId,
uint256[] memory _randomWords
) override internal {
RequestStatus storage request = s_requests[_requestId];
require(!request.fulfilled, "Fulfilled");
request.fulfilled = true;
request.randomWords = _randomWords;
emit RequestFulfilled(_requestId, _randomWords);
}
function getNumberOfRequests() external view returns (uint256) {
return requestIds.length;
}
function getRequestStatus(
uint256 _requestId
)
external
view
returns (bool fulfilled, uint256[] memory randomWords)
{
RequestStatus memory request = s_requests[_requestId];
return (request.fulfilled, request.randomWords);
}
}
VRFConsumerBase.sol contract:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
abstract contract VRFConsumerBase {
error OnlyCoordinatorCanFulfill(address have, address want);
address private immutable vrfCoordinator;
/**
* @param _vrfCoordinator address of VRFCoordinator contract
*/
constructor(address _vrfCoordinator) {
vrfCoordinator = _vrfCoordinator;
}
/**
* @notice fulfillRandomness handles the VRF response. Your contract must
* @notice implement it. See "SECURITY CONSIDERATIONS" above for important
* @notice principles to keep in mind when implementing your fulfillRandomness
* @notice method.
*
* @dev VRFConsumerBase expects its subcontracts to have a method with this
* @dev signature, and will call it once it has verified the proof
* @dev associated with the randomness. (It is triggered via a call to
* @dev rawFulfillRandomness, below.)
*
* @param requestId The Id initially returned by requestRandomness
* @param randomWords the VRF output expanded to the requested number of words
*/
function fulfillRandomWords(uint256 requestId, uint256[] memory randomWords) internal virtual;
// rawFulfillRandomness is called by VRFCoordinator when it receives a valid VRF
// proof. rawFulfillRandomness then calls fulfillRandomness, after validating
// the origin of the call
function rawFulfillRandomWords(uint256 requestId, uint256[] memory randomWords) external {
if (msg.sender != vrfCoordinator) {
revert OnlyCoordinatorCanFulfill(msg.sender, vrfCoordinator);
}
fulfillRandomWords(requestId, randomWords);
}
}
The request code is as follows:
import {ethers} from "ethers";
import abi_VRFConsumer from "./abi_VRFConsumer.json" assert { type: "json" }
import abi_VRFCoordinator from "./abi_VRFCoordinator.json" assert { type: "json" }
const main = async () => {
const tabiRpc = "https://rpc.testnet.tabichain.com/"
const vrfConsumerAddr = "0xb484B5F803F912C074Ac204dC66114F06aBc2100"
const privateKey = 'YOUR_PRIVATE_KEY'
const provider = new ethers.JsonRpcProvider(tabiRpc);
const wallet = new ethers.Wallet(privateKey, provider)
const vrfConsumer = ethers.Contract.from(vrfConsumerAddr, abi_VRFConsumer, wallet)
const requestId = await requestRandomWords(vrfConsumer)
console.log("requestRandomWords requestId:", requestId)
const randomWords = await listener(vrfConsumer, requestId)
console.log("randomWords:", randomWords)
}
const requestRandomWords = async (vrfConsumer) => {
const callbackGasLimit = 400000
const numWords = 4
const result = await (await vrfConsumer.requestRandomWords(callbackGasLimit, numWords)).wait()
console.log("requestRandomWords hash:", result.hash)
const RandomWordsRequested = ethers.Interface.from(abi_VRFCoordinator).parseLog(result.logs[0])
return RandomWordsRequested.args.requestId
}
const listener = async (vrfConsumer, requestId) => {
return new Promise((resolve) => {
vrfConsumer.once("RequestFulfilled", (_requestId, _randomWords) => {
if (requestId === _requestId) {
resolve(_randomWords)
}
})
})
}
main()
See VRF Example
Last updated