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.
- 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
LINK Token Address: 0xb0897686c545045aFc77CF20eC7A532E3120E0F1
Operator Address: 0x97b6Df5808b7f46Ee2C0e482E1B785CE3A2BC8BF
LINK Token Address: 0x326C977E6efc84E512bB9C30f76E30c160eD06FB
Operator Address: 0xB9756312523826A566e222a34793E414A81c88E1
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.
Payment Amount: 0.1 LINK
JobID: 827352c4d8684571b4605f9022853ddf
JobID as bytes32
: 0x3832373335326334643836383435373162343630356639303232383533646466
Payment Amount: 0.1 LINK
JobID: 14f849816fac426abda2992cbf47d2cd
JobID as bytes32
: 0x3134663834393831366661633432366162646132393932636266343764326364
- The EVM compatible user address to check
- A
string
with three possible outputs:NOT_FOUND
,HUMAN_AND_UNIQUE
, andKYC_USER
- A
uint40
representing a Unix Timestamp with granularity of seconds, only forKYC_USER
s, indicating the date and time at which they completed their KYC process. All otherstatus
results will return0
forkycTimestamp
.
// 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);
}
}