Chainlink Data Feeds son redes de oráculos descentralizadas que agregan datos de múltiples fuentes independientes y publican el resultado onchain, donde cualquier contrato puede leer el último valor en una sola llamada. Este artículo explica el modelo de feeds basado en push, recorre un consumidor Solidity ejecutable usando AggregatorV3Interface y muestra cómo añadir protecciones de obsolescencia y de disponibilidad del secuenciador L2 que evitan que tu protocolo actúe sobre datos incorrectos. También cubre lecturas offchain, un procedimiento de verificación reproducible y las ventajas y desventajas entre Data Feeds y Data Streams basados en pull.
Qué entregan realmente Chainlink Data Feeds
Chainlink Data Feeds son productos de datos onchain producidos por redes de oráculos descentralizadas (DON). Cada feed agrega valores de múltiples proveedores de datos independientes y operadores de nodos, y luego publica una única respuesta validada en un contrato agregador onchain. Tu contrato inteligente lee esa respuesta a través de una dirección proxy, de modo que la implementación subyacente puede actualizarse sin cambiar tu integración.
El tipo de feed más común es un feed de precios (por ejemplo, un activo cotizado en USD), pero el mismo modelo de entrega también transporta datos de prueba de reservas, valores liquidativos netos, curvas de tasas de interés y paquetes multivariable. La propiedad clave es que los datos ya están onchain: leerlos es una llamada normal a un contrato, no una solicitud API, y solo cuesta el gas de esa llamada.
Este artículo se centra en la mecánica de integración que importa en producción: cómo se comporta el modelo basado en push, cómo leer un feed correctamente, cómo detectar respuestas obsoletas y cómo verificar tu integración de forma reproducible. No intenta enumerar cada feed o red; usa el directorio oficial de feeds para ese inventario.
- Basado en push: la DON escribe actualizaciones onchain según un intervalo de heartbeat y cuando el valor se desvía más allá de un umbral.
- Patrón proxy: llamas a una dirección proxy estable; el agregador detrás de ella puede ser reemplazado.
- Lecturas en una sola llamada: sin solicitud offchain, sin callback, sin suposición de confianza adicional en el momento de la lectura.
Supuestos y requisitos previos
Los ejemplos asumen una cadena compatible con EVM, un conjunto de herramientas Solidity (Foundry o Hardhat) y acceso a un endpoint RPC para la red de destino. Necesitarás la dirección proxy del feed específico que pretendes leer; las direcciones son específicas de cada red y deben tomarse del directorio oficial de feeds de Chainlink en lugar de copiarse de otra cadena.
Para lecturas offchain necesitarás Node.js con ethers o viem, o Python con web3.py. Para cualquier script que envíe transacciones necesitarás una cuenta con fondos; mantén la clave privada en una variable de entorno y nunca la confirmes en el repositorio. Si aún estás eligiendo un proveedor RPC para tu cadena de destino, el Asistente RPC puede ayudarte a elegir un endpoint concreto, y la guía de endpoints RPC multicadena explica cómo estructurar el acceso entre redes.
El modelo de feed basado en push y sus disparadores de actualización
Un Data Feed no se actualiza en cada tick de precio. La DON publica una nueva ronda cuando transcurre un intervalo de heartbeat o cuando el valor observado se mueve más allá de un umbral de desviación configurado. Cada actualización crea una nueva ronda con un ID, una respuesta y una marca de tiempo de actualización, y el agregador expone la última ronda a través de una interfaz estándar.
Este diseño tiene dos consecuencias que debes manejar explícitamente. Primero, la respuesta puede ser más antigua que el precio de mercado actual hasta por el intervalo de heartbeat, por lo que cualquier lógica que dependa de la frescura necesita una comprobación de obsolescencia. Segundo, como las actualizaciones se basan en eventos, el tiempo entre rondas no es constante; un mercado tranquilo puede producir largos intervalos aunque el feed esté saludable.
La interfaz que consume tu contrato es AggregatorV3Interface, que expone latestRoundData, getRoundData, decimals y description. El valor de decimals es por feed y no siempre es 8, así que nunca lo codifiques de forma fija.
Leer un feed desde un contrato Solidity
El consumidor a continuación lee la última ronda, valida que la respuesta sea positiva y fresca, y normaliza el valor a 18 decimales. Reemplaza FEED_ADDRESS con la dirección proxy para tu red y activo, y establece MAX_STALENESS en un valor derivado del heartbeat documentado del feed en lugar de una suposición.
La comprobación de answeredInRound es una medida defensiva: en algunas implementaciones históricas de agregadores podía quedar por detrás de roundId, y tratar eso como obsoleto evita actuar sobre una ronda incompleta.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
interface AggregatorV3Interface {
function decimals() external view returns (uint8);
function description() external view returns (string memory);
function latestRoundData()
external
view
returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound);
}
contract FeedReader {
AggregatorV3Interface public immutable feed;
uint256 public immutable maxStaleness;
error StaleAnswer(uint256 updatedAt);
error NonPositiveAnswer(int256 answer);
constructor(address feedAddress, uint256 maxStalenessSeconds) {
feed = AggregatorV3Interface(feedAddress);
maxStaleness = maxStalenessSeconds;
}
function latestPrice18() external view returns (uint256) {
(uint80 roundId, int256 answer, , uint256 updatedAt, uint80 answeredInRound) = feed.latestRoundData();
if (answer <= 0) revert NonPositiveAnswer(answer);
if (updatedAt == 0 || block.timestamp - updatedAt > maxStaleness) revert StaleAnswer(updatedAt);
if (answeredInRound < roundId) revert StaleAnswer(updatedAt);
uint8 d = feed.decimals();
return uint256(answer) * (10 ** (18 - d));
}
}Proteger despliegues L2 con feeds de disponibilidad del secuenciador
En redes Layer 2, un feed puede parecer fresco mientras el secuenciador acaba de volver a estar en línea, porque las transacciones en cola durante una interrupción pueden ejecutarse en ráfaga. Chainlink publica L2 Sequencer Uptime Feeds para este caso. El patrón consiste en leer el feed de disponibilidad, confirmar que el secuenciador ha estado activo durante un período de gracia y solo entonces confiar en el feed de precios.
El período de gracia existe porque es posible que los precios no se hayan actualizado inmediatamente después de que el secuenciador se reanudara. Elige un período de gracia que refleje la rapidez con la que tu feed objetivo se actualiza tras una interrupción y documenta la elección. La misma protección debe envolver cada lectura de precio en la ruta de código afectada, no solo el punto de entrada.
interface AggregatorV3Interface {
function latestRoundData() external view returns (uint80, int256, uint256, uint256, uint80);
}
contract SequencerGuard {
AggregatorV3Interface public immutable uptimeFeed;
uint256 public constant GRACE_PERIOD = 30 minutes;
constructor(address uptimeFeedAddress) {
uptimeFeed = AggregatorV3Interface(uptimeFeedAddress);
}
function sequencerHealthy() public view returns (bool) {
(, int256 answer, uint256 startedAt, , ) = uptimeFeed.latestRoundData();
if (answer != 0) return false; // 0 = up, 1 = down
return block.timestamp - startedAt > GRACE_PERIOD;
}
}Leer feeds offchain con ethers
Las lecturas offchain son útiles para paneles de control, bots de liquidación y comprobaciones previas a operaciones. El script a continuación lee la última ronda e imprime el precio normalizado, el ID de ronda y la antigüedad de la respuesta. Establece RPC_URL en el endpoint de tu proveedor y FEED_ADDRESS en el proxy para tu red.
Como la lectura es una simple eth_call, funciona contra cualquier endpoint RPC estándar. Si estás comparando proveedores por latencia o profundidad de archivo, la descripción general de la API RPC de Ethereum y la página de precios describen qué evaluar.
import { ethers } from "ethers";
const ABI = [
"function decimals() view returns (uint8)",
"function latestRoundData() view returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound)"
];
async function main() {
const provider = new ethers.JsonRpcProvider(process.env.RPC_URL);
const feed = new ethers.Contract(process.env.FEED_ADDRESS, ABI, provider);
const [decimals, round] = await Promise.all([
feed.decimals(),
feed.latestRoundData()
]);
const price = Number(round.answer) / 10 ** Number(decimals);
const ageSeconds = Math.floor(Date.now() / 1000) - Number(round.updatedAt);
console.log({ price, roundId: round.roundId.toString(), ageSeconds });
}
main().catch((e) => { console.error(e); process.exit(1); });Un procedimiento de verificación reproducible
Antes de confiar en una integración, verifícala contra los metadatos del propio feed en lugar de contra un precio que viste en otro lugar. El procedimiento a continuación produce una pequeña tabla de resultados que puedes volver a ejecutar en cualquier red y comparar a lo largo del tiempo. No requiere ningún resultado numérico específico; el punto es que las columnas estén pobladas y sean internamente consistentes.
Ejecuta el script offchain tres veces con unos minutos de diferencia y registra las siguientes columnas en una tabla Markdown: red, dirección del feed, descripción, decimales, roundId, respuesta, updatedAt, antigüedad en segundos y si la antigüedad está por debajo de tu umbral de obsolescencia. Un feed saludable muestra un roundId que no disminuye monotónicamente, una respuesta positiva y una antigüedad por debajo del heartbeat más un pequeño margen. Si roundId no avanza entre ejecuciones, eso es esperado en un mercado tranquilo; no es por sí mismo una falla.
Luego llama a tu consumidor Solidity en un fork o testnet y confirma que latestPrice18 devuelve el mismo valor normalizado que el script offchain. Una discrepancia generalmente indica un error en la suposición de decimales o una dirección de feed diferente a la prevista.
- Confirma que la descripción coincide con el par de activos que esperas.
- Confirma que los decimales coinciden con el valor que usaste para normalizar.
- Confirma que updatedAt no es cero y que la antigüedad está dentro de tu umbral.
- Confirma que el consumidor onchain y el script offchain coinciden.
Solución de problemas comunes de integración
La mayoría de los errores de integración de Data Feeds se engloban en un pequeño conjunto de categorías. La siguiente tabla relaciona síntomas con causas probables y la comprobación que confirma cada una.
Si una lectura se revierte sin datos devueltos, la dirección probablemente no es un contrato en esa cadena, lo que normalmente significa que la dirección del feed se copió de una red diferente. Si el precio está desviado por órdenes de magnitud, el valor de decimales se codificó de forma fija o se aplicó dos veces. Si tu protocolo se comporta de forma extraña solo en una L2, verifica si la protección del secuenciador está presente y si el período de gracia es lo suficientemente largo para tu feed.
- Reversión en la llamada: verifica que la dirección sea un contrato en la cadena de destino usando eth_getCode.
- Precio escalado incorrectamente: lee decimals() y normaliza una vez.
- Datos obsoletos aceptados: añade una comprobación de updatedAt vinculada al heartbeat documentado.
- Ráfaga en L2 tras una interrupción: añade una protección de disponibilidad del secuenciador con un período de gracia.
- Desajuste de ronda: compara answeredInRound con roundId.
Limitaciones y ventajas y desventajas
Los Data Feeds basados en push optimizan para la composabilidad onchain y costos de lectura predecibles, no para latencia mínima. Si tu aplicación necesita actualizaciones subsegundo o granularidad por bloque, los Data Streams basados en pull son el producto adecuado, pero requieren un flujo de verificación diferente y no son un reemplazo directo para una llamada a latestRoundData.
Los umbrales de heartbeat y desviación son por feed y pueden cambiar; una constante de obsolescencia que es correcta hoy puede ser incorrecta después de una actualización de configuración del feed. Trata el umbral como configuración, monitorízalo y alerta cuando la antigüedad observada se acerque a él. También recuerda que la respuesta de un feed es un agregado, no una garantía sobre ningún lugar individual; para la valoración de garantías, combínala con tus propios parámetros de riesgo en lugar de tratarla como verdad absoluta.
Finalmente, la capa RPC importa para las lecturas offchain. Un nodo retrasado puede devolver un bloque más antiguo y, por lo tanto, una ronda más antigua. Si la frescura es crítica, lee en un número de bloque específico y compara entre proveedores, y considera un nodo dedicado para una latencia de cabeza consistente.
Próximos pasos para una integración en producción
Comienza enumerando cada ruta de código que consume un precio y confirma que cada una tiene una comprobación de obsolescencia y, en L2, una protección del secuenciador. Luego añade monitoreo que registre roundId y antigüedad de forma programada para que puedas ver el comportamiento del feed antes de un incidente. Si operas tu propia infraestructura, un nodo dedicado reduce la variación en la latencia de lectura offchain; la descripción general de nodos dedicados explica las ventajas y desventajas.
Para equipos que construyen en varias cadenas, estandariza las direcciones de feeds y los decimales en un archivo de configuración por red y cárgalo en el momento del despliegue en lugar de codificarlo de forma fija. El directorio de redes enumera las cadenas compatibles, y la página de precios RPC te ayuda a dimensionar la capacidad del endpoint para el volumen de lectura que generan tus monitores. Cuando estés listo para elegir un endpoint concreto para una cadena específica, pide una recomendación al Asistente RPC en lugar de adivinar.