Skip to main content

⚙️ Core Model

Fetching prices on-demand

This is our basic operating model when the data is automatically appended to user transaction.

In Prod

Core model is the most mature way to use RedStone, battle tested in production, protecting >$100M TVL of DeFi Protocols (not all listed yet) across multiple mainnets. The price feeds have been injected to more than ~50K transactions.

Installation

Install @redstone-finance/evm-connector from NPM registry

Hardhat

  yarn add @redstone-finance/evm-connector

Foundry

Foundry installs dependencies using git submodules. Thus additional steps are needed to install dependencies.

In foundry project:

  1. Install @redstone-finance/evm-connector - it will install current code from main branch
forge install redstone-finance/redstone-oracles-monorepo
  1. Install @OpenZeppelin contracts (dependency of @redstone-finance/evm-connector) - it will install current code from main branch
forge install OpenZeppelin/openzeppelin-contracts@v4.9.5
  1. Add libraries to remappings.txt
echo "@redstone-finance/evm-connector/dist/contracts/=lib/redstone-oracles-monorepo/packages/evm-connector/contracts/
@openzeppelin/contracts=lib/openzeppelin-contracts/contracts/" >> remappings.txt

Usage

TLDR;

You need to do 2 things:

  1. Adjust your smart contracts to include the libraries responsible for data extraction and verification
  2. Adjust Javascript code of your dApp to inject the additional payload with data feeds (otherwise you will get smart contract errors).

1. Adjust your smart contracts

Heads up
  1. Our contracts require solidity > 0.8.4. If your code is written in an older version please use the Manual Payload.
  2. If you work with 3rd party aggregators, make sure that they also support passing the additional payload.
  3. Please don't use Remix to test RedStone oracles, as Remix does not support modifying transactions in the way that the evm-connector does
  4. We strongly recommend having some upgradability mechanism for your contracts (it can be based on multisig or DAO). This way, you can quickly replace data providers in case of any issues.

You need to apply a minimum change to the source code to enable smart contract to access data. Your contract needs to extend one of our base contracts, depending on which data service are you going to use.

List of base contracts with data services
Base ContractData service with the list of feedsStatus
MainDemoConsumerBase.solredstone-main-demoDemo
RapidDemoConsumerBase.solredstone-rapid-demoDemo
StocksDemoConsumerBase.solredstone-stocks-demoDemo
AvalancheDataServiceConsumerBase.solredstone-avalanche-prodProduction
PrimaryProdDataServiceConsumerBase.solredstone-primary-prodProduction

💡 Note: Service with Production status have got multiple nodes deployed and are professionally monitored.

import "@redstone-finance/evm-connector/contracts/data-services/MainDemoConsumerBase.sol";

contract YourContractName is MainDemoConsumerBase {
...
}

You should pass the data feed id converted to bytes32.

  uint256 ethPrice = getOracleNumericValueFromTxMsg(bytes32("ETH"));

For all the supported feeds we provide UI with charts and historical data

💡 Note: You can also override the following functions (do it at your own risk):

  • isTimestampValid(uint256 receivedTimestamp) returns (bool) - to enable custom logic of timestamp validation. You may specify a shorter delay to accept only the most recent price fees. However, on networks with longer block times you may extend this period to avoid rejecting too many transactions.

  • aggregateValues(uint256[] memory values) returns (uint256) - to enable custom logic of aggregating values from different providers (by default this function takes the median value). For example, you may request values from providers to be strictly equal while dealing with discrete numbers.

  • getAuthorisedSignerIndex(address _signerAddress) returns (uint256) - to whitelist additional signers or remove corrupted ones.

  • getUniqueSignersThreshold() returns (uint256) - to modify number of required signers. The higher number means greater reliability but also higher gas costs.

2. Adjust Javascript code of your dApp

You should also update the code responsible for submitting transactions. If you're using ethers.js, we've prepared a dedicated library to make the transition seamless.

Contract object wrapping

First, you need to import the wrapper code to your project

  const { WrapperBuilder } = require("@redstone-finance/evm-connector");

Then you can wrap your ethers contract pointing to the selected RedStone data service id. You can (optionally) specify a number of unique signers, data feed identifiers, and URLs for the redstone cache nodes.

const yourEthersContract = new ethers.Contract(address, abi, provider);

const wrappedContract = WrapperBuilder.wrap(contract).usingDataService(
{
dataFeeds: ["ETH", "BTC"],
},
);

Now you can access any of the contract's methods in exactly the same way as interacting with the ethers-js code:

wrappedContract.executeYourMethod();

Testing

Hardhat

If you'd like to use the wrapper in a test context, we recommend using a mock wrapper so that you can easily override the oracle values to test different scenarios. To use the mock wrapper just use the usingMockData(signedDataPackages) function instead of the usingDataService function.

const { SimpleNumericMockWrapper } = require("@redstone-finance/evm-connector/dist/src/wrappers/SimpleMockNumericWrapper");

const wrappedContract =
WrapperBuilder.wrap(yourContract).usingSimpleNumericMock(
{
mockSignersCount: 10,
dataPoints: [
{dataFeedId: "ETH", value: 1000}
],
},
);
await wrappedContract.yourMethod();

You can see more examples of mocking data here.

Foundry

To use Redstone Oracles with Foundry in test context, we recommend using foundry vm.ffi function to generate mocked dataPackages. We have prepared repository showing how we can integrate foundry with redstone.

Manual payload

This approach is helpful if you need to pass the pricing data from one contract to another in your protocol.

It's also a solution for a case, where your contracts are written in solidity in a version lower than 0.8.4 it could be problematic to extend from the RedstoneConsumerBase contract. In that case we recomment to deploy a separate Extractor contract that will contain the verification logic:

pragma solidity 0.8.4;

import "@redstone-finance/evm-connector/contracts/mocks/RedstoneConsumerNumericMock.sol";

contract RedstoneExtractor is RedstoneConsumerNumericMock {

function extractPrice(bytes32 feedId, bytes calldata redstonePayload) public view returns(uint256) {
return getOracleNumericValueFromTxMsg(feedId);
}
}

and proxy the payload from your originating contract

function getPriceFromRedstoneOracle(bytes32 feedId, bytes calldata redstonePayload) public view returns(uint256) {
return redstoneExtractor.extractPrice(feedId, redstonePayload);
}

The manual payload could be obtained using the following code on the client side:

const redstonePayload = await (new DataServiceWrapper({
dataServiceId: "redstone-main-demo",
dataFeeds: ["ETH"]
}).getRedstonePayloadForManualUsage(yourContract));

// Interact with the contract (getting oracle value securely)
const price = await yourContract.getPriceFromRedstoneOracle(redstonePayload);

Working demo

You can see examples of the @redstone-finance/evm-connector usage in our dedicated repo with examples.