# WINkLink Price Feed Service

Before integrating, see Developer Notes.

# Overview

To ensure that smart contracts reflect token prices in real-world time, it is necessary to frequently update them. In particular, the prices of assets in DeFi must closely match those of the real world. Otherwise, arbitrage or contract attacks may cause losses for users and developers.

WINkLink's price service focuses on digital currency pairs, providing decentralized applications (DApps) with accurate and stable price information on real-world digital currencies. The solution offered by WINkLink aggregates price data from multiple oracle nodes, resulting in a stable price service known as the Price Feed Contract.

The contract above is called an Aggregator — the on-chain contract that holds aggregated price data from WINkLink oracle nodes. Each price feed (e.g., BTC/USD) is implemented as an Aggregator contract that consumer contracts read from.

# Supported Price Pairs List & Configurations

# Mainnet

Pair Contract Address (Proxy)
BTC-TRX TX4rin6u2SaF3TqANqRgzfSCGi95azQNVY (opens new window)
BTC-USD TQoijQ1iZKRgJsAAWNPMu6amgtCJ3WMUV7 (opens new window)
BTT-TRX TS26cn4GmmipyGTcgvRRwqL6AyEU6vK4rw (opens new window)
BTT-USD TBAAW545oJ6iTxqzezGvagrSUzCpz1S8eR (opens new window)
BTTOLD-TRX TUjTmKMxGmH78t5DmY7YsfJFoGw6XyX9VZ (opens new window)
BTTOLD-USD TEEnwU47Fgx4Ehii7Xs9bLWK3XKo4fs6sV (opens new window)
ETH-TRX TXZ9AUk6nC2454NSDGUWvPB72JxSNJrezX (opens new window)
ETH-USD TR2yWYWovJaSM7TfZq7L7sT7ZRugdJJQmL (opens new window)
HTX-TRX TJCCcAqSc1hK5DrJF8v26QW5LQbmXayPhJ (opens new window)
JST-TRX TC19QPF2mjP1ZhXxD8JNKJs4ksxMZkCuNP (opens new window)
JST-USD TE5rKoDzKmpVAQp1sn7x6V8biivR3d5r47 (opens new window)
KGST-TRX TGt2qPTTCv6PvWRzXj12VVoG9kebzYP7WZ (opens new window)
LTC-TRX TVJPFXKMysYsRWEXJ3JkSnAUPucinUFUB6 (opens new window)
LTC-USD TGxGL85kN3W5sGdBiobgWabWFcMEtoqRJJ (opens new window)
NFT-TRX TKtc1V6QAY1Gpy511QjzXkLUphG8Dre8CY (opens new window)
NFT-USD TEC8b2oL6sAQFMiea73tTgjtTLwyV1GuZU (opens new window)
STRX-TRX TW9bNueyJZA9iZnNXGYkJuPJJ7KFN3o5qw (opens new window)
SUN-TRX TLLyqXr5cbYEMjzeThe1esss1SVBbxxubu (opens new window)
SUN-USD TRMgzSPsuWEcVpd5hv19XtLeCk8Z799sZa (opens new window)
SUNOLD-TRX TWAob1YsNzh7bfgkjfAD9MAdarcoSWScWw (opens new window)
SUNOLD-USD TEEuSdqyv2NFREtNoUXMTDSmJVK3KCuLac (opens new window)
TRX-USD TR5HtpPK4gX4RFC4DCBUHfFgsGkGFEzSAb (opens new window)
TUSD-TRX TLXMULb1SRpv841Q54C4DhWkmmGfRA2rUH (opens new window)
TUSD-USD TBc3yBP8xcyQ1E3hDTUhRxToMrgekLH2kh (opens new window)
U-TRX TJyxgoVxr6a5uaBMaT8TazZMaK9fsSFNw3 (opens new window)
U-USD TX6DsYNoMurRqnY9tRHuj4MnBoW76jVKa3 (opens new window)
USDC-TRX TNTm5ezUGHxYc9Mvst58yYTAjxDmqWWGZc (opens new window)
USDC-USD TNu3zS55MP4KnBBP6Maw1nHSzRpc3CXAxm (opens new window)
USDD-TRX TWW4P2pck8rFcxx3H8NfnH4qhNPu1V35Pb (opens new window)
USDD-USD TJ7jEgoYVaeymVfYZ3bS57dYArwVDS1mhW (opens new window)
USDJ-TRX TCBKyYMP4YQFHxYznuUaResHDTaEWLuJNW (opens new window)
USDJ-USD TB1MyT7pDCNg8w7cSW1QvYKs4WPzErzP5k (opens new window)
USDT-TRX TUfV7S4RYtdmBvtHzedfFPVsK9nvndtETp (opens new window)
USDT-USD TKePc46n5CiUCR8LL788TFeKA4kjvNnuem (opens new window)
WBTC-USD TCYS6aj9shB6rZNpTCqSkN1aTwkSnz1wHq (opens new window)
WIN-TRX TQvCG1U2jGTVwXLqvFWR27LDtEJZVgRbEg (opens new window)
WIN-USD TSCef3LT3jpLwwXCWhZe3hZoMsYk1ZLif2 (opens new window)
WSTUSDT-TRX TKcTU5vCPqBBfuULEGXg9kkLx6Tzs2Zo3x (opens new window)
USDDOLD-USD TJrtqTiWkzsdCSU7Jz4JiBswc3Sv8FCw1i (opens new window)
USDDOLD-TRX TGJ7FspL9bftnXKrRFah1yU7VxHkgBpzVB (opens new window)
USD1-TRX TVRD5vMZDnmBXF3AxBiHVJYeykVFzC8eLB (opens new window)

