Skip to main content
Below you can find a few sample oracles demonstrating how to develop custom oracles on top of the Kenshi Oracle Network.
The time oracle is one of the simplest oracles you can make. It might not do much, but it shows exactly how you can make your custom oracle.
export const handler = async () => {
  return {
    statusCode: 200,
    body: JSON.stringify({
      method: "setTime",
      args: [new Date().valueOf()],
      maxGas: "100000000000000", // 0.0001 ETH
      abort: false,
    }),
  };
};
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;

contract Time {
    uint256 timestamp;
    event TimeRequest();

    /**
     * Emit and event that will be picked up by the Kenshi
     * Oracle Network and sent to your oracle for processing
     */
    function requestTime() external {
        emit TimeRequest();
    }

    /**
     * This method will be called by the Kenshi Oracle Network
     * with the result returned from your oracle
     */
    function setTime(uint256 time) external {
        timestamp = time;
    }

    /**
     * Public function to display the last retrieved timestamp
     */
    function showTime() public view returns (uint256) {
        return timestamp;
    }
}
The following example shows how to get price data from coingecko.com .
import { ethers } from "ethers";
import fetch from "node-fetch";

const url =
  "https://api.coingecko.com/api/v3/coins/kenshi/market_chart?vs_currency=usd&days=1";

export const handler = async () => {
  const resp = await fetch(url);
  const { prices } = await resp.json();
  const average =
    prices
      .slice(-6) // last 30 minutes
      .map(([_, price]) => price)
      .reduce((a, b) => a + b) / 6;
  return {
    statusCode: 200,
    body: JSON.stringify({
      method: "setPrice",
      args: [ethers.utils.parseUnits(average.toFixed(18)).toString()],
      maxGas: "100000000000000", // 0.0001 ETH
      abort: false,
    }),
  };
};
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;

contract Price {
    event PriceRequest();
    event PriceRequestFulfilled(uint256 price);

    /**
     * Emit and event that will be picked up by the Kenshi
     * Oracle Network and sent to your oracle for processing
     */
    function requestPrice() external {
        emit PriceRequest();
    }

    /**
     * This method will be called by the Kenshi Oracle Network
     * with the result returned from your oracle
     */
    function setPrice(uint256 price) external {
        emit PriceRequestFulfilled(price);
    }
}
The following example shows how to get weather data from brightsky.dev .
import { ethers } from "ethers";
import fetch from "node-fetch";

export const handler = async ({ body }) => {
  const now = new Date();
  const { entry } = JSON.parse(body);
  // Solidity doesn't have floating point numbers;
  // We divide lat and long by 100 for 0.01 precision
  const lat = ethers.BigNumber.from(entry.event.args.lat).toNumber() / 100;
  const long = ethers.BigNumber.from(entry.event.args.long).toNumber() / 100;
  const url = `https://api.brightsky.dev/weather?lat=${lat}&lon=${long}&date=${now.toISOString()}`;
  const resp = await fetch(url);
  const { weather } = await resp.json();
  return {
    statusCode: 200,
    body: JSON.stringify({
      method: "setWeather",
      args: [
        entry.event.args.lat,
        entry.event.args.long,
        // Solidity doesn't have floating point numbers;
        // We multiply the temperature by 100 for 0.01 precision
        Math.round(weather[0].temperature * 100),
      ],
      maxGas: "100000000000000", // 0.0001 ETH
      abort: false,
    }),
  };
};
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;

contract Weather {
    event WeatherRequest(uint256 lat, uint256 long);
    event WeatherRequestFulfilled(
        uint256 lat,
        uint256 long,
        uint256 temperature
    );

    /**
     * Emit and event that will be picked up by the Kenshi
     * Oracle Network and sent to your oracle for processing
     *
     * Note: Solidity doesn't have floating point numbers,
     * we assume lat and long are multiplied by 100 to eliminate
     * the decimal part of them
     */
    function requestWeather(uint256 lat, uint256 long) external {
        emit WeatherRequest(lat, long);
    }

    /**
     * This method will be called by the Kenshi Oracle Network
     * with the result returned from your oracle
     */
    function setWeather(
        uint256 lat,
        uint256 long,
        uint256 temperature
    ) external {
        emit WeatherRequestFulfilled(lat, long, temperature);
    }
}
The following example shows how to check if the user owns a specific amount of tokens or NFTs on another chain.
import { ethers } from "ethers";

const abi = ["function balanceOf(address) view returns (uint)"];

export const handler = async ({ body }) => {
  const { entry } = JSON.parse(body);
  const provider = new ethers.providers.JsonRpcProvider(process.env.RPC_ADDR);
  const contract = new ethers.Contract(entry.event.args.token, abi, provider);
  const balance = await contract.balanceOf(entry.event.args.user);
  return {
    statusCode: 200,
    body: JSON.stringify({
      method: "setBalance",
      args: [entry.event.args.user, entry.event.args.token, balance.toString()],
      maxGas: "100000000000000", // 0.0001 ETH
      abort: false,
    }),
  };
};
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;

contract Balance {
    event BalanceRequest(address user, address token);
    event BalanceRequestFulfilled(address user, address token, uint256 balance);

    /**
     * Emit and event that will be picked up by the Kenshi
     * Oracle Network and sent to your oracle for processing
     */
    function requestBalance(address user, address token) external {
        emit BalanceRequest(user, token);
    }

    /**
     * This method will be called by the Kenshi Oracle Network
     * with the result returned from your oracle
     */
    function setBalance(
        address user,
        address token,
        uint256 balance
    ) external {
        emit BalanceRequestFulfilled(user, token, balance);
    }
}
The following example shows how to create your own VRF oracle. This example uses the Kenshi VRF libraries, which implement draft 10 of the IETF ECVRF.
import { decode, prove, getFastVerifyComponents } from "@kenshi.io/node-ecvrf";
import { createHash } from "crypto";
import Elliptic from "elliptic";

const EC = new Elliptic.ec("secp256k1");

export const getPublicKey = (privateKey) => {
  const key = EC.keyFromPrivate(privateKey);
  return {
    key: key.getPublic("hex"),
    compressed: key.getPublic(true, "hex"),
    x: key.getPublic().getX(),
    y: key.getPublic().getY(),
  };
};

const fromHex = (hex) => Buffer.from(hex.slice(2));

const hash = (...args) => {
  const sha256 = createHash("sha256");
  for (const arg of args) {
    sha256.update(arg);
  }
  return sha256.digest().toString("hex");
};

export const handler = async ({ body }) => {
  const { entry } = JSON.parse(body);

  // You can generate a private key using the `keygen` function
  // exported by the @kenshi.io/node-ecvrf library
  const publicKey = getPublicKey(process.env.VRF_PRIVATE_KEY);

  const alpha = hash(
    fromHex(entry.transaction.hash),
    fromHex(entry.log.index),
    fromHex(entry.block.address),
    fromHex(entry.event.args.requestId)
  );

  const proof = prove(process.env.VRF_PRIVATE_KEY, alpha);
  const fast = getFastVerifyComponents(publicKey.key, proof, alpha);
  const [Gamma, c, s] = decode(proof);

  return {
    statusCode: 200,
    body: JSON.stringify({
      method: "setRandomness",
      args: [
        [Gamma.x.toString(), Gamma.y.toString(), c.toString(), s.toString()],
        `0x${alpha}`,
        [fast.uX, fast.uY],
        [fast.sHX, fast.sHY, fast.cGX, fast.cGY],
        entry.event.args.requestId,
      ],
      maxGas: "100000000000000", // 0.0001 ETH
      abort: false,
    }),
  };
};
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;

import "@kenshi.io/vrf-consumer/contracts/VRFUtils.sol";

contract VRFOracle {
    uint256 _requestId;
    mapping(uint256 => bool) alreadyFulfilled;

    event RandomnessRequest(uint256 requestId);
    event RandomnessRequestFulfilled(uint256 requestId, uint256 randomness);

    VRFUtils utils;

    constructor(bytes memory publicKey) {
        utils = new VRFUtils();
        utils.setPublicKey(publicKey);
    }

    /**
     * Emit and event that will be picked up by the Kenshi
     * Oracle Network and sent to your oracle for processing
     */
    function requestRandomness() external {
        emit RandomnessRequest(_requestId++);
    }

    /**
     * This method will be called by the Kenshi Oracle Network
     * with the result returned from your oracle
     *
     * Note: We encourage reading IETF ECVRF drafts to understand
     * what's going on: https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-vrf
     */
    function setRandomness(
        uint256[4] memory proof,
        bytes memory message,
        uint256[2] memory uPoint,
        uint256[4] memory vComponents,
        uint256 requestId
    ) external {
        require(!alreadyFulfilled[requestId], "Already fulfilled");
        bool isValid = utils.fastVerify(proof, message, uPoint, vComponents);
        require(isValid, "Cannot verify VRF results");
        bytes32 beta = utils.gammaToHash(proof[0], proof[1]);
        uint256 randomness = uint256(beta);
        alreadyFulfilled[requestId] = true;
        emit RandomnessRequestFulfilled(requestId, randomness);
    }
}