ChainScore Labs
LABS
Guides

Using RedStone Oracles in DeFi Applications

Chainscore © 2025
concepts

Core RedStone Oracle Concepts

Foundational principles and mechanisms that power the RedStone decentralized oracle system.

01

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.

02

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.

03

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.

04

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.

05

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.

1

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-connector or yarn add @redstone-finance/evm-connector.
  • Sub-step 2: In your contract, add the import statements: import "@redstone-finance/evm-connector/contracts/mocks/PriceAware.sol"; and import "@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-services and core) to avoid unnecessary mock dependencies.

2

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 bytes32 array 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.
solidity
contract 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.

3

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 the signer matches a known trusted address, like 0x1Bf2b6d1c7f9B5A5a30C0A6aA7F0a5B5a5A5a5A5.
  • Sub-step 2: Implement isTimestampValid(uint256 receivedTimestampMilliseconds). Compare it to block.timestamp, allowing a tolerance (e.g., 3 minutes).
  • Sub-step 3: Keep these functions pure or view to minimize gas costs during validation.
solidity
function 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.

4

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.
solidity
function 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 _extractPrice will revert if the validation checks fail or if the requested dataFeedId is not found in the provided calldata.

5

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 MockDataService helper 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 / NetworkRedStone 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.

1

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 signature and dataPackage fields 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 timestamp is recent (e.g., within the last 5 minutes) to ensure you are using fresh data.

2

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 bytes32 dataFeedId: 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 value and timestamp are converted to BigNumber or equivalent to prevent precision loss.

3

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, and v components. The last byte is v.
  • Sub-step 2: Call the ecrecover or 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.splitSignature method automatically handles the v value conversion from 27/28 to 0/1 for the recovery param.

4

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 recoveredAddress is 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.

5

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 signature in 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

RedStone uses a decentralized data provider network where multiple independent nodes sign price data. The core security model relies on cryptographic signatures and on-chain verification. Each data feed is signed by a provider's private key, and the smart contract verifies these signatures against a whitelist of public keys. For high-value transactions, you should require a minimum number of unique signatures (e.g., 3 out of 5 trusted providers) to achieve consensus, making it economically infeasible to manipulate the feed without collusion.