# Nile Testnet

  • WIN Token Contract Address: TNDSHKGBmgRx9mDYA9CnxPx55nu672yQw2
  • WinkMid Contract Address: TLDU7C8K3Gd3pXrAj9gtpVVNRHZHuHHZ8P

List of price service contract addresses:

Pair Nile (Proxy)
BTC-TRX TFETSL1Yc8dCJM7z6uBkHhAsPbqP5UaCDE (opens new window)
BTC-USD TAX8Pm3FgN74za72TFZrn5gPBxJTKgnnpE (opens new window)
BTT-TRX TKbeHN2hdrgSShG6iF3mDsJTu9fFzNrHjo (opens new window)
BTT-USD TJdzg4wqBt4JkP1ehbYQufg1cLjbomT2j7 (opens new window)
BTTOLD-TRX TETkTRbnyB4ptWiK9qXgiyFxQQ9d8ZacT6 (opens new window)
BTTOLD-USD TRpRfFzubR7oheDCwHRbwJRfeFa85L6tWE (opens new window)
ETH-TRX TSVJwLrhWBF7K6BkEG6hStjMxQJXAzBABQ (opens new window)
ETH-USD TQGyY3mWTTzKKBLBg3wQTSbAGqBnGqYSzX (opens new window)
HTX-TRX TGcVt5g4ExZNVz9gXPWBbnsAN1DPkg3RbG (opens new window)
JST-TRX TSf6ZwFrDg5Jvyci1PnRHrrZvPpCCKNTjj (opens new window)
JST-USD TJ7SizJiCAjMPAri1CFAxzg4xCRLycumMj (opens new window)
KGST-TRX TY9vDmskSFkahX7LESSe5cF5twZHerKgUd (opens new window)
LTC-TRX TWProfbHdGBCf7HVNys5KAbVT4vhUwpu22 (opens new window)
LTC-USD TRSFTb2seuQxQqUsyeJ8Wg8XhX1e2g3T19 (opens new window)
NFT-TRX TWP99RnyMVKFjzuu9XT5J21qBZu8DwhCum (opens new window)
NFT-USD TX5KVe4sp24w5HJ4nfk2ZstRhV8RTFm67W (opens new window)
STRX-TRX TEbUQ4gohuK5wdtKmpnGd2kvyzhhznJDCx (opens new window)
SUN-TRX TTxxeWGpSDV3zPDxnYXzG1ue7RpTYTvDpY (opens new window)
SUN-USD TJjENuVH7TD8RJdGtj22ac6Bt1ktpBGURR (opens new window)
SUNOLD-TRX TNfn4qt4QJ7LAndM2aWbxrGGH8CRGvzxui (opens new window)
SUNOLD-USD TMKzWKMA1gSwjYSL6VpfCUXLuwPKdjEsQ2 (opens new window)
TRX-USD TCeXRh9vcb78j2Eb2oJk4YwwnoHQDT64T1 (opens new window)
TUSD-TRX TM1bvBzHkRrQqvvHGi1CC1Heb8ESWreiNW (opens new window)
TUSD-USD TUuxMFxv6qPn1ymZoYY45SSK1hhEVAvyKz (opens new window)
U-TRX TRGQuwRsH84b2GZ4U2AGfK8GZg36nKJ5dP (opens new window)
U-USD TBzWFDDQkzE4vxhw4tExSVibPRRRKVoLWX (opens new window)
USDC-TRX TWio8JqYx2aey49ua2ohLoyBPbVVWos8RB (opens new window)
USDC-USD TF5a2qhfxtWzUQnAocPoxgKXLe1vEE8oER (opens new window)
USDD-TRX TFr7TWdb5RWPNCfecr3HNfnCmNNL8qvgmJ (opens new window)
USDD-USD TX264fxRmdhNfUgkruk9orzAVvtCehyowq (opens new window)
USDJ-TRX TDJtnT7JRNqmNaqY1mK9i1xWN4GnX1UfGd (opens new window)
USDJ-USD TKZUQTYAhH1LTG67QmhX4HxTWZdvLfH9d1 (opens new window)
USDT-TRX TVZjuqiJNNuLQAQoPAFfUqvYUxhZYkUX5Z (opens new window)
USDT-USD TT2ETLY1Mmx2DKYT9S6fMvKGPqbWH3LDEJ (opens new window)
WBTC-USD TSBoimXw9Mgx7u5A6eC3yfTimn1jfUWAKE (opens new window)
WIN-TRX TP7aHYuXUkKPKsojs9BNJDVyAJeQ2KtfCj (opens new window)
WIN-USD TYYMqsRNZTwsiFkRtn2NewvXT9GnnsPBH9 (opens new window)
WSTUSDT-TRX TZGEUihByCHG79Hbpider6pGZfY9S8ct6P (opens new window)
USDDOLD-USD THoshmYmTfmcNTqYLKAhMYTDNYBhJH3mbt (opens new window)
USDDOLD-TRX TKxxkv4CkJANRVNay8C2eY9E2zMjmWViE7 (opens new window)
USD1-TRX TNeep6gumLAPU4ZfPkyhjJyyu7akMKKjrT (opens new window)

