Foundational principles and mechanisms that power the RedStone decentralized oracle system.
Using RedStone Oracles in DeFi Applications
Core RedStone Oracle Concepts
Data Feeds & Signers
Data feeds are the core information streams, like ETH/USD price. They are supplied by a decentralized network of signers, independent entities that fetch, sign, and broadcast data. Signers stake tokens to ensure honesty. This model removes single points of failure, providing robust and censorship-resistant data for DeFi protocols.
Data Packages & Timestamps
Data packages are the atomic units of information, containing a data point, its timestamp, and the signer's cryptographic signature. The precise timestamp is critical for verifying data freshness and preventing replay attacks. Protocols can check timestamps to ensure they are using recent, valid data, which is essential for accurate pricing and liquidation events.
On-Demand Data Pull
Instead of continuously pushing data on-chain, RedStone uses an on-demand pull model. Data is stored off-chain in a decentralized cache (like Arweave). Smart contracts request data only when needed, attaching signed data packages as call data. This drastically reduces gas costs, enabling high-frequency data updates and support for thousands of assets.
Token-Curated Signer Lists
The integrity of the oracle relies on a token-curated registry of signers. STONE token holders vote to add or remove signers based on performance and reliability. This decentralized governance ensures the network adapts, maintaining a high-quality, Sybil-resistant set of data providers without relying on a central authority.
Aggregation & Deviation
Aggregation combines data from multiple signers into a single robust value, typically using a median to filter out outliers. Deviation thresholds are predefined limits for how much a new data point can differ from the median before being rejected. This mechanism protects against flash crashes and manipulation, delivering a stable and reliable price feed.
Data Integration Patterns
Understanding Oracle Data Feeds
Oracles are critical infrastructure that connect off-chain data, like asset prices, to on-chain smart contracts. RedStone provides a modular design where data is signed off-chain and delivered on-demand, reducing gas costs.
Key Integration Models
- Push Model: The oracle periodically updates a data feed contract (e.g., a price feed) that other contracts can read. This is common for frequently used data like ETH/USD.
- Pull Model: Data is fetched only when needed by a transaction. The user or contract provides the signed data payload, which is then verified on-chain. This saves gas for less frequent operations.
- Hybrid Approach: Combines both; a core push model maintains essential feeds, while a pull mechanism handles niche or custom data requests.
Example Use Case
A lending protocol like Aave primarily uses a push model for its core asset prices to ensure immediate liquidity calculations. However, for a less liquid collateral asset, it might use a pull model to fetch a price only during a liquidation event, optimizing overall gas expenditure for users.
Implement the RedStone Core Contract
Process for integrating the RedStone Core contract to fetch signed price data on-chain.
Install and Import the Core Library
Add the RedStone Core library to your project and import the necessary contracts.
Detailed Instructions
Start by installing the @redstone-finance/evm-connector package via npm or yarn. This library contains the core contracts and helper functions for on-chain data retrieval. In your Solidity file, import the PriceAware abstract contract and the LibRedStone library. The PriceAware contract provides the foundational logic for processing RedStone data feeds, while LibRedStone offers utility functions for data extraction.
- Sub-step 1: Run
npm install @redstone-finance/evm-connectororyarn add @redstone-finance/evm-connector. - Sub-step 2: In your contract, add the import statements:
import "@redstone-finance/evm-connector/contracts/mocks/PriceAware.sol";andimport "@redstone-finance/evm-connector/contracts/mocks/LibRedStone.sol";. - Sub-step 3: Ensure your development environment (e.g., Hardhat, Foundry) is configured to handle the library's dependencies.
solidity// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import "@redstone-finance/evm-connector/contracts/mocks/PriceAware.sol"; import "@redstone-finance/evm-connector/contracts/mocks/LibRedStone.sol";
Tip: For production, consider using the non-mock versions (
data-servicesandcore) to avoid unnecessary mock dependencies.
Inherit from PriceAware and Define Data Feed IDs
Make your contract inherit the PriceAware base contract and specify the data feeds you need.
Detailed Instructions
Your main contract must inherit from the PriceAware abstract contract. This inheritance provides access to the internal _extractPrice function and the required isSignerAuthorized and isTimestampValid validators. You must also define the data feed IDs for the assets you want to query. These are UTF-8 strings like "ETH" or "BTC" that correspond to RedStone's data feed identifiers.
- Sub-step 1: Declare your contract and inherit from
PriceAware:contract MyDeFiApp is PriceAware { ... }. - Sub-step 2: Define a constant
bytes32array for your required data feeds, e.g.,bytes32[] public dataFeedIds = [bytes32("ETH"), bytes32("BTC")];. - Sub-step 3: Optionally, create a mapping or function to map these IDs to your internal logic, ensuring consistency.
soliditycontract MyLendingProtocol is PriceAware { // Define the data feeds this contract will consume bytes32[] public dataFeedIds = [bytes32("ETH"), bytes32("AVAX"), bytes32("REDSTONE")]; // Contract logic continues... }
Tip: You can find the full list of supported data feed IDs (e.g.,
"ARB","LINK") in RedStone's official documentation or data portal.
Implement the Required Validation Functions
Override the abstract validation functions to secure the oracle data.
Detailed Instructions
The PriceAware contract requires you to implement two critical view functions: isSignerAuthorized and isTimestampValid. These functions are called internally to validate the signed data package attached to the transaction's calldata. isSignerAuthorized must return true for trusted signer addresses, while isTimestampValid must ensure the data's timestamp is acceptably recent, preventing stale price attacks.
- Sub-step 1: Implement
isSignerAuthorized(address signer). Check if thesignermatches a known trusted address, like0x1Bf2b6d1c7f9B5A5a30C0A6aA7F0a5B5a5A5a5A5. - Sub-step 2: Implement
isTimestampValid(uint256 receivedTimestampMilliseconds). Compare it toblock.timestamp, allowing a tolerance (e.g., 3 minutes). - Sub-step 3: Keep these functions
pureorviewto minimize gas costs during validation.
solidityfunction isSignerAuthorized(address _signer) public pure override returns (bool) { // Example: Authorize a specific RedStone signer return _signer == 0x1Bf2b6d1c7f9B5A5a30C0A6aA7F0a5B5a5A5a5A5; } function isTimestampValid(uint256 _receivedTimestampMilliseconds) public view override returns (bool) { // Accept data if it's no older than 3 minutes (180000 ms) uint256 currentTimestamp = block.timestamp * 1000; return currentTimestamp - _receivedTimestampMilliseconds <= 180000; }
Tip: For production, maintain an updatable set of authorized signers rather than a single hardcoded address to enhance security and flexibility.
Fetch and Use Price Data in Your Functions
Call the internal _extractPrice function to retrieve verified price values.
Detailed Instructions
Within your contract's business logic (e.g., a function to calculate collateral value), you must call the internal _extractPrice function. This function takes a bytes32 dataFeedId as an argument and returns a uint256 representing the price, scaled to 8 decimals by default. The price data is extracted from the signed calldata appended to your transaction, which is provided by off-chain RedStone data providers.
- Sub-step 1: In your function, declare the price variable:
uint256 ethPrice = _extractPrice(bytes32("ETH"));. - Sub-step 2: Use the returned value in your logic, e.g.,
uint256 collateralValue = (ethPrice * collateralAmount) / 10**8;. - Sub-step 3: Ensure any transaction calling this function includes the signed data payload, typically handled by a front-end using the RedStone SDK.
solidityfunction calculateBorrowLimit(uint256 _ethCollateralAmount) external returns (uint256) { // Fetch the current ETH/USD price uint256 ethPrice = _extractPrice(bytes32("ETH")); // Calculate collateral value (assuming price has 8 decimals) uint256 collateralValue = (ethPrice * _ethCollateralAmount) / 10**8; // Apply a 70% loan-to-value ratio uint256 borrowLimit = (collateralValue * 70) / 100; return borrowLimit; }
Tip: Remember that
_extractPricewill revert if the validation checks fail or if the requesteddataFeedIdis not found in the provided calldata.
Test the Integration with a Mock Data Service
Write and run tests to verify price fetching and validation work correctly.
Detailed Instructions
Create comprehensive tests using a framework like Hardhat or Foundry. You will need to simulate the off-chain data provision by using RedStone's mock data service or by manually constructing a valid signed data package. The test should verify that your contract correctly extracts prices, rejects unauthorized signers, and discards stale timestamps.
- Sub-step 1: Import and use the
MockDataServicehelper from the RedStone connector in your test file. - Sub-step 2: Deploy your contract and call the
calculateBorrowLimit(or similar) function, passing the mock data as calldata. - Sub-step 3: Write assertions to check the returned price values are as expected and that transactions revert when invalid data is supplied.
javascript// Example Hardhat test snippet const { MockDataService } = require('@redstone-finance/evm-connector'); describe('MyDeFiApp', function() { it('Should correctly extract the ETH price', async function() { const mockData = await MockDataService.getMockDataForOneSigner(["ETH"], [2500000000]); // 2500 USD with 8 decimals await myDeFiApp.calculateBorrowLimit(ethers.utils.parseEther("1"), mockData); // Add assertion for expected result }); });
Tip: Test edge cases like zero values, extremely old timestamps, and signatures from unauthorized addresses to ensure your validation logic is robust.
Available Data Feeds and Networks
Comparison of RedStone Oracle data feed types and supported blockchain networks.
| Data Feed / Network | RedStone Core (Classic) | RedStone On-Chain (Relayers) | RedStone X (Data Services) |
|---|---|---|---|
Primary Update Mechanism | Off-chain signed data pushed by providers | On-chain price updates via relayers | Pull-based, on-demand data fetching |
Gas Cost for Consumer | ~50k-80k gas (signature verification) | ~100k-150k gas (full storage) | ~25k-40k gas (optimized calldata) |
Data Freshness (Update Frequency) | 1-2 minutes (per provider discretion) | As needed (triggered by deviation/time) | Real-time (fetched at transaction time) |
Supported Asset Types | Crypto, Forex, Commodities, Equities | Primarily major crypto assets (BTC, ETH, etc.) | Custom, including NFTs, RWAs, and derivatives |
Number of Supported Blockchains | 40+ (EVM, L2s, Cosmos, Solana) | 15+ (EVM-compatible focus) | All (via API/gateway, chain-agnostic) |
Decentralization Level | Decentralized data sourcing, single provider sig | Decentralized relayers, centralized data aggregation | Configurable (can use decentralized provider set) |
Typical Use Case | General DeFi protocols (lending, derivatives) | High-frequency trading, perpetuals, options | Custom dApps, insurance, gaming, analytics |
Verify Data Signatures Off-Chain
Process for cryptographically verifying data feed signatures before on-chain submission to ensure integrity and reduce gas costs.
Retrieve the Data Package from the RedStone API
Fetch the signed data payload containing the price feed you need from the RedStone public API.
Detailed Instructions
Query the RedStone API endpoint for the specific data feed symbol (e.g., ETH, BTC) and provider (e.g., redstone, redstone-rapid). The API returns a Data Package—a JSON object containing the data point, timestamp, and most importantly, the ECDSA signature and the signer's address. Use a standard HTTP client like fetch in Node.js or requests in Python. Specify the data-service-id header to target the correct data service. This step is performed entirely off-chain, allowing you to inspect and validate the data before any blockchain transaction.
- Sub-step 1: Construct the API URL:
https://api.redstone.finance/prices?symbol=ETH&provider=redstone&limit=1 - Sub-step 2: Make the HTTP GET request and parse the JSON response.
- Sub-step 3: Extract the
signatureanddataPackagefields from the first item in the returned array.
javascript// Example using fetch in Node.js const response = await fetch('https://api.redstone.finance/prices?symbol=ETH&provider=redstone&limit=1'); const data = await response.json(); const dataPackage = data[0]; const signature = dataPackage.signature; const value = dataPackage.value; const timestamp = dataPackage.timestamp;
Tip: Always verify the
timestampis recent (e.g., within the last 5 minutes) to ensure you are using fresh data.
Reconstruct the Signed Message Hash
Hash the precise message that was signed by the RedStone oracle node using the same structured format.
Detailed Instructions
The signature is generated over a specific, deterministic message derived from the data package. You must reconstruct this message exactly. For RedStone, the signed message is the keccak256 hash of a tightly packed ABI encoding of the data point's properties. The required fields are the value (as a uint256), timestamp (as a uint256), and the dataFeedId (as a bytes32). The dataFeedId is the keccak256 hash of the feed's symbol string (e.g., ETH). Use a library like ethers.js or web3.js to perform the hashing and packing. Any deviation in encoding will result in a different hash and cause signature verification to fail.
- Sub-step 1: Convert the data feed symbol (e.g., 'ETH') to a
bytes32dataFeedId:keccak256(abi.encodePacked('ETH')). - Sub-step 2: ABI encode the parameters:
abi.encodePacked(value, timestamp, dataFeedId). - Sub-step 3: Hash the encoded bytes using keccak256 to produce the
messageHash.
javascript// Example using ethers.js const { ethers } = require('ethers'); const dataFeedId = ethers.utils.keccak256(ethers.utils.toUtf8Bytes('ETH')); const packedMessage = ethers.utils.solidityPack( ['uint256', 'uint256', 'bytes32'], [value, timestamp, dataFeedId] ); const messageHash = ethers.utils.keccak256(packedMessage);
Tip: Ensure the
valueandtimestampare converted toBigNumberor equivalent to prevent precision loss.
Recover the Signer's Address from the Signature
Use the ECDSA recovery function to derive the public address that created the signature for the given message hash.
Detailed Instructions
Apply the ecrecover operation, which is the standard method for extracting an Ethereum address from an ECDSA signature. The signature from the API is typically a hex string representing the r, s, and v components of the ECDSA signature. The v is the recovery id (27 or 28). Pass the messageHash, along with the v, r, and s signature components, into the recovery function. The output will be the 20-byte Ethereum address of the signer. This step proves that the holder of the private key corresponding to a known oracle address authorized this specific data point.
- Sub-step 1: Split the hex signature string into its
r,s, andvcomponents. The last byte isv. - Sub-step 2: Call the
ecrecoveror equivalent function provided by your crypto library. - Sub-step 3: Compare the recovered address to the expected, trusted signer address (e.g.,
0x1...for a RedStone core node).
javascript// Example using ethers.js const sig = ethers.utils.splitSignature(signature); // Splits into { r, s, v, recoveryParam } const recoveredAddress = ethers.utils.recoverAddress(messageHash, sig); console.log('Recovered signer:', recoveredAddress);
Tip: The
ethers.utils.splitSignaturemethod automatically handles thevvalue conversion from 27/28 to 0/1 for the recovery param.
Validate Against the Authorized Signer Set
Check that the recovered address is part of the predefined set of trusted oracle signers for the data feed.
Detailed Instructions
Verification is not complete until you confirm the signer is authorized. You must check the recovered address against a whitelist of authorized signers. This list is specific to the data service (e.g., redstone or redstone-rapid) and is publicly available, often stored in a configuration file or on-chain in the oracle contract. For robust validation, you should require signatures from multiple authorized signers (multi-signature verification) or verify that the single signer's address matches one of several possible valid addresses. This step prevents accepting data signed by malicious or compromised nodes.
- Sub-step 1: Obtain the list of authorized signer addresses for your chosen data service (e.g., from RedStone's documentation or on-chain via a view function).
- Sub-step 2: Check if the
recoveredAddressis included in this list. - Sub-step 3: For higher security, repeat steps 1-3 for multiple independent data packages and ensure a minimum threshold of unique, authorized signers (e.g., 2 out of 3) have signed the same value.
javascript// Example validation logic const authorizedSigners = [ '0x1C0D72B5aDDD5a8D358857dBC39D5a3F1BBea1dF', '0x2F344D6c87b2E1b6c6C8dD0dF8c7a1C5E1b2A3C4', '0x3A456E9c8f7B2D1e0F3C4B5a6D7E8F9A0B1C2D3E' ]; const isAuthorized = authorizedSigners.includes(recoveredAddress.toLowerCase()); if (!isAuthorized) { throw new Error('Data signed by unauthorized address'); }
Tip: Store the authorized signer list in a secure, updatable configuration to adapt to changes in the oracle network.
Prepare the Verified Data for On-Chain Use
Format the validated data and signature into the structure required by the on-chain oracle consumer contract.
Detailed Instructions
After successful off-chain verification, you need to package the data for the final on-chain transaction. The consumer contract (e.g., a PriceFeed or DataFeed contract) expects specific calldata. This typically includes the data value, timestamp, and the original signature bytes. Some contracts may require the dataFeedId as well. The exact ABI encoding must match the function signature of the target contract's update method, such as updatePrice(bytes32 dataFeedId, uint256 value, uint256 timestamp, bytes calldata signature). This preparation ensures the on-chain verification (which repeats ecrecover) will succeed, as it uses the identical data package.
- Sub-step 1: Ensure all values (
value,timestamp,dataFeedId) are in the correct hex or numeric format for the contract call. - Sub-step 2: Keep the original
signaturein its exact bytes form as retrieved from the API. - Sub-step 3: Encode the function call using the contract ABI, passing the verified parameters.
solidity// Example of the typical on-chain function you will call interface IRedstoneConsumer { function updatePrice( bytes32 dataFeedId, uint256 value, uint256 timestamp, bytes calldata signature ) external; }
Tip: Performing this off-chain verification first saves significant gas by preventing failed transactions due to invalid signatures.
Security and Operational Considerations
Development Resources and References
Ready to Start Building?
Let's bring your Web3 vision to life.
From concept to deployment, ChainScore helps you architect, build, and scale secure blockchain solutions.