Skip to content

Instantly share code, notes, and snippets.

@ericjaurena
Last active April 30, 2023 02:53
Show Gist options
  • Save ericjaurena/e29060362f311158dfccff94c95dd9c8 to your computer and use it in GitHub Desktop.
Save ericjaurena/e29060362f311158dfccff94c95dd9c8 to your computer and use it in GitHub Desktop.

Everest ID Oracle Overview

Everest is a platform and ecosystem that empowers users & businesses to build the future. Through the use of digital identities, electronic wallets, document management, a regulated stablecoin and fast, cost-effective ledger, users and businesses can verifiably engage in any transaction - all more transparently, fairer and more economically than ever before.

This oracle leverages Everest's Identity and Compliance services to identify whether a given EVM compatible blockchain wallet address is associated with a unique and human user, and has undergone Everest's KYC process.

Steps For Using This Oracle

  • Write and deploy your Chainlink contract using the network details and example consumer contract below
  • Fund your consumer contract with LINK
  • Call your request method, as described in these docs

Network Details

Polygon (Matic) Mainnet

LINK Token Address: 0xb0897686c545045aFc77CF20eC7A532E3120E0F1
Operator Address: 0x97b6Df5808b7f46Ee2C0e482E1B785CE3A2BC8BF

Ethereum Goerli Testnet

LINK Token Address: 0x326C977E6efc84E512bB9C30f76E30c160eD06FB
Operator Address: 0xB9756312523826A566e222a34793E414A81c88E1

Everest ID Job

This job indicates whether a given EVM compatible blockchain wallet address is associated with a unique and human user, and has undergone Everest's KYC process.

Polygon (Matic) Mainnet

Payment Amount: 0.1 LINK
JobID: 827352c4d8684571b4605f9022853ddf
JobID as bytes32: 0x3832373335326334643836383435373162343630356639303232383533646466

Ethereum Goerli Testnet

Payment Amount: 0.1 LINK
JobID: 14f849816fac426abda2992cbf47d2cd JobID as bytes32: 0x3134663834393831366661633432366162646132393932636266343764326364

Adapter

Request Parameters

address

  • The EVM compatible user address to check

Outputs

status

  • A string with three possible outputs: NOT_FOUND, HUMAN_AND_UNIQUE, and KYC_USER

kycTimestamp

  • A uint40 representing a Unix Timestamp with granularity of seconds, only for KYC_USERs, indicating the date and time at which they completed their KYC process. All other status results will return 0 for kycTimestamp.

Example Consumer Contract

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.9;

import "@chainlink/contracts/src/v0.8/ChainlinkClient.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";