# Apply for New Trading Pairs

To request for new trading pair to be added to WINkLink officially, please fill in and submit this form. (opens new window)

# Acquire latest price

The following example demonstrates how to read a WINkLink price feed via AggregatorV3Interface: fetch the latest price with latestRoundData(), validate freshness against the heartbeat, and query decimals() for proper precision handling.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;

/**
 * @title AggregatorV3Interface (WINkLink Price Feed)
 * @notice Interface for reading WINkLink price feeds.
 */
interface AggregatorV3Interface {
  function decimals() external view returns (uint8);
  function description() external view returns (string memory);
  function getRoundData(uint80 _roundId)
    external view
    returns (
      uint80 roundId,
      int256 answer,
      uint256 startedAt,
      uint256 updatedAt,
      uint80 answeredInRound
    );
  function latestRoundData()
    external view
    returns (
      uint80 roundId,
      int256 answer,
      uint256 startedAt,
      uint256 updatedAt,
      uint80 answeredInRound
    );
}

/**
 * THIS IS AN EXAMPLE CONTRACT THAT USES UN-AUDITED CODE.
 * DO NOT USE THIS CODE IN PRODUCTION.
 */
contract PriceConsumer {
  AggregatorV3Interface internal priceFeed;
  uint256 public constant MAX_PRICE_AGE = 1 hours; // Adjust to your business tolerance

  /**
   * Network: TRON Mainnet / Nile Testnet
   * Pass the feed address for the price pair you want to read.
   * Find supported pairs and addresses in the Price Feed Service documentation.
   */
  constructor(address feedAddress) {
    priceFeed = AggregatorV3Interface(feedAddress);
  }

  /// @notice Returns the latest price together with its decimals.
  /// @dev Reverts if the round is incomplete or the price is stale.
  function getLatestPrice() public view returns (int256 answer, uint8 decimals) {
    uint256 updatedAt;
    (
      /* uint80  roundId         */,
      answer,
      /* uint256 startedAt       */,
      updatedAt,
      /* uint80  answeredInRound */
    ) = priceFeed.latestRoundData();

    require(updatedAt > 0, "Round not complete");
    require(block.timestamp - updatedAt <= MAX_PRICE_AGE, "Price too stale");

    decimals = priceFeed.decimals();
  }
}

