Accessing Price Feeds with Acurast¶
Introduction¶
Acurast gives developers complete permissionless access to compute that is trustless, affordable, and confidential for deploying their applications.
One of Acurast's use cases is to enable developers to deploy their own push/pull oracles, interacting with off-chain APIs to bring price feeds on-chain. Pricing data is confidentially processed through Acurast Processors, pushing data to smart contracts of EVM-compatible chains like Tanssi EVM appchains via a standard Chainlink Aggregator Interface.
This tutorial will walk through a demo of interacting with price feeds enabled by Acurast on the demo Tanssi EVM-compatible appchain. You can also deploy your own price feeds to your Tanssi EVM-compatible appchain. Please be advised that the steps shown in this tutorial are for demonstration purposes only - it's highly recommended that you contact the Acurast team directly as they can assist you with launching price feeds on your appchain to ensure the integrity of the deployment process.
What is Acurast?¶
Acurast is a decentralized, serverless cloud where everyone can become part of the cloud with their new, used, or even mobile phones with a smashed screen by providing compute power to the cloud and earning rewards. These so-called Processors are scattered across the globe, creating a distributed network of compute across the globe.
Processors and developers can seamlessly interact through the Acurast Console.
Fetch Price Data¶
You can design your Acurast price feed exactly as you wish. The demo price feed built for this tutorial inherits the same interface as the Chainlink price feeds. The data lives in a series of smart contracts (one per price feed) and can be fetched with the aggregator interface:
AggregatorV3Interface.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface AggregatorV3Interface {
/**
* Returns the decimals to offset on the getLatestPrice call
*/
function decimals() external view returns (uint8);
/**
* Returns the description of the underlying price feed aggregator
*/
function description() external view returns (string memory);
/**
* Returns the version number representing the type of aggregator the proxy points to
*/
function version() external view returns (uint256);
/**
* Returns price data about a specific round
*/
function getRoundData(uint80 _roundId) external view returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound);
/**
* Returns price data from the latest round
*/
function latestRoundData() external view returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound);
}
As seen above in the interface, there are five functions for fetching data: decimals
, description
, version
, getRoundData
, and latestRoundData
. For more information about the AggregatorV3Interface.sol
, see the Chainlink API Reference.
Interacting with Price Feeds on the Tanssi Demo EVM Appchain¶
This tutorial will showcase interacting with a sample BTC/USDT price feed contract on the demo EVM appchain, but you can interact any of the price feeds listed in Supported Assets. The BTC/USDT price feed is deployed on the demo EVM appchain, so you can interact with it by accessing the aggregator contract at the below contract address:
0x02093b190D9462d964C11587f7DedD92718D7B56
For a refresher on setting up Remix to interface with the demo EVM appchain, see the Deploy Smart Contracts with Remix guide. Secondly, make sure you have connected MetaMask to the demo EVM appchain.
Paste the aggregator contract into a new file in Remix and compile it.
Then, take the following steps:
- Head to the Deploy and Run Transactions tab
- Set the ENVIRONMENT to Injected Provider -- MetaMask
- Select the AggregatorV3Interface contract from the CONTRACT dropdown
- Enter the sample price feed contract address for
BTC to USD
, which is0x02093b190D9462d964C11587f7DedD92718D7B56
on the demo EVM appchain in the At Address field and click the At Address button
The aggregator contract should now be accessible. To interact with the aggregator contract, take the following steps:
- Expand the AggregatorV3Interface contract to reveal the available functions
- Click decimals to query how many digits after the decimal point are included in the returned price data
- Click description to verify the asset pair of the price feed
- Click latestRoundData to see the most recent price data for the asset pair. The price data for the pair is returned as the int256 answer
Note that to obtain a readable price from the price feed, it's essential to adjust for the feed's decimal places, which can be determined using the decimals()
method. For instance, if the price feed returns a value of 51933620000
, you'll need to move the decimal point six places to accurately reflect the price. In this example, it corresponds to a Bitcoin price of $51,933.62
at the time of writing.
Supported Assets¶
By its design, Acurast can support the price feed of any arbitrary asset that is accessible by an API. The API request that powers the demo price feed is as follows:
curl "https://api.binance.com/api/v3/ticker/price?symbol=BTCUSDT"
Upon running the above command in your terminal, you'll see a result that resembles the following:
Note
This simple example of fetching a price feed relies on a single source of price feed data from one exchange. You can build a more complex job script that aggregates pricing data from multiple sources.
The Acurast team has deployed the below price feeds on the Tanssi demo EVM appchain:
Asset & Base Pair | Aggregator Contract |
---|---|
AAVE to USDT | 0x6239Ff749De3a21DC219bcFeF9d27B0dfE171F42 |
BTC to USDT | 0x02093b190D9462d964C11587f7DedD92718D7B56 |
CRV to USDT | 0x01F143dfd745861902dA396ad7dfca962e5C83cA |
DAI to USDT | 0x73aF6b14b73059686a9B93Cd28b2dEABF76AeC92 |
ETH to USDT | 0x007c3F3cc99302c19792F73b7434E3eCbbC3db25 |
USDC to USDT | 0xe4a46ef4cFbf87D026C3eB293b7672998d932F62 |
USDT to USD | 0xf9c885E3A5846CEA887a0D69655BC08e52afe569 |
Designing and Launching Your Own Price Feed¶
You can build and launch your own Acurast price feed on your Tanssi EVM-compatible appchain. Please be advised that the steps shown in this tutorial are unaudited, unverified, and for demonstration purposes only - it's highly recommended that you contact the Acurast team directly as they can assist you with launching price feeds on your appchain to ensure the integrity of the deployment process.
To launch an Acurast price feed, you need two key components: a smart contract and a script. In the prior example of Interacting with the BTC/USD price feed on the demo EVM appchain, the generic Chainlink interface is used because it is a more straightforward example for demonstration purposes. The underlying smart contract that powers that price feed conforms to the Chainlink Aggregator interface, but the demo contract has additional components worthy of discussion. You can find both the demo contract and script at the GitHub repo for the Acurast demo BTC/USD price feed.
The demo contract, InsecureDummyPriceFeed.sol
, emits an event when the price is updated and when a new round begins. The setPrice
method is insecure, as shown in this demo smart contract, but it is provided to show you where you might add logic like aggregation consensus, access control checks, and other parameters.
InsecureDummyPriceFeed.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "./interfaces/chainlink/AggregatorV2V3Interface.sol";
/**
This Dummy Oracle is compatible with Chainlink's AggregatorV2V3Interface, meaning projects currently using
Chainlink can seamlessly migrate. The updating of the price feed happens in the interval you specified on
the Acurast script.
At the moment no check on the signer/source is being performed, making this implementation INSECURE. However
with a minimal effort you can extend the "setPrice" entrypoint to reflext the kind of logic you are looking for
(i.e. Aggregation Consensus, Check sources, thresholds, etc).
**/
contract DummyChainlinkCompatibleOracle is AggregatorV2V3Interface {
int256 private latestPrice;
uint256 private latestPriceTimestamp;
uint256 private latestRoundId;
// Assuming price can be set without restriction for simplicity
// In a real-world scenario, there should be access control mechanisms
function setPrice(int256 _price) external {
latestPrice = _price;
latestPriceTimestamp = block.timestamp;
latestRoundId++;
emit AnswerUpdated(latestPrice, latestRoundId, latestPriceTimestamp);
emit NewRound(latestRoundId, msg.sender, latestPriceTimestamp);
}
// AggregatorInterface functions
function latestAnswer() external view override returns (int256) {
return latestPrice;
}
function latestTimestamp() external view override returns (uint256) {
return latestPriceTimestamp;
}
function latestRound() external view override returns (uint256) {
return latestRoundId;
}
function getAnswer(uint256 _roundId) external view override returns (int256) {
if(_roundId == latestRoundId) {
return latestPrice;
}
return 0; // Simplification, should handle historical data
}
function getTimestamp(uint256 _roundId) external view override returns (uint256) {
if(_roundId == latestRoundId) {
return latestPriceTimestamp;
}
return 0; // Simplification, should handle historical data
}
// AggregatorV3Interface functions
function decimals() external pure override returns (uint8) {
return 6; // Assume a common decimal value for simplicity
}
function description() external pure override returns (string memory) {
return "Sample Price Feed";
}
function version() external pure override returns (uint256) {
return 1;
}
function getRoundData(uint80 _roundId)
external
view
override
returns (
uint80 roundId,
int256 answer,
uint256 startedAt,
uint256 updatedAt,
uint80 answeredInRound
)
{
require(_roundId == latestRoundId, "Only latest round data available");
return (uint80(latestRoundId), latestPrice, latestPriceTimestamp, latestPriceTimestamp, uint80(latestRoundId));
}
function latestRoundData()
external
view
override
returns (
uint80 roundId,
int256 answer,
uint256 startedAt,
uint256 updatedAt,
uint80 answeredInRound
)
{
return (uint80(latestRoundId), latestPrice, latestPriceTimestamp, latestPriceTimestamp, uint80(latestRoundId));
}
}
Warning
This demo contract has some security vulnerabilities and lacks access control mechanisms, making it unsuitable for any real use. It was developed by the Acurast team for demonstration purposes only.
Before proceeding to the next steps, you must first deploy your price feed's smart contract on your Tanssi EVM appchain. Or, you can deploy it to the demo EVM appchain, and you can obtain TestNet TANGO tokens from the Tanssi faucet. Once deployed, be sure to record the contract address, as you will need to enter this information into your Acurast price feed script.
Building the Acurast Script¶
The Acurast oracle script plays a crucial role by updating your on-chain oracle with fresh data, acting as the vital connection between the Tanssi appchain's price feed and the Acurast network. Through the Acurast console, you will upload this script and specify all necessary parameters for your price feed's operation, including its frequency, schedule, and rewards for Acurast processors, among others. To facilitate this process, you will need cACU tokens, which are available from the faucet, and serve as the native currency of the Acurast Canary network.
The Acurast script for the demo BTC/USD price feed can be used as a basis for creating your own script. Remember to update the contract address and RPC URL fields.
AcurastScript.js
/**
* This Oracle Script observes the BTC USDT Pair and posts the price on-chain.
* Deploying the script is easy using console.acurast.com and simply copy/pasting
* this script. Make sure to update `DESTINATION_CONTRACT` and `EVM_RPC_NODE` to
* reflect your deployment. For RPC's with API keys like i.e. infura make sure
* to work with the Acurast confidential environment variables. After having set
* them for your job, you can access them easily with a `_STD_.env["MY_KEY"]`. They
* also come in handy for paid API KEYs, that you don't want to share publicly.
*/
const DESTINATION_CONTRACT = 'INSERT_CONTRACT_ADDRESS';
const EVM_RPC_NODE = 'INSERT_APPCHAIN_RPC_URL';
httpGET(
'https://api.binance.com/api/v3/ticker/price?symbol=BTCUSDT',
{},
(response, certificate) => {
if (
certificate ===
'4795062d13e1ed971c6b6e5699764681e4d090bad39a7ef367cc9cb705652384'
) {
//const price = BigInt(JSON.parse(response)["price"] * 10 ** 18); // if you need more precision, just keep in mind that JS stored bigger numbers in float format, rounding up/down your stuff.
const price = BigInt(JSON.parse(response)['price'] * 10 ** 6);
const int256AsBytes = '0x' + price.toString(16).padStart(64, '0');
const payload = '0x' + _STD_.chains.ethereum.abi.encode(int256AsBytes);
_STD_.chains.ethereum.fulfill(
EVM_RPC_NODE,
DESTINATION_CONTRACT,
payload,
{
methodSignature: 'setPrice(int256)',
gasLimit: '9000000',
maxFeePerGas: '2550000000',
maxPriorityFeePerGas: '2550000000',
},
(opHash) => {
console.log('Succeeded: ' + opHash);
},
(err) => {
console.log('Failed: ' + err);
}
);
}
},
(err) => {
console.log('Failed: ' + err);
}
);
To configure your job, head to the Acurast console, then take the following steps:
- Click Create Jobs on the left-hand sidebar underneath the Consumer heading
- Select Moonbeam as the chain
- Select Moonbase as the environment. Remember that Tanssi's EVM-compatibility is derived from Moonbeam
- Select Price Feeds
- Paste in the code of your job script. You can copy and paste directly from the script of the sample BTC/USD price feed, just make sure to change the destination contract to one that you deployed on your appchain and the RPC node to your appchain's RPC URL, which can be found on the Tanssi dApp
- Optionally, you can test your code here. Any error messages will be readable in the browser's console
Continuing down the same setup page, take the following steps:
- Select Use Public Processors
- Select Interval
- Specify a Start time and End time
- Specify the Interval in minutes
- Specify a job duration and max start delay duration
- Select Number of processors to assign. The more processors you choose, the proportionally higher amount of cACU you'll need, which you can get from the faucet
- Select Max Reward paid to each processor for each job execution. You don't need to specify exactly
0.01
cACU - this amount was chosen as an example - Review everything first, then Press Publish Job
On the following screen, you'll be able to monitor the status of your job. For more information about using Acurast to build and access price feeds on your Tanssi EVM-compatible appchain, be sure to check out the Acurast docs.
| Created: February 17, 2024