contract EverestConsumer is ChainlinkClient, Ownable {
    using Chainlink for Chainlink.Request;
    using SafeERC20 for IERC20;

    enum Status {
        // Address does not exist
        NotFound,
        // KYC User status
        KYCUser,
        // Human & Unique status
        HumanAndUnique
    }

    struct Request {
        bool isFulfilled; // 1 byte - slot 0
        bool isCanceled; // 1 byte - slot 0
        bool isHumanAndUnique; // 1 byte - slot 0
        bool isKYCUser; // 1 byte - slot 0
        address revealer; // 20 bytes - slot 0
        address revealee; // 20 bytes - slot 1
        // `kycTimestamp` is zero if the status is not `KYCUser`,
        // otherwise it is an epoch timestamp that represents the KYC date
        uint40 kycTimestamp; // 5 bytes - slot 1
        // expiration = block.timestamp while `requestStatus` + 5 minutes.
        // If `isFulfilled` and `isCanceled` are false by this time -
        // the the owner of the request can cancel its
        // request using `cancelRequest` and return paid link tokens
        uint40 expiration; // 5 bytes - slot 1
    }

    uint40 private constant OPERATOR_EXPIRATION_TIME = 5 minutes;

    // latest sent request id by revealer address
    mapping(address => bytes32) public latestSentRequestId;

    // latest fulfilled request id by revealee address
    mapping(address => bytes32) public latestFulfilledRequestId;

    mapping(bytes32 => Request) private _requests;

    string public signUpURL;
    bytes32 public jobId;
    uint256 public oraclePayment;

    event Requested(
        bytes32 _requestId,
        address indexed _revealer,
        address indexed _revealee,
        uint40 _expiration
    );

    event Fulfilled(
        bytes32 _requestId,
        address indexed _revealer,
        address indexed _revealee,
        Status _status,
        uint40 _kycTimestamp
    );

    modifier ifRequestExists(bytes32 requestId) {
        require(requestExists(requestId), "Request does not exist");
        _;
    }

    constructor(
        address _link,
        address _oracle,
        string memory _jobId,
        uint256 _oraclePayment,
        string memory _signUpURL
    ) {
        setChainlinkToken(_link);
        setChainlinkOracle(_oracle);
        jobId = stringToBytes32(_jobId);
        oraclePayment = _oraclePayment;
        signUpURL = _signUpURL;
    }

    function requestStatus(address _revealee) external {
        require(_revealee != address(0), "Revelaee should not be zero address");

        IERC20(chainlinkTokenAddress()).safeTransferFrom(
            msg.sender,
            address(this),
            oraclePayment
        );

        Chainlink.Request memory request = buildOperatorRequest(
            jobId,
            this.fulfill.selector
        );
        request.addBytes("address", abi.encode(_revealee));

        bytes32 requestId = sendOperatorRequest(request, oraclePayment);

        uint40 expiration = uint40(block.timestamp) + OPERATOR_EXPIRATION_TIME;
        _requests[requestId] = Request({
            isFulfilled: false,
            isCanceled: false,
            isHumanAndUnique: false,
            isKYCUser: false,
            revealer: msg.sender,
            revealee: _revealee,
            kycTimestamp: 0,
            expiration: expiration
        });
        latestSentRequestId[msg.sender] = requestId;

        emit Requested(requestId, msg.sender, _revealee, expiration);
    }

    function fulfill(
        bytes32 _requestId,
        Status _status,
        uint40 _kycTimestamp
    ) external recordChainlinkFulfillment(_requestId) {
        if (_status == Status.KYCUser) {
            require(
                _kycTimestamp != 0,
                "_kycTimestamp should not be zero for KYCUser"
            );
        } else {
            require(
                _kycTimestamp == 0,
                "_kycTimestamp should be zero for non-KYCUser"
            );
        }

        Request storage request = _requests[_requestId];
        request.kycTimestamp = _kycTimestamp;
        request.isFulfilled = true;
        request.isHumanAndUnique = _status != Status.NotFound;
        request.isKYCUser = _status == Status.KYCUser;
        latestFulfilledRequestId[request.revealee] = _requestId;

        emit Fulfilled(
            _requestId,
            request.revealer,
            request.revealee,
            _status,
            _kycTimestamp
        );
    }

    function cancelRequest(bytes32 _requestId)
        external
        ifRequestExists(_requestId)
    {
        Request storage request = _requests[_requestId];
        require(
            !request.isCanceled && !request.isFulfilled,
            "Request should not be canceled or fulfilled"
        );
        require(
            request.revealer == msg.sender,
            "You are not an owner of the request"
        );
        cancelChainlinkRequest(
            _requestId,
            oraclePayment,
            this.fulfill.selector,
            request.expiration
        );
        IERC20(chainlinkTokenAddress()).safeTransfer(msg.sender, oraclePayment);
        request.isCanceled = true;
    }

    function getRequest(bytes32 _requestId)
        public
        view
        ifRequestExists(_requestId)
        returns (Request memory)
    {
        return _requests[_requestId];
    }

    function getLatestFulfilledRequest(address _revealee)
        external
        view
        returns
        (Request memory)
    {
        return getRequest(latestFulfilledRequestId[_revealee]);
    }

    function setSignUpURL(string memory _signUpURL) external onlyOwner {
        signUpURL = _signUpURL;
    }

    function getLatestSentRequestId() external view returns (bytes32) {
        require(latestSentRequestId[msg.sender] != 0, "No requests yet");

        return latestSentRequestId[msg.sender];
    }

    function requestExists(bytes32 _requestId) public view returns (bool) {
        return _requests[_requestId].revealer != address(0);
    }

    function statusToString(Status _status)
        external
        pure
        returns (string memory)
    {
        if (_status == Status.KYCUser) {
            return "KYC_USER";
        }
        if (_status == Status.HumanAndUnique) {
            return "HUMAN_AND_UNIQUE";
        }
        return "NOT_FOUND";
    }

    function setOracle(address _oracle) external onlyOwner {
        setChainlinkOracle(_oracle);
    }

    function setLink(address _link) external onlyOwner {
        setChainlinkToken(_link);
    }

    function setOraclePayment(uint256 _oraclePayment) external onlyOwner {
        oraclePayment = _oraclePayment;
    }

    function setJobId(string memory _jobId) external onlyOwner {
        jobId = stringToBytes32(_jobId);
    }

    function oracleAddress() external view returns (address) {
        return chainlinkOracleAddress();
    }

    function linkAddress() external view returns (address) {
        return chainlinkTokenAddress();
    }

    function stringToBytes32(string memory _source)
        private
        pure
        returns (bytes32)
    {
        bytes memory source = bytes(_source);
        require(source.length == 32, "Incorrect length");
        return bytes32(source);
    }
}

Sidebar Text

Chainlink Developer Documentation

Need Help?

DataProvider Documentation and Support

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment