Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 18 additions & 15 deletions chainlink-data-feeds-skill/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ compatibility: Designed for AI agents that implement https://agentskills.io/spec
allowed-tools: Read WebFetch Write Edit Bash
metadata:
purpose: Chainlink Data Feeds developer assistance and reference
version: "0.0.3"
version: "0.0.4"
---

# Chainlink Data Feeds Skill
Expand All @@ -19,24 +19,26 @@ Route Data Feed requests to the simplest valid path. Generate working code on fi

1. Keep this file as the default guide.
2. Read [references/reading-price-feeds.md](references/reading-price-feeds.md) only when the user wants to read a price feed on EVM, write a consumer contract, read off-chain, look up AggregatorV3Interface, or debug a price feed integration.
3. Read [references/mvr-feeds.md](references/mvr-feeds.md) only when the user asks about Multiple-Variable Response feeds, bundle feeds, or BundleAggregatorProxy.
4. Read [references/svr-feeds.md](references/svr-feeds.md) only when the user asks about Smart Value Recapture, OEV recapture, or searcher onboarding.
5. Read [references/feed-types.md](references/feed-types.md) only when the user asks about feed categories, SmartData/RWA, rates/volatility, tokenized equity feeds, or needs help choosing a feed type.
6. Read [references/multi-chain.md](references/multi-chain.md) only when the user targets Solana, StarkNet, Aptos, or Tron.
7. Read [references/feed-operations.md](references/feed-operations.md) only when the user asks about L2 sequencer uptime checks, feed deprecation, contract registry, developer responsibilities, or data sources.
8. Read [references/official-sources.md](references/official-sources.md) only when the answer depends on live data that the reference files do not contain — feed addresses for a specific chain, current deprecation schedules, specific network parameters.
9. Read [references/source-code.md](references/source-code.md) only when debugging interface mismatches or the user needs to inspect contract source code on GitHub.
10. Do not load reference files speculatively.
3. Read [templates/starter-kit/README.md](templates/starter-kit/README.md) and the files in [templates/starter-kit](templates/starter-kit) when the user asks for a working example project, Foundry starter kit, runnable Data Feeds example, or says something like "give me a working example of a data feeds project." Use this starter-kit template instead of inventing project scaffolding.
4. Read [references/mvr-feeds.md](references/mvr-feeds.md) only when the user asks about Multiple-Variable Response feeds, bundle feeds, or BundleAggregatorProxy.
5. Read [references/svr-feeds.md](references/svr-feeds.md) only when the user asks about Smart Value Recapture, OEV recapture, or searcher onboarding.
6. Read [references/feed-types.md](references/feed-types.md) only when the user asks about feed categories, SmartData/RWA, rates/volatility, tokenized equity feeds, or needs help choosing a feed type.
7. Read [references/multi-chain.md](references/multi-chain.md) only when the user targets Solana, StarkNet, Aptos, or Tron.
8. Read [references/feed-operations.md](references/feed-operations.md) only when the user asks about L2 sequencer uptime checks, feed deprecation, contract registry, developer responsibilities, or data sources.
9. Read [references/official-sources.md](references/official-sources.md) only when the answer depends on live data that the reference files do not contain -- feed addresses for a specific chain, current deprecation schedules, specific network parameters.
10. Read [references/source-code.md](references/source-code.md) only when debugging interface mismatches or the user needs to inspect contract source code on GitHub.
11. Do not load reference files speculatively.

## Routing

1. Use reading-price-feeds.md as the default for any EVM price feed request — this covers the vast majority of Data Feeds use cases.
2. Route to the chain-specific section of multi-chain.md for non-EVM chains (Solana, Aptos, StarkNet, Tron).
3. Route to mvr-feeds.md for bundle or multi-variable feed requests.
4. Route to svr-feeds.md for OEV or MEV recapture requests.
5. Route to feed-operations.md for operational concerns (L2 sequencer checks, deprecation, monitoring).
6. Ask one focused question if the chain, feed type, or integration method is unclear.
7. Proceed without asking for read-only work: explanations, code generation, debugging.
2. Use templates/starter-kit for project-level example requests, especially Foundry starter requests. Return a concise file tree, the relevant template files, install/test commands, and the Sepolia ETH/USD feed configuration unless the user asks for another chain.
3. Route to the chain-specific section of multi-chain.md for non-EVM chains (Solana, Aptos, StarkNet, Tron).
4. Route to mvr-feeds.md for bundle or multi-variable feed requests.
5. Route to svr-feeds.md for OEV or MEV recapture requests.
6. Route to feed-operations.md for operational concerns (L2 sequencer checks, deprecation, monitoring).
7. Ask one focused question if the chain, feed type, or integration method is unclear.
8. Proceed without asking for read-only work: explanations, code generation, debugging.

## Safety Defaults

Expand Down Expand Up @@ -68,3 +70,4 @@ This skill references official Data Feeds documentation URLs throughout its refe
4. Keep answers proportional — a simple "read a price feed" question gets a code block and brief explanation, not a full tutorial.
5. Generate code only when code is actually needed.
6. Keep unsupported or out-of-scope features out of the answer rather than speculating.
7. When using the starter-kit template, preserve its Foundry layout and contract names unless the user asks for a different framework. The template is based on the Chainlink Foundry Starter Kit `PriceFeedConsumer.sol` example, with the required Data Feeds validation added for safer generated code.
44 changes: 44 additions & 0 deletions chainlink-data-feeds-skill/templates/starter-kit/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Chainlink Data Feeds Foundry Starter Kit Template

Use this template when a user asks for a working Data Feeds project, a Foundry starter kit, or a runnable example. It is based on the Chainlink Foundry Starter Kit `PriceFeedConsumer.sol` pattern and adds the skill's required validation: round completion, staleness, positive answer checks, and runtime decimals access.

## Files

```text
foundry.toml
remappings.txt
src/PriceFeedConsumer.sol
script/PriceFeedConsumer.s.sol
test/PriceFeedConsumer.t.sol
test/mocks/MockV3Aggregator.sol
```

## Dependencies

For a fresh Foundry project, install these dependencies before running the template:

```sh
forge install foundry-rs/forge-std
forge install smartcontractkit/chainlink-evm@contracts-v1.5.0
```

The included `remappings.txt` expects Chainlink contracts under `lib/chainlink-evm/` and forge-std under `lib/forge-std/`.

## Default Network

The deploy script defaults to Sepolia ETH/USD:

```text
0x694AA1769357215DE4FAC081bf1f309aDC325306
```

Tell users to verify feed addresses against the official Chainlink docs before deploying. For L2 networks, add a Sequencer Uptime Feed check before production use.

## Commands

```sh
forge test
forge script script/PriceFeedConsumer.s.sol:DeployPriceFeedConsumer --rpc-url $SEPOLIA_RPC_URL --private-key $PRIVATE_KEY --broadcast
```

Example code is unaudited and should be reviewed before production use.
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[profile.default]
src = "src"
out = "out"
libs = ["lib"]
optimizer = true
optimizer_runs = 20000
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
@chainlink/=lib/chainlink-evm/
forge-std/=lib/forge-std/src/
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;

import {Script} from "forge-std/Script.sol";
import {PriceFeedConsumer} from "../src/PriceFeedConsumer.sol";
import {MockV3Aggregator} from "../test/mocks/MockV3Aggregator.sol";

contract DeployPriceFeedConsumer is Script {
uint8 internal constant DECIMALS = 8;
int256 internal constant INITIAL_ANSWER = 2000e8;
uint256 internal constant STALENESS_THRESHOLD = 3600;

address internal constant SEPOLIA_ETH_USD_FEED = 0x694AA1769357215DE4FAC081bf1f309aDC325306;

function run() external returns (PriceFeedConsumer) {
vm.startBroadcast();
address priceFeed =
block.chainid == 11155111 ? SEPOLIA_ETH_USD_FEED : address(new MockV3Aggregator(DECIMALS, INITIAL_ANSWER));
PriceFeedConsumer consumer = new PriceFeedConsumer(priceFeed, STALENESS_THRESHOLD);
vm.stopBroadcast();

return consumer;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;

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

/**
* @title PriceFeedConsumer
* @notice Reads the latest answer from a Chainlink Data Feed with basic safety checks.
*/
contract PriceFeedConsumer {
AggregatorV3Interface internal immutable priceFeed;
uint256 public immutable stalenessThreshold;

error IncompleteRound();
error StalePrice(uint256 updatedAt);
error InvalidPrice(int256 price);

constructor(address _priceFeed, uint256 _stalenessThreshold) {
priceFeed = AggregatorV3Interface(_priceFeed);
stalenessThreshold = _stalenessThreshold;
}

/**
* @notice Returns the latest validated price.
* @return latest price using the feed's decimals.
*/
function getLatestPrice() public view returns (int256) {
(, int256 price,, uint256 updatedAt,) = priceFeed.latestRoundData();

if (updatedAt == 0) {
revert IncompleteRound();
}
if (block.timestamp - updatedAt > stalenessThreshold) {
revert StalePrice(updatedAt);
}
if (price <= 0) {
revert InvalidPrice(price);
}

return price;
}

/**
* @notice Returns the number of decimals used by the feed answer.
* @return feed decimals.
*/
function getDecimals() public view returns (uint8) {
return priceFeed.decimals();
}

/**
* @notice Returns the Price Feed address.
* @return Price Feed contract.
*/
function getPriceFeed() public view returns (AggregatorV3Interface) {
return priceFeed;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;

import {Test} from "forge-std/Test.sol";
import {PriceFeedConsumer} from "../src/PriceFeedConsumer.sol";
import {MockV3Aggregator} from "./mocks/MockV3Aggregator.sol";

contract PriceFeedConsumerTest is Test {
uint8 public constant DECIMALS = 8;
int256 public constant INITIAL_ANSWER = 2000e8;
uint256 public constant STALENESS_THRESHOLD = 3600;

PriceFeedConsumer public priceFeedConsumer;
MockV3Aggregator public mockV3Aggregator;

function setUp() public {
mockV3Aggregator = new MockV3Aggregator(DECIMALS, INITIAL_ANSWER);
priceFeedConsumer = new PriceFeedConsumer(address(mockV3Aggregator), STALENESS_THRESHOLD);
}

function testConsumerReturnsStartingValue() public view {
int256 price = priceFeedConsumer.getLatestPrice();

assertEq(price, INITIAL_ANSWER);
}

function testConsumerReturnsFeedDecimals() public view {
assertEq(priceFeedConsumer.getDecimals(), DECIMALS);
}

function testConsumerRevertsOnStalePrice() public {
vm.warp(block.timestamp + STALENESS_THRESHOLD + 1);

vm.expectRevert();
priceFeedConsumer.getLatestPrice();
}

function testConsumerRevertsOnInvalidPrice() public {
mockV3Aggregator.updateAnswer(0);

vm.expectRevert();
priceFeedConsumer.getLatestPrice();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;

contract MockV3Aggregator {
uint256 public constant version = 0;

uint8 public decimals;
int256 public latestAnswer;
uint256 public latestTimestamp;
uint256 public latestRound;

mapping(uint256 => int256) public getAnswer;
mapping(uint256 => uint256) public getTimestamp;
mapping(uint256 => uint256) private getStartedAt;

constructor(uint8 _decimals, int256 _initialAnswer) {
decimals = _decimals;
updateAnswer(_initialAnswer);
}

function updateAnswer(int256 _answer) public {
latestAnswer = _answer;
latestTimestamp = block.timestamp;
latestRound++;
getAnswer[latestRound] = _answer;
getTimestamp[latestRound] = block.timestamp;
getStartedAt[latestRound] = block.timestamp;
}

function updateRoundData(uint80 _roundId, int256 _answer, uint256 _timestamp, uint256 _startedAt) public {
latestRound = _roundId;
latestAnswer = _answer;
latestTimestamp = _timestamp;
getAnswer[latestRound] = _answer;
getTimestamp[latestRound] = _timestamp;
getStartedAt[latestRound] = _startedAt;
}

function getRoundData(uint80 _roundId)
external
view
returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound)
{
return (_roundId, getAnswer[_roundId], getStartedAt[_roundId], getTimestamp[_roundId], _roundId);
}

function latestRoundData()
external
view
returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound)
{
return (
uint80(latestRound),
getAnswer[latestRound],
getStartedAt[latestRound],
getTimestamp[latestRound],
uint80(latestRound)
);
}

function description() external pure returns (string memory) {
return "MockV3Aggregator";
}
}
4 changes: 2 additions & 2 deletions evals/chainlink-data-feeds-skill/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ npx promptfoo eval --filter-pattern "reading-price-feeds-01"

## Test Categories

- **Functional** (12 cases): End-to-end workflow tests covering reading-price-feeds, mvr-feeds, svr-feeds, feed-types, multi-chain, and feed-operations
- **Trigger Positive** (8 cases): Verify the skill activates for data-feeds-specific prompts
- **Functional** (14 cases): End-to-end workflow tests covering reading-price-feeds, mvr-feeds, svr-feeds, feed-types, multi-chain, feed-operations, and starter-kit scaffolding
- **Trigger Positive** (9 cases): Verify the skill activates for data-feeds-specific prompts
- **Trigger Negative** (6 cases): Verify the skill does not over-trigger for CCIP, VRF, Automation, Data Streams, Functions, or generic Solidity requests

## Rubrics
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Give me a working example of a data feeds project that I can build and test locally.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Scaffold a Foundry starter kit for reading the ETH/USD price feed on Sepolia, including a deploy script and tests I can run with `forge test`.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Can you set me up with a runnable Foundry project for reading Chainlink price feeds?
41 changes: 41 additions & 0 deletions evals/chainlink-data-feeds-skill/promptfooconfig.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,20 @@ functional_asserts: &functional_asserts
- type: llm-rubric
value: file://rubrics/clarification.txt

starter_kit_asserts: &starter_kit_asserts
- type: llm-rubric
value: file://rubrics/correctness.txt
- type: llm-rubric
value: file://rubrics/completeness.txt
- type: llm-rubric
value: file://rubrics/safety.txt
- type: llm-rubric
value: file://rubrics/freshness.txt
- type: llm-rubric
value: file://rubrics/clarification.txt
- type: llm-rubric
value: file://rubrics/starter-kit.txt

tests:
# ── Functional: Reading Price Feeds ──────────────────────────────────
- vars:
Expand Down Expand Up @@ -142,6 +156,23 @@ tests:
requires_live_source: "yes"
assert: *functional_asserts

# ── Functional: Starter Kit ──────────────────────────────────────────
- vars:
case_file: cases/functional/starter-kit-01.txt
workflow: starter-kit
requires_live_source: "no"
metadata:
workflow: starter-kit
category: functional
smoke: true
assert: *starter_kit_asserts

- vars:
case_file: cases/functional/starter-kit-02.txt
workflow: starter-kit
requires_live_source: "no"
assert: *starter_kit_asserts

# ── Trigger Positive ─────────────────────────────────────────────────
- vars:
case_file: cases/trigger-positive/reading-price-feeds-01.txt
Expand Down Expand Up @@ -199,6 +230,16 @@ tests:
expected_trigger: "yes"
assert: *trigger_positive_asserts

- vars:
case_file: cases/trigger-positive/starter-kit-01.txt
workflow: starter-kit
expected_trigger: "yes"
metadata:
workflow: starter-kit
category: trigger-positive
smoke: true
assert: *trigger_positive_asserts

# ── Trigger Negative ─────────────────────────────────────────────────
- vars:
case_file: cases/trigger-negative/ccip-01.txt
Expand Down
1 change: 1 addition & 0 deletions evals/chainlink-data-feeds-skill/rubrics/correctness.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ Workflow definitions:
- feed-types: choosing a feed type, understanding SmartData/RWA, rates/volatility, or tokenized equity feeds
- multi-chain: reading data feeds on Solana, Aptos, StarkNet, or Tron
- feed-operations: L2 sequencer checks, feed deprecation, contract registry, developer responsibilities, data sources
- starter-kit: scaffolding a complete, runnable Foundry project (PriceFeedConsumer contract, mock aggregator, deploy script, tests, and project config) from the starter-kit template when the user asks for a working example or Foundry starter kit

The expected workflow is: {{workflow}}

Expand Down
Loading
Loading