Chainlink Oracles skill for Codex

Treat every oracle answer as untrusted input. Codex wires Chainlink Data Feeds with positive-answer, heartbeat staleness, decimal scaling and L2 sequencer uptime checks, builds VRF v2.5 consumers that lock inputs before requesting and never revert in the callback, and writes Automation upkeeps whose performUpkeep re-validates its condition. Each integration ships with mocks and tests for the failure cases.

Install

curl -fsSL codexskills.dev/install.sh \  | sh -s -- chainlink-oracles

Installs to ~/.agents/skills/chainlink-oracles/ for every project on this machine.

104-line POSIX script. No sudo, no telemetry. Read install.sh

Use it in Codex

Start a prompt with $chainlink-oracles, or pick it from /skills. You can also just describe the task: Codex reads the description below and loads the playbook when your request matches.

What Codex reads before loading it

Integrates Chainlink services into Solidity contracts correctly - Data Feeds via AggregatorV3Interface with staleness, sign, decimals and L2 sequencer uptime checks, VRF v2.5 subscriptions with VRFConsumerBaseV2Plus and re-roll-proof request flows, and Chainlink Automation upkeeps with checkUpkeep and re-validated performUpkeep - plus local mocks and fork tests. Use when a contract needs an asset price, verifiable randomness for a raffle, mint or game, or scheduled and conditional execution, or when reviewing existing oracle code for unsafe assumptions.

Use it when

  • A contract needs an asset price for collateral, minting or settlement
  • A raffle, game or NFT reveal needs verifiable randomness
  • A contract function must run on a schedule or when a condition holds
  • Existing oracle code needs review for stale or manipulable prices

Use something else when

  • You need a full protocol security review, not only oracle usage: use smart-contract-audit
  • Randomness or prices are needed only off-chain in a frontend: use viem-wagmi

How to check the result

Tests cover zero, negative and stale prices, sequencer downtime and grace period, VRF fulfillment through the coordinator mock, and performUpkeep called when not needed or by a stranger.

The mistake it prevents

Reading latestRoundData without a staleness check, or on an L2 without the sequencer uptime feed, so frozen prices keep pricing liquidations.

~/.agents/skills/chainlink-oracles/SKILL.md

--- name: chainlink-oracles description: Integrates Chainlink services into Solidity contracts correctly - Data Feeds via AggregatorV3Interface with staleness, sign, decimals and L2… license: MIT ---

Oracle integration is right when the contract treats every oracle answer as untrusted input: prices are checked for staleness, sign and scale, L2 sequencer downtime is handled, randomness cannot be re-rolled or front-run, and automation entry points stay safe when anyone calls them.

Inspect first

  1. Read AGENTS.md, remappings and package files. Foundry projects usually install forge install smartcontractkit/chainlink-brownie-contracts with the remapping @chainlink/contracts/=lib/chainlink-brownie-contracts/contracts/; Hardhat projects use the @chainlink/contracts npm package. Both need network to install.
  2. Confirm import paths against the installed version: Chainlink has reorganized folders between releases (for example shared/interfaces/ for feeds, vrf/dev/ for VRF v2.5).
  3. Identify target chains. Feed addresses, VRF coordinators, key hashes and Automation registries differ per chain: take them from docs.chain.link for that exact network and store them in per-chain config, never hard-code across chains.

Data Feeds (prices)

// SPDX-License-Identifier: MIT
pragma solidity 0.8.28;

import {AggregatorV3Interface} from "@chainlink/contracts/src/v0.8/shared/interfaces/AggregatorV3Interface.sol";