If you only need the price off-chain (e.g., in a frontend, backend, or script), you do not need to deploy any contract — read the proxy directly with TronWeb:

// Off-chain read with TronWeb — no contract deployment required.
// npm install tronweb
const { TronWeb } = require('tronweb');

// Mainnet: https://api.trongrid.io  |  Nile testnet: https://nile.trongrid.io
// For production, use a free TronGrid API key to avoid rate limits:
//   new TronWeb({ fullHost: 'https://api.trongrid.io', headers: { 'TRON-PRO-API-KEY': 'your-key' } })
const tronWeb = new TronWeb({ fullHost: 'https://api.trongrid.io' });
// A constant (read-only) call still needs a from-address set; any valid address works:
tronWeb.setAddress('T9yD14Nj9j7xAB4dbGeiX9h8unkKHxuWwb');

// Proxy address of the price pair you want to read (see Supported Price Pairs List).
const FEED_ADDRESS = 'TXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX';

async function getLatestPrice() {
  const feed = await tronWeb.contract().at(FEED_ADDRESS);
  const data = await feed.latestRoundData().call();
  const decimals = await feed.decimals().call();
  // Normalize the price as answer / 10 ** decimals
  console.log('answer:', data.answer.toString());
  console.log('updatedAt:', data.updatedAt.toString());
  console.log('decimals:', decimals.toString());
}

getLatestPrice();

Or with tronpy (Python):

# Off-chain read with tronpy — no contract deployment required.
# pip install tronpy
from tronpy import Tron

# Mainnet: Tron()  |  Nile testnet: Tron(network='nile')
# For production, use a free TronGrid API key to avoid rate limits:
#   from tronpy.providers import HTTPProvider
#   client = Tron(HTTPProvider(api_key='your-key'))
client = Tron()

# Proxy address of the price pair you want to read (see Supported Price Pairs List).
FEED_ADDRESS = 'TXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'

feed = client.get_contract(FEED_ADDRESS)
round_id, answer, started_at, updated_at, answered_in_round = feed.functions.latestRoundData()
decimals = feed.functions.decimals()
# Normalize the price as answer / 10 ** decimals
print('answer:', answer)
print('updatedAt:', updated_at)
print('decimals:', decimals)

# Acquire Price History

Round / RoundId — Each time the price is updated, a new Round is created and uniquely identified by a RoundId (uint80). Historical prices for any past round can be queried via getRoundData(roundId).

The following code demonstrates how to read a historical price by roundId via getRoundData().

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;

/**
 * DO NOT USE THIS CODE IN PRODUCTION.
 *
 * Continued from the AggregatorV3Interface defined above.
 */
contract HistoricalPriceConsumer {
  AggregatorV3Interface internal priceFeed;

  constructor(address feedAddress) {
    priceFeed = AggregatorV3Interface(feedAddress);
  }

  /// @notice Returns the historical price for a specific roundId.
  /// @param  _roundId The round to query (must be a valid completed round).
  function getHistoricalPrice(uint80 _roundId)
    public view
    returns (int256 answer, uint256 updatedAt)
  {
    (
      /* uint80  roundId         */,
      answer,
      /* uint256 startedAt       */,
      updatedAt,
      /* uint80  answeredInRound */
    ) = priceFeed.getRoundData(_roundId);

    require(updatedAt > 0, "Round not complete");
  }
}

# How to setup Price Feed contracts

# Contract Deployment

Employing a decentralized structure, WINkLink features open-source smart contracts and allows any organization or individual to deploy their WINkLink oracle contracts and release these services to the public.

Users may pick their sets from all the open services available on WINkLink to create their own aggregated data contracts and benefit from decentralization.

Contracts for the project is hosted at: https://github.com/tron-oracle/winklink-libocr/tree/main/tvm-contracts (opens new window) - Connect your Github account

You may use any of the following tools or libraries for contract deployment and call testing:

# Aggregator Contract

Aggregator contract is deployed on the TRON public chain with the following features:

  • Accepts transmission from WINkLink node's off-chain aggregation
  • Calculate the WIN fee on data requests and allow Oracle nodes to claim rewards
  • Implements the Owned interface. This provides access control on different methods of exposed by the aggregator contract.

Contract code is available at AccessControlledOCRAggregator.sol.

