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.
curl -fsSL codexskills.dev/install.sh \ | sh -s -- chainlink-oracles Installs to ~/.agents/skills/chainlink-oracles/ for every project on this machine.
curl -fsSL codexskills.dev/cli.mjs -o codex-skills.mjsnode codex-skills.mjs install chainlink-oracles The CLI also searches, updates and removes skills. Run it with no arguments to pick from a list. Needs Node 20.12+.
curl -fsSL codexskills.dev/install.sh \ | sh -s -- --project chainlink-oracles Installs to .agents/skills/chainlink-oracles/ in the current repository. Commit it and everyone who runs Codex there gets it.
curl -fsSLO codexskills.dev/downloads/chainlink-oracles.zipunzip -o chainlink-oracles.zip -d ~/.agents/skills/ The archive holds the skill folder with its generated agents/openai.yaml. Unzip into .agents/skills/ to scope it to a repo.
& ([scriptblock]::Create((irm codexskills.dev/install.ps1))) chainlink-oracles PowerShell 5.1 or 7. Installs to %USERPROFILE%\.agents\skills\. Add -Project for the current repo.
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.
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
- Read
AGENTS.md, remappings and package files. Foundry projects usually installforge install smartcontractkit/chainlink-brownie-contractswith the remapping@chainlink/contracts/=lib/chainlink-brownie-contracts/contracts/; Hardhat projects use the@chainlink/contractsnpm package. Both need network to install. - 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). - 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:
| Check | Reason |
|---|---|
answer > 0 | Feeds return int256; a zero or negative value must never price collateral |
| Staleness vs the feed’s heartbeat | Feeds 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 period | During downtime prices freeze while L1 users can still act via forced inclusion |
| Bounds for your use case | Some 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; uselatestRoundData().- 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. callbackGasLimitmust cover the callback; measure it in tests and add margin.VRFConsumerBaseV2Plusalready includes Chainlink’sConfirmedOwner(onlyOwner,owner()); do not also inherit OpenZeppelinOwnable.- Since
@chainlink/contracts1.1.1 the callback takesuint256[] calldata; older code withmemoryneeds 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 { /* ... */ }
}
checkUpkeepruns off-chain in simulation; it can be expensive but must beview-safe.performUpkeepis 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
checkUpkeeplogic; 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
AggregatorV3Interfacewith settableanswer,updatedAtanddecimals; test zero, negative, stale, sequencer down, inside grace period and decimal scaling. - VRF:
VRFCoordinatorV2_5Mockfrom the VRF mocks folder. Create and fund a subscription, add the consumer, request, then call the mock’sfulfillRandomWords(requestId, consumer). Fuzz the random word to show any value yields a valid winner index. - Automation: call
checkUpkeepthenperformUpkeepdirectly; also callperformUpkeepwhen 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
-
latestRoundDataused, 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
-
performUpkeepre-validates and restricts or tolerates any caller
What the zip contains
SKILL.md plus the interface file below. Nothing runs on install.
agents/openai.yaml
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.