Разработка кастомного оракула
Chainlink и Pyth закрывают 95% потребностей в ценовых данных. Но есть случаи где готовое решение не подходит: специфический актив без coverage, нестандартный тип данных, требование минимального доверия к третьим сторонам, или необходимость специфической агрегации. Тогда разрабатывают кастомный оракул.
Когда нужен кастомный оракул
- Неликвидный или нишевый актив: Chainlink не добавляет feed для токена с $1M TVL
- Off-chain данные: спортивные результаты, погода, страховые индексы — специфичны
- Частные данные: корпоративные метрики, TradFi данные с лицензионными ограничениями
- Агрегация по-особому: медиана по конкретному набору источников, VWAP за кастомный период
- On-chain данные с верификацией: данные из другой сети через ZK proofs
Архитектура кастомного оракула
Компоненты
External Data Sources
↓ (HTTP/WebSocket)
Oracle Node Network
├── Node 1 (fetches, signs, submits)
├── Node 2 (fetches, signs, submits)
└── Node N (fetches, signs, submits)
↓ (signed reports)
On-chain Aggregator Contract
├── Collect reports
├── Verify signatures
├── Aggregate (median/TWAP)
└── Publish result
↓
Consumer Contracts
On-chain Aggregator
contract CustomOracle {
struct Report {
uint256 value;
uint256 timestamp;
bytes signature;
}
mapping(address => bool) public trustedNodes;
uint256 public requiredReports; // минимум M из N нод
uint256 public latestValue;
uint256 public latestTimestamp;
function submitReport(uint256 value, uint256 timestamp, bytes calldata sig) external {
require(trustedNodes[msg.sender], "Not trusted node");
require(timestamp > block.timestamp - 300, "Report too old");
// Верификация подписи
bytes32 messageHash = keccak256(abi.encodePacked(value, timestamp));
address signer = recoverSigner(messageHash, sig);
require(signer == msg.sender, "Invalid signature");
_pendingReports[roundId][msg.sender] = Report(value, timestamp, sig);
if (_pendingReportCount[roundId] >= requiredReports) {
_finalizeRound(roundId);
}
}
function _finalizeRound(uint256 roundId) internal {
uint256[] memory values = _collectValues(roundId);
latestValue = _median(values);
latestTimestamp = block.timestamp;
emit NewRound(roundId, latestValue);
}
}
Oracle Node
Off-chain нода которая периодически:
- Запрашивает данные с источников
- Агрегирует (median, weighted average)
- Подписывает ECDSA
- Отправляет транзакцию в агрегатор
import asyncio
import aiohttp
from eth_account import Account
from web3 import Web3
class OracleNode:
async def fetch_price(self, source_url: str) -> float:
async with aiohttp.ClientSession() as session:
async with session.get(source_url) as response:
data = await response.json()
return float(data['price'])
async def run_round(self):
# Получить цены из нескольких источников
prices = await asyncio.gather(*[
self.fetch_price(url) for url in self.sources
])
# Медиана
median_price = sorted(prices)[len(prices) // 2]
# Подписать и отправить
timestamp = int(time.time())
message = Web3.solidity_keccak(['uint256', 'uint256'],
[int(median_price * 1e8), timestamp])
signed = self.account.sign_message(message)
await self.oracle_contract.functions.submitReport(
int(median_price * 1e8), timestamp, signed.signature
).transact()
Verifiable Oracle с ZK Proofs
Для высоких требований к trustlessness — ZK-based oracle. Нода предоставляет ZK proof того что данные корректно получены и агрегированы.
DECO (Chainlink Labs research): TLS-based ZK proofs. Нода доказывает что получила данные с конкретного HTTPS endpoint без раскрытия TLS session.
zkOracle концепция: вычисление агрегации доказывается через zkSNARK. Верификатор on-chain дешево проверяет proof вместо доверия подписи.
На практике это экспериментально и дорого по вычислениям — но развивающееся направление.
Манипуляция оракулами: защита
Flash loan + oracle manipulation: злоумышленник берёт flash loan, двигает цену на DEX (если оракул использует spot price), эксплуатирует протокол по манипулированной цене.
Защиты:
- TWAP вместо spot: Time-Weighted Average Price за N минут сложнее манипулировать
- Multiple sources: медиана по нескольким биржам, не только Uniswap
- Circuit breakers: отказ при отклонении от предыдущей цены > X%
- Volume-weighted: игнорировать источники с аномально малым объёмом
Update frequency и front-running: если оракул обновляется раз в час — арбитражники видят запланированное обновление в mempool и front-runят. Частые обновления или private mempool снижают этот риск.
Разработка кастомного оракула: 4-12 недель в зависимости от числа источников, сетей и требований к trustlessness.