# Add a Job to Your Node

The job of your node represents the data service that your node supports, and each job has a unique 32-byte ID. For end users, (Oracle address, job ID) uniquely identifies the data service provided by a WINkLink node. Each WINkLink node can provide multiple data services.

When your WINkLink node is running properly, you can add a job to your node via Operator UI:

Example: (change the parameters below to the Oracle contract address deployed in the steps above)

TIP

For bootstrap node, set the DefaultBootstrapPeers in the config file as well.

Example: DefaultBootstrapPeers = ['/ip4/127.0.0.1/tcp/6788/p2p/12D3KooWMrKGdnH6nBrf7hDz25NXFSFNk7vgsTxj9bHskWEct4xh']

# Bootstrap node

type               = "offchainreporting"
schemaVersion      = 1
tvmChainID         = 1
name               = "TUSD-TRX"
contractAddress    = "ACCESS-CONTROLLED-OCR-AGGREGATOR-ADDRESS"
p2pBootstrapPeers  = [
  "/ip4/127.0.0.1/tcp/6788/p2p/P2P-PEER-ID",
]
isBootstrapPeer = true
keyBundleID = "NODE-KEY-BUNDLE"
forwardingAllowed = false
maxTaskDuration = "0s"

TIP

Ensure that the bootstrap node and the oracle node are configured as individual entities, each linking to its unique database instance. When operating locally, modify the config.toml file to designate different port numbers for each instance, facilitating access to the distinct Operator UIs for each.

[WebServer]
HTTPPort = 3000
SecureCookies = false # Default

# Oracle node

type               = "offchainreporting"
schemaVersion      = 1
tvmChainID         = 2
name               = "OCR: TUSD-TRX"
contractAddress    = "ACCESS-CONTROLLED-OCR-AGGREGATOR-ADDRESS"
p2pBootstrapPeers  = [
"/ip4/127.0.0.1/tcp/6788/p2p/P2P-PEER-ID",
]
isBootstrapPeer    = false
keyBundleID        = "NODE-KEY-BUNDLE"
transmitterAddress = "THE-CURRENT-NODE-EIP55-ADDRESS"
observationTimeout = "10s"
blockchainTimeout  = "20s"
contractConfigTrackerSubscribeInterval = "2m"
contractConfigTrackerPollInterval = "1m"
contractConfigConfirmations = 3
observationSource   = """
ds_http           [type="http" method=GET url="https://www.okx.com/api/v5/market/index-tickers?instId=TUSD-USD"]
    ds_parse          [type="jsonparse" path="data,0,idxPx"]
ds_converttrx     [type="converttrx" url="https://www.okx.com/api/v5/market/index-tickers?instId=TRX-USD" path="data.0.idxPx"]
ds_multiply       [type="multiply" times=1000000]

    ds_http -> ds_parse -> ds_converttrx -> ds_multiply
"""

# Query Jobs

Jobs can be retrieved under the jobs tab in Operator UI.

The sub-tabs are as follows:

  • Overview: list of recent job runs and task flow
  • Definition: job specification
  • Errors: cumulative count of different errors encountered since job creation
  • Runs: list of all job runs

job-page-ui.png

# API Reference

When you use WINkLink price feeds, retrieve the feeds through the AggregatorV3Interface and the proxy address. Optionally, you can call variables and functions in the AccessControlledOffchainAggregator contract to get information about the aggregator behind the proxy.

The proxy exposes a stable address whose underlying aggregator can be upgraded without affecting your integration.

# AggregatorV3Interface

Import this interface to your contract and use it to run functions in the proxy contract. Create the interface object by pointing to the proxy address (find supported pairs and their proxy addresses in the Supported Price Pairs List above). For example:

/**
 * Network: TRON Mainnet / Nile Testnet
 * Pass the proxy address of the price pair you want to read.
 */
constructor(address feedAddress) {
  priceFeed = AggregatorV3Interface(feedAddress);
}

To see examples for how to use this interface, see the "Acquire latest price" section above.

Name Description
decimals The number of decimals in the response.
description The description of the aggregator that the proxy points to.
getRoundData Get data from a specific round.
latestRoundData Get data from the latest round.
version The version representing the type of aggregator the proxy points to.

decimals

Get the number of decimals present in the response value.

function decimals() external view returns (uint8);
  • RETURN: The number of decimals.

description