contract PriceConsumer {
    error InvalidPrice();
    error StalePrice(uint256 updatedAt);
    error SequencerDown();
    error GracePeriodNotOver();

    AggregatorV3Interface public immutable feed;
    AggregatorV3Interface public immutable sequencerFeed; // address(0) on L1
    uint256 public immutable maxStaleness;                // heartbeat + margin, per feed
    uint8 private immutable feedDecimals;
    uint256 private constant GRACE_PERIOD = 1 hours;

    constructor(address feed_, address sequencerFeed_, uint256 maxStaleness_) {
        feed = AggregatorV3Interface(feed_);
        sequencerFeed = AggregatorV3Interface(sequencerFeed_);
        maxStaleness = maxStaleness_;
        feedDecimals = feed.decimals();
    }

    /// @return price scaled to 18 decimals
    function price() public view returns (uint256) {
        if (address(sequencerFeed) != address(0)) {
            (, int256 status, uint256 startedAt, , ) = sequencerFeed.latestRoundData();
            if (status != 0) revert SequencerDown();               // 0 = up, 1 = down
            if (startedAt == 0 || block.timestamp - startedAt <= GRACE_PERIOD) revert GracePeriodNotOver();
        }
        (, int256 answer, , uint256 updatedAt, ) = feed.latestRoundData();
        if (answer <= 0) revert InvalidPrice();
        if (block.timestamp - updatedAt > maxStaleness) revert StalePrice(updatedAt);
        return uint256(answer) * 10 ** (18 - feedDecimals);
    }
}

Checks and why:

CheckReason
answer > 0Feeds return int256; a zero or negative value must never price collateral
Staleness vs the feed’s heartbeatFeeds update on deviation or heartbeat; heartbeats vary per feed (often 1 h or 24 h)
Decimals from feed.decimals()USD pairs are commonly 8 decimals, ETH pairs 18, but never assume
L2 sequencer uptime feed + grace periodDuring downtime prices freeze while L1 users can still act via forced inclusion
Bounds for your use caseSome aggregators clamp at min/max answers; a crash below the floor reports the floor. Add sanity bounds or a secondary source for liquidation-critical paths
  • Decide what happens on a failed check: revert (simplest, may block liquidations) or fall back to a second oracle. Document the choice; never silently use a stale price.
  • latestAnswer() is deprecated; use latestRoundData().
  • Derived prices (TOKEN/ETH x ETH/USD) multiply the error and staleness of both feeds; check both.
  • Feeds can be deprecated. Watch the feed’s status on data.chain.link and make the feed address settable behind a timelock if the contract lives long.

VRF v2.5 (randomness)

import {VRFConsumerBaseV2Plus} from "@chainlink/contracts/src/v0.8/vrf/dev/VRFConsumerBaseV2Plus.sol";
import {VRFV2PlusClient} from "@chainlink/contracts/src/v0.8/vrf/dev/libraries/VRFV2PlusClient.sol";

contract Raffle is VRFConsumerBaseV2Plus {
    uint256 public immutable subId;
    bytes32 public immutable keyHash;
    uint32 private constant CALLBACK_GAS = 150_000;
    uint16 private constant CONFIRMATIONS = 3;

    enum State { Open, Drawing, Closed }
    State public state;
    address[] public players;
    uint256 public pendingRequest;
    address public winner;

    error NotOpen();
    error NoPlayers();
    event DrawRequested(uint256 requestId);
    event WinnerPicked(address winner);

    constructor(address coordinator, uint256 subId_, bytes32 keyHash_) VRFConsumerBaseV2Plus(coordinator) {
        subId = subId_;
        keyHash = keyHash_;
    }

    // enter() (pushes msg.sender while state == Open) and claim() omitted for brevity.

    function draw() external onlyOwner {
        if (state != State.Open) revert NotOpen();
        if (players.length == 0) revert NoPlayers();   // callback must not divide by zero
        state = State.Drawing;                         // lock entries before requesting
        pendingRequest = s_vrfCoordinator.requestRandomWords(
            VRFV2PlusClient.RandomWordsRequest({
                keyHash: keyHash,
                subId: subId,
                requestConfirmations: CONFIRMATIONS,
                callbackGasLimit: CALLBACK_GAS,
                numWords: 1,
                extraArgs: VRFV2PlusClient._argsToBytes(VRFV2PlusClient.ExtraArgsV1({nativePayment: false}))
            })
        );
        emit DrawRequested(pendingRequest);
    }

    function fulfillRandomWords(uint256 requestId, uint256[] calldata randomWords) internal override {
        if (requestId != pendingRequest) return;       // never revert in the callback
        winner = players[randomWords[0] % players.length];
        state = State.Closed;
        emit WinnerPicked(winner);                     // payout happens in a separate claim()
    }
}

