APRO Data Pull is a Oracle Product based on the pull-model. The Pull model, where price data is fetched from APRO's decentralized network only when required, providing cost-effective, on-demand access to updates. This model reduces the need for continuous on-chain interactions, ensuring accuracy and minimizing costs.
You can use APRO Data Pull to connect your smart contracts to real-time asset pricing data. These data feeds aggregate information from many independent APRO node operators, allowing contracts to fetch data on-demand when needed.
How to Use Real-Time Data
Anyone can submit a report verification to the on-chain APRO contract, the report includes the price, timestamp, signatures. The price data contained in the report will be stored in the contract for future use when the report is verified successfully.
This guide explains how to use real-time APRO data in EVM contracts.
Acquire report data (include price, timestamp, signatures) from our Live-Api service
Consensused price data is published via Live-Api Service. Please see the REST API & WebSocket Integration Guide for detailed instructions on fetching and decoding reports.
Using the report data in the contract
Report data can be used in the following scenarios.
Scenario 1: Get the latest price of a price feed from the Live-Api server, verify and update the price in the on-chain contract, and then use the latest price in subsequent business logic. The price update logic and business logic can be processed in the same transaction.
Suitable for on-chain applications that always use the latest price data. Please refer to the function verifyAndReadLatestPrice.
Scenario 2: Get a specific timestamp price of a price feed from the Live-Api server, verify the price in the on-chain contract, and use this price in subsequent business logic.
Suitable for on-chain applications that need a specific timestamp price data. Please refer to the function verifyAndReadThePrice.
WARN: The validity period of report data is 24 hours, so some not-so-new report data can also be verified successfully. Please ensure that you do not mistake this price for the latest price.
Scenario 3: Get the latest price of a feed from the Live-Api server, verify the price in the on-chain contract.
This usage is similar to using the traditional push-model based oracle product. The price update logic and business logic are separated. Please refer to the function verifyReportWithWrapNativeToken or verifyReportWithNativeToken.
Scenario 4: Read only the latest price of a feed that has been verified in the on-chain contract.
The price read in this way may be not timely if no other users actively submit report verifications. Please refer to the function readPrice.
Here are some codes for your reference.
// SPDX-License-Identifier: MITpragmasolidity 0.8.19;interface IERC20 {/** * @dev Emitted when `value` tokens are moved from one account (`from`) to * another (`to`). * * Note that `value` may be zero. */eventTransfer(addressindexed from, addressindexed to, uint256 value);/** * @dev Emitted when the allowance of a `spender` for an `owner` is set by * a call to {approve}. `value` is the new allowance. */eventApproval(addressindexed owner, addressindexed spender, uint256 value);/** * @dev Returns the amount of tokens in existence. */functiontotalSupply() externalviewreturns (uint256);/** * @dev Returns the amount of tokens owned by `account`. */functionbalanceOf(address account) externalviewreturns (uint256);/** * @dev Moves `amount` tokens from the caller's account to `to`. * * Returns a boolean value indicating whether the operation succeeded. * * Emits a {Transfer} event. */functiontransfer(address to,uint256 amount) externalreturns (bool);/** * @dev Returns the remaining number of tokens that `spender` will be * allowed to spend on behalf of `owner` through {transferFrom}. This is * zero by default. * * This value changes when {approve} or {transferFrom} are called. */functionallowance(address owner,address spender) externalviewreturns (uint256);/** * @dev Sets `amount` as the allowance of `spender` over the caller's tokens. * * Returns a boolean value indicating whether the operation succeeded. * * IMPORTANT: Beware that changing an allowance with this method brings the risk * that someone may use both the old and the new allowance by unfortunate * transaction ordering. One possible solution to mitigate this race * condition is to first reduce the spender's allowance to 0 and set the * desired value afterwards: * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 * * Emits an {Approval} event. */functionapprove(address spender,uint256 amount) externalreturns (bool);/** * @dev Moves `amount` tokens from `from` to `to` using the * allowance mechanism. `amount` is then deducted from the caller's * allowance. * * Returns a boolean value indicating whether the operation succeeded. * * Emits a {Transfer} event. */functiontransferFrom(address from,address to,uint256 amount) externalreturns (bool);}libraryCommon {// @notice The asset struct to hold the address of an asset and amountstructAsset {address assetAddress;uint256 amount; }}interface IWERC20 {functiondeposit() externalpayable;functionwithdraw(uint256) external;}interface IVerifierFeeManager {}contract AproStructs {structPrice {uint192 value;int8 decimal;uint32 observeAt;uint32 expireAt; }}// Custom interfaces for IVerifierProxy and IFeeManagerinterface IVerifierProxy {/** * @notice Verifies that the data encoded has been signed. * correctly by routing to the correct verifier, and bills the user if applicable. * @param payload The encoded data to be verified, including the signed * report. * @param parameterPayload Fee metadata for billing. For the current implementation this is just the abi-encoded fee token ERC-20 address.
* @return verifierResponse The encoded report from the verifier. */functionverify(bytescalldata payload,bytescalldata parameterPayload ) externalpayablereturns (bytesmemory verifierResponse);functions_feeManager() externalviewreturns (IVerifierFeeManager);}interface IPriceReader {/// @notice Returns the period (in seconds) that a price feed is considered valid since its observe timefunctiongetValidTimePeriod() externalviewreturns (uint256 validTimePeriod);functionupdateReport(bytescalldata report) external;functiongetPrice(bytes32 id ) externalviewreturns (AproStructs.Pricememory price);functiongetPriceUnsafe(bytes32 id ) externalviewreturns (AproStructs.Pricememory price);functiongetPriceNoOlderThan(bytes32 id,uint age ) externalviewreturns (AproStructs.Pricememory price);}interface IFeeManager {functiongetFeeAndReward(address subscriber,bytesmemory unverifiedReport,address quoteAddress ) externalreturns (Common.Assetmemory,Common.Assetmemory,uint256);functioni_linkAddress() externalviewreturns (address);functioni_nativeAddress() externalviewreturns (address);functioni_rewardManager() externalviewreturns (address);}contract DemoContract { error NothingToWithdraw(); // Thrown when a withdrawal attempt is made but the contract holds no tokens of the specified type.
error NotOwner(address caller); // Thrown when a caller tries to execute a function that is restricted to the contract's owner.
structReport {bytes32 feedId;uint32 validFromTimestamp; uint32 observationsTimestamp;uint192 nativeFee;uint192 linkFee;uint32 expiresAt;uint192 price;uint192 bid; uint192 ask; } IVerifierProxy public s_verifierProxy;addressprivate s_owner;constructor(address_verifierProxy) { s_owner = msg.sender; s_verifierProxy =IVerifierProxy(_verifierProxy); }/// @notice Checks if the caller is the owner of the contract.modifieronlyOwner() {if (msg.sender != s_owner) revertNotOwner(msg.sender); _; }receive () externalpayablevirtual {}/** * This method is an example of how to interact with the APRO contract. * Fetch the latest report from the live-api server and pass it to the APRO contract to verify the price. * If the price observation time in the report is later than the price observation time stored in the contract, the price in the contract will be updated.
* * @param payload The encoded report data contains the latest timestamp's price. */functionverifyAndReadLatestPrice(bytescalldata payload) public { (bytes32 feedId,, ) =verifyReportWithWrapNativeToken(payload);// Read the current price from a price feed if it is less than 60 seconds old.// Note: You can decrease the time value according to your business logic. AproStructs.Price memory price =IPriceReader(address(s_verifierProxy)).getPriceNoOlderThan(feedId,60);//do some business logic with the latest price }/** * This method is an example of how to interact with the APRO contract. * Fetch the report at a specific timestamp from the live-api server and pass it to the APRO contract to verify the price.
* Note: This price contained in the report is not necessarily the latest price. * @param payload The encoded report data contains the feedId's specific timestamp's price. */functionverifyAndReadThePrice(bytescalldata payload) public { (bytes32 feedId,uint32 timestamp,uint192 verifiedPrice) =verifyReportWithWrapNativeToken(payload);//do some business logic with the verifiedPrice }/** * This method is an example of how to interact with the APRO contract. * Read the feedId's price through three methods. * @param feedId each price feed (e.g., BTC/USD) is identified by a price feed ID. * The complete list of feed IDs is available at https://docs.apro.com/en/data-pull/price-feed-id */functionreadPrice(bytes32 feedId) publicviewreturns(AproStructs.Pricememory price){// Read the current price from a price feed.// Note: this transactions may failed with PriceFeedExpire or PriceFeedNotFound error AproStructs.Price memory price1 =IPriceReader(address(s_verifierProxy)).getPriceUnsafe(feedId);// Read the current price from a price feed if it is less than `validTimePeriod` (default: 120) seconds old.// Note: this transactions may failed with PriceFeedExpire or PriceFeedNotFound or StalePrice error//AproStructs.Price memory price2 = IPriceReader(address(s_verifierProxy)).getPrice(feedId);// Read the current price from a price feed if it is less than 60 seconds old.// Note: this transactions may failed with PriceFeedExpire or PriceFeedNotFound or StalePrice error//AproStructs.Price memory price3 = IPriceReader(address(s_verifierProxy)).getPriceNoOlderThan(feedId, 60);return price1; }functionverifyReportWithWrapNativeToken(bytescalldata payload) publicreturns(bytes32,uint32,uint192){// Report verification fees IFeeManager feeManager =IFeeManager(address(s_verifierProxy.s_feeManager()));//decode the report from the payload (/* bytes32[3] reportContextData */,bytesmemory reportData,,,) = abi .decode(payload, (bytes32[3],bytes,bytes32[],bytes32[],bytes32));address feeTokenAddress = feeManager.i_nativeAddress(); (Common.Asset memory fee,,) = feeManager.getFeeAndReward(address(this), reportData, feeTokenAddress );// Approve feeManager to spend this contract's balance in feesIERC20(feeTokenAddress).approve(address(feeManager), fee.amount);// Verify the reportbytesmemory verifiedReportData = s_verifierProxy.verify( payload, abi.encode(feeTokenAddress) );// Decode verified report data into a Report struct Report memory verifiedReport = abi.decode(verifiedReportData, (Report));return (verifiedReport.feedId, verifiedReport.validFromTimestamp, verifiedReport.price); }functionverifyReportWithNativeToken(bytescalldata payload) payablepublicreturns(bytes32,uint32,uint192) {// Report verification fees IFeeManager feeManager =IFeeManager(address(s_verifierProxy.s_feeManager()));address feeTokenAddress = feeManager.i_nativeAddress();//decode the report from the payload (/* bytes32[3] reportContextData */,bytesmemory reportData,,,) = abi .decode(payload, (bytes32[3],bytes,bytes32[],bytes32[],bytes32)); (Common.Asset memory fee,,) = feeManager.getFeeAndReward(address(this), reportData, feeTokenAddress );// Verify the reportbytesmemory verifiedReportData = s_verifierProxy.verify{value: msg.value}( payload, abi.encode(feeTokenAddress) );uint256 change;unchecked {//msg.value is always >= to fee.amount change = msg.value - fee.amount; }if (change !=0) {payable(msg.sender).transfer(change); }// Decode verified report data into a Report struct Report memory verifiedReport = abi.decode(verifiedReportData, (Report));return (verifiedReport.feedId, verifiedReport.validFromTimestamp, verifiedReport.price); }/** * @notice Withdraws all tokens of a specific ERC20 token type to a beneficiary address. * @dev Utilizes SafeERC20's safeTransfer for secure token transfer. Reverts if the contract's balance of the specified token is zero.
* @param _beneficiary Address to which the tokens will be sent. Must not be the zero address. * @param _token Address of the ERC20 token to be withdrawn. Must be a valid ERC20 token contract. */functionwithdrawToken(address_beneficiary,address_token ) publiconlyOwner {// Retrieve the balance of this contractuint256 amount =IERC20(_token).balanceOf(address(this));// Revert if there is nothing to withdrawif (amount ==0) revertNothingToWithdraw();IERC20(_token).transfer(_beneficiary, amount); }}
You can test the contract code by following these steps:
Compile and deploy the DemoContract with the VerifierProxy contract address.
Deposit some wrapped native token to the DemoContract.
Use the full report obtained in the API& WebSocket Guide guide as the payload params and trigger the appropriate functions.