Get the description of the underlying aggregator that the proxy points to.

function description() external view returns (string memory);
  • RETURN: The description of the underlying aggregator.

getRoundData

Get data about a specific round, using the roundId.

function getRoundData(
  uint80 _roundId
) external view returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound);

Parameters:

  • _roundId: The round ID

Return values:

  • roundId: The round ID
  • answer: The answer for this round
  • startedAt: Timestamp of when the round started
  • updatedAt: Timestamp of when the round was updated
  • answeredInRound: Deprecated — previously used when answers could take multiple rounds to be computed. On WINkLink feeds it is always equal to roundId.

latestRoundData

Get the data from the latest round.

function latestRoundData()
  external
  view
  returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound);

Return values:

  • roundId: The round ID.
  • answer: The price that this feed provides for the configured pair.
  • startedAt: Timestamp of when the round started.
  • updatedAt: Timestamp of when the round was updated. Validate this against the feed's heartbeat to check freshness.
  • answeredInRound: Deprecated — previously used when answers could take multiple rounds to be computed. On WINkLink feeds it is always equal to roundId.

Notes for WINkLink OCR feeds: each report writes both startedAt and updatedAt to the block timestamp of that report, so the two values are identical. The proxy encodes a phase in the high bits of roundId (phaseId << 64 | aggregatorRoundId); as a result round IDs are not contiguous from 1 and increase across aggregator upgrades. Pass the phase-encoded roundId (e.g. the one returned by latestRoundData()) when calling getRoundData().

version

The version representing the type of aggregator the proxy points to.

function version() external view returns (uint256);
  • RETURN: The version number. WINkLink price feeds currently return 4.

# AccessControlledOffchainAggregator

This is the contract for the aggregator. You can call functions on the aggregator directly, but it is a best practice to use the AggregatorV3Interface to run functions on the proxy instead so that changes to the aggregator do not affect your application. Read the aggregator contract only if you need functions that are not available in the proxy.

The aggregator contract has several variables and functions that might be useful for your application. Although aggregator contracts are similar for each data feed, some aggregators have different variables. Use the typeAndVersion() function on the aggregator to identify what type of aggregator it is and what version it is running.

Always check the contract source code and configuration to understand how specific data feeds operate. A reference WINkLink aggregator contract: TU5dpGzXAZwrpFfCrFzDBPWNxPm4diREqu (opens new window) on TronScan.

This contract imports OffchainAggregator and SimpleReadAccessController, which also include their own imports. The variables and functions lists include the publicly accessible items from these imported contracts.

A simple way to read the variables or functions is to get the ABI from a blockchain explorer (TronScan) and point the ABI to the aggregator address.

Variables:

Name Description
checkEnabled A boolean that indicates if access is limited to addresses on the internal access list.
maxAnswer The upper bound on the answer the aggregator is allowed to report. A report whose median is above this value is rejected.
minAnswer The lower bound on the answer the aggregator is allowed to report. A report whose median is below this value is rejected.
owner The address that owns this aggregator contract. This controls which address can execute specific functions.

Functions:

Name Description
billingAccessController The address for the billingAccessController, which limits access to the billing configuration for the aggregator.
decimals Return the number of digits of precision for the stored answer. Answers are stored in fixed-point format.
description Return a description for this data feed. This is different depending on which feed you select.
getAnswer Deprecated — Do not use this function.
getBilling Retrieve the current billing configuration.
getRoundData Get the full information for a specific aggregator round including the answer and update timestamps.
getTimestamp Deprecated — Do not use this function.
getWinToken Get the address of the WIN token contract used to pay oracles.
hasAccess Check if an address has internal access.
latestAnswer Return the latest answer for this data feed. No timestamp is included to check data freshness.
latestConfigDetails Return information about the current offchain reporting protocol configuration.
latestRound Deprecated — Do not use this function.
latestRoundData Get the full information for the most recent round including the answer and update timestamps.
latestTimestamp Deprecated — Do not use this function.
latestTransmissionDetails Get information about the most recent answer.
oracleObservationCount Returns the number of observations that oracle is due to be reimbursed for.
owedPayment Returns how much WIN an oracle is owed for its observations.
requesterAccessController Returns the address for the requester access controller contract.
transmitters The oracle addresses that can report answers to this aggregator.
typeAndVersion Returns the aggregator type and version. The version is for the type of aggregator, and different from the contract version.
validatorConfig Returns the address and the gas limit for the validator contract.
version Returns the contract version. This is different from the typeAndVersion for the aggregator.
winAvailableForPayment Get the amount of WIN on this contract that is available to make payments to oracles. This value can be negative if there are outstanding payment obligations.