Hard rules for VRF:

  • Lock all inputs before requesting. No new entries, bets or parameter changes between request and fulfillment.
  • No re-requests or cancellations that let an operator or user retry until a favourable result. If a request stalls, follow a documented, time-locked recovery path.
  • Never revert in fulfillRandomWords. The coordinator will not retry. Store the result and do heavy work (payouts, NFT minting loops) in a separate call.
  • callbackGasLimit must cover the callback; measure it in tests and add margin.
  • VRFConsumerBaseV2Plus already includes Chainlink’s ConfirmedOwner (onlyOwner, owner()); do not also inherit OpenZeppelin Ownable.
  • Since @chainlink/contracts 1.1.1 the callback takes uint256[] calldata; older code with memory needs updating when upgrading the package.
  • Subscriptions are created and funded (LINK or native) in the VRF app; the contract address must be added as a consumer. Funding is a payment: the user does it or confirms the amount.

Automation

import {AutomationCompatibleInterface} from "@chainlink/contracts/src/v0.8/automation/AutomationCompatible.sol";

contract Harvester is AutomationCompatibleInterface {
    uint256 public lastRun;
    uint256 public constant INTERVAL = 1 days;
    address public forwarder; // set to the upkeep's forwarder after registration

    function checkUpkeep(bytes calldata) external view returns (bool upkeepNeeded, bytes memory) {
        upkeepNeeded = block.timestamp - lastRun >= INTERVAL;
    }

    function performUpkeep(bytes calldata) external {
        if (msg.sender != forwarder) revert();                      // or accept anyone, see below
        if (block.timestamp - lastRun < INTERVAL) revert();         // re-validate
        lastRun = block.timestamp;
        _harvest();
    }

    function _harvest() internal { /* ... */ }
}
  • checkUpkeep runs off-chain in simulation; it can be expensive but must be view-safe.
  • performUpkeep is a public function: re-check the condition inside it, and either restrict the caller to the upkeep’s forwarder or make the action harmless for anyone to trigger.
  • Pure time schedules can use a time-based (cron) upkeep with no checkUpkeep logic; event driven work can use log-trigger upkeeps.
  • Upkeeps need a LINK balance; monitor it or they silently stop.

Testing

  • Unit: write a small mock implementing AggregatorV3Interface with settable answer, updatedAt and decimals; test zero, negative, stale, sequencer down, inside grace period and decimal scaling.
  • VRF: VRFCoordinatorV2_5Mock from the VRF mocks folder. Create and fund a subscription, add the consumer, request, then call the mock’s fulfillRandomWords(requestId, consumer). Fuzz the random word to show any value yields a valid winner index.
  • Automation: call checkUpkeep then performUpkeep directly; also call performUpkeep when not needed and from a stranger to prove it reverts or is harmless.
  • Fork tests against real feeds pinned to a block (network needed) to confirm addresses and decimals: see $foundry-testing.

Review checklist for existing oracle code

  • latestRoundData used, with positive-answer and staleness checks
  • Staleness threshold matches the specific feed’s heartbeat
  • Decimals read from the feed; scaling tested
  • Sequencer uptime check on L2 deployments
  • No DEX spot price used where a feed should be
  • VRF inputs locked before request; no re-roll path; callback cannot revert
  • performUpkeep re-validates and restricts or tolerates any caller

What the zip contains

SKILL.md plus the interface file below. Nothing runs on install.

agents/openai.yamlgenerated from this page

Gives the Codex app a display name, a one-line description and a starter prompt for this skill.

interface:
  display_name: "Chainlink Oracles"
  short_description: "Treat every oracle answer as untrusted input"
  default_prompt: "$chainlink-oracles Use the Chainlink ETH/USD feed to value collateral on Arbitrum safely"

About this skill

Written and maintained by the Codex Skills editors to the same editorial standard as every skill here: a selective description, a real workflow, known pitfalls and a way to verify the work. First published , last updated . MIT licensed. Something wrong or out of date? Tell us.

esc
Just launched

Codex Skills Pro is live

79 premium skills for Codex, each with scripts, references and an evidence report that proves the result.

Bought one by one$882.21
All 79 skills$49
94% off

That's $0.62 a skill, paid once.

Get all 79 for $49Or pick single skills from $4.99 →

Secure checkout by Creem