billingAccessController

The address for the billingAccessController, which limits access to the billing configuration for the aggregator.

function billingAccessController() external view returns (AccessControllerInterface) {
  return s_billingAccessController;
}

decimals

Return the number of digits of precision for the stored answer. Answers are stored in fixed-point format.

function decimals() external view returns (uint8 decimalPlaces);

description

Return a description for this data feed. Usually this is an asset pair for a price feed.

function description() public view override checkAccess() returns (string memory) {
  return super.description();
}

getAnswer

Deprecated — do not use this function. Use getRoundData() instead.

getBilling

Retrieve the current billing configuration. On TRON, the first three fields (maximumGasPrice, reasonableGasPrice, microWinPerTrx) are not used and are set to 0.

function getBilling()
  external
  view
  returns (
    uint32 maximumGasPrice,
    uint32 reasonableGasPrice,
    uint32 microWinPerTrx,
    uint32 winPerObservation,
    uint32 winPerTransmission
  )
{
  Billing memory billing = s_billing;
  return (
    billing.maximumGasPrice,
    billing.reasonableGasPrice,
    billing.microWinPerTrx,
    billing.winPerObservation,
    billing.winPerTransmission
  );
}

getRoundData

Get the full information for a specific aggregator round including the answer and update timestamps. Use this to get the full historical data for a round.

function getRoundData(uint80 _roundId)
  public
  view
  override
  checkAccess()
  returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound)
{
  return super.getRoundData(_roundId);
}

getTimestamp

Deprecated — do not use this function. Use getRoundData() instead.

getWinToken

Get the address of the WIN token contract used to pay oracles.

function getWinToken() external view returns (WinTokenInterface winToken) {
  return s_winToken;
}

hasAccess

Check if an address has internal access.

function hasAccess(address _user, bytes memory _calldata) public view virtual override returns (bool) {
  return super.hasAccess(_user, _calldata) || _user == tx.origin;
}

latestAnswer

Return the latest answer for this data feed. No timestamp is included to check data freshness. Returns 0 if no writes have occurred. latestRoundData includes the latest answer and timestamps for checking data freshness.

latestConfigDetails

Return information about the current offchain reporting protocol configuration.

function latestConfigDetails() external view returns (uint32 configCount, uint32 blockNumber, bytes16 configDigest) {
  return (s_configCount, s_latestConfigBlockNumber, s_hotVars.latestConfigDigest);
}

latestRound

Deprecated — do not use this function. Use latestRoundData() instead.

latestRoundData

Get the full information for the most recent round including the answer and update timestamps.

function latestRoundData()
  public
  view
  override
  checkAccess()
  returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound)
{
  return super.latestRoundData();
}

latestTimestamp

Deprecated — do not use this function. Use latestRoundData() instead.

latestTransmissionDetails

Get information about the most recent answer. Callable only by an externally owned account (EOA).

function latestTransmissionDetails()
  external
  view
  returns (bytes16 configDigest, uint32 epoch, uint8 round, int256 latestAnswer, uint64 latestTimestamp)
{
  require(msg.sender == tx.origin, "Only callable by EOA");
  return (
    s_hotVars.latestConfigDigest,
    uint32(s_hotVars.latestEpochAndRound >> 8),
    uint8(s_hotVars.latestEpochAndRound),
    s_transmissions[s_hotVars.latestAggregatorRoundId].answer,
    s_transmissions[s_hotVars.latestAggregatorRoundId].timestamp
  );
}

oracleObservationCount

Returns the number of observations that oracle is due to be reimbursed for.

function oracleObservationCount(address _signerOrTransmitter) external view returns (uint16) {
  Oracle memory oracle = s_oracles[_signerOrTransmitter];
  if (oracle.role == Role.Unset) {
    return 0;
  }
  return s_oracleObservationsCounts[oracle.index] - 1;
}

owedPayment

Returns how much WIN an oracle is owed for its observations.

function owedPayment(address _transmitter) public view returns (uint256) {
  Oracle memory oracle = s_oracles[_transmitter];
  if (oracle.role == Role.Unset) {
    return 0;
  }
  Billing memory billing = s_billing;
  uint256 winAmount = uint256(s_oracleObservationsCounts[oracle.index] - 1) * uint256(billing.winPerObservation);
  winAmount += s_gasReimbursementsWin[oracle.index] - 1;
  return winAmount;
}

requesterAccessController

Returns the address for the requester access controller contract.

function requesterAccessController() external view returns (AccessControllerInterface) {
  return s_requesterAccessController;
}

transmitters

The oracle addresses that can report answers to this aggregator.

function transmitters() external view returns (address[] memory) {
  return s_transmitters;
}

typeAndVersion

Returns the aggregator type and version. The version is for the type of aggregator, and different from the contract version.

function typeAndVersion() external pure virtual override returns (string memory) {
  return "AccessControlledOffchainAggregator 4.0.0";
}

validatorConfig

Returns the address and the gas limit for the validator contract.

function validatorConfig() external view returns (AggregatorValidatorInterface validator, uint32 gasLimit) {
  ValidatorConfig memory vc = s_validatorConfig;
  return (vc.validator, vc.gasLimit);
}

version

Returns the contract version. This is different from the typeAndVersion for the aggregator.

function version() external view returns (uint256);

winAvailableForPayment

Get the amount of WIN on this contract that is available to make payments to oracles. This value can be negative if there are outstanding payment obligations.

function winAvailableForPayment() external view returns (int256 availableBalance) {
  int256 balance = int256(s_winToken.balanceOf(address(this)));
  int256 due = int256(totalWINDue());
  return int256(balance) - int256(due);
}

# Developer Notes

WINkLink price feeds provide reliable on-chain price data through a decentralized network of 7 independent oracle nodes. The performance of feeds in your DApp depends on both external market conditions and the integration code you write. When integrating WINkLink price feeds, please be mindful of two areas: market integrity and application code.

# Market Integrity

WINkLink price feeds reflect real-world market conditions and are subject to the external market environment. When integrating, understand the market characteristics of the asset you read, and account for extreme scenarios in your application logic.

The external market environment affects feeds in the following ways:

  • The market structure of the underlying asset (exchange distribution, depth, liquidity, etc.) affects price stability.
  • Assets with lower liquidity are more sensitive to changes in the external market environment.
  • Under extreme market scenarios (sharp volatility, sudden drops in liquidity, market disruptions), feed update behavior may diverge from normal patterns.

We recommend designing your application logic to handle extreme market scenarios, and continuously observing feed behavior after going live.

# Application Code

Application code quality, reliability, and dependency management directly affect how robustly your DApp consumes price data.

Validate price freshness

Each WINkLink price feed has a heartbeat — an interval within which the on-chain price is guaranteed to be updated. We recommend validating the timestamp before using a price:

(uint80 roundId, int256 answer, , uint256 updatedAt, ) = priceFeed.latestRoundData();
require(updatedAt > 0, "Round not complete");
require(block.timestamp - updatedAt <= MAX_PRICE_AGE, "Price too stale");

MAX_PRICE_AGE should be larger than the heartbeat of the price pair you are reading. Choose the value based on your business tolerance.

Understand decimals

The precision (decimal places) of WINkLink price feeds may differ between pairs. Always call decimals() before performing any price calculation:

uint8 dec = priceFeed.decimals();
// Normalize the price using dec in your calculation

Do not hard-code a precision value.

Keep your contract upgradeable

Avoid hard-coding feed contract addresses deep into immutable business logic. Store them in variables that can be updated by a contract owner or governance, so you can migrate to a replacement feed if WINkLink upgrades or deprecates the underlying aggregator.

Test on Nile first

Before deploying your consumer contract to mainnet, run full integration testing on the Nile testnet against WINkLink's Nile feed addresses:

Watch for WINkLink updates

WINkLink publishes price feed–related updates through two channels. We recommend integrators monitor content relevant to their applications:

Before integration, record the baseline configuration parameters of the feeds you use, so you can periodically compare them after launch and detect parameter changes promptly.

Monitor in production

After integration, we recommend monitoring the following signals:

  • Whether latestRoundData() is updated on schedule (heartbeat behavior is normal)
  • Whether stale check or related validation events are triggered in your contract
  • The frequency and distribution of the above

You can implement this through TRON block explorer event subscriptions or your own off-chain monitoring scripts.