Plugin feeds on XDC
Early access. Live data and the free APIs are online. Sign-ups and payments open soon.

Reading a Plugin feed safely

Five rules, copy-paste examples and the public JSON API. For tested Solidity templates, see FeedKit.

Five things to know first

  1. Always read the proxy (EACAggregatorProxy), never the aggregator directly: the owner can swap the aggregator behind it.
  2. Decimals differ per feed: most live feeds use 16 and some older ones 18 (and Plugin's contracts allow others, such as 25). Call decimals(), don't hard-code 8.
  3. Check updatedAt. Many feeds update only on deviation or not at all (see the observed update gaps in the FeedKit feed table). Set a max age per feed.
  4. Some proxies are access-controlled and revert with "No access" unless your contract is whitelisted. Ask Plugin through its feeds portal to add your contract to the feed's access list.
  5. Know who can change it: check owner() on the proxy. Most feeds (60 of 90) share one owner; three other owners control the rest, including XDC/USDT. See the owner field in the feed list (JSON).
  • Quote direction. A pair BASE/QUOTE reports the price of 1 BASE in QUOTE units, scaled by decimals(). For example PLI/XDC reports how many XDC one PLI is worth, not the other way round. Feeds named USD/X (for example USD/AAVE) are inverted pairs.
  • Addresses. XDC explorers show addresses as xdc…. This is display-only: on chain, in Solidity, viem, ethers and Foundry, use the same address with 0x. Examples use checksummed 0x.
  • Mainnet addresses. These feeds are on XDC mainnet (chainId 50). Plugin runs feeds on Apothem (51), but they are unlisted and less reliable: most of them stopped updating on 21 Oct 2026. Treat them as best-effort test data and check updatedAt from latestRoundData; for reliable tests use a mainnet fork or a mock. Some of these mainnet addresses hold unrelated contracts on Apothem.
  • Reverts over JSON-RPC. Off chain (eth_call), an access-controlled proxy returns a JSON-RPC error "execution reverted: No access" with Error(string) data (0x08c379a0…). A broken proxy such as BTC/USDT 0xEe28…bDCb returns "execution reverted" with no data. Treat both as "feed unavailable", not as a transport failure, and don't retry them on another RPC.
  • Feed list as data. All 90 known proxies with pair, decimals, status, staleness default, owner and aggregator: /kit/feeds.json (static file, CORS-enabled, snapshot).

Solidity

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

interface IPluginFeed {
  function decimals() external view returns (uint8);
  function latestRoundData() external view returns (
    uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound);
}

contract UsesPluginPrice {
  IPluginFeed public immutable feed = IPluginFeed(0x2baa1AEfa06D58454c9c06C56436CdD5B7904248); // PLI/USDT proxy
  uint256 public constant MAX_AGE = 1 hours; // pick per feed: check its real update rhythm first

  function price() external view returns (int256 answer, uint8 decimals) {
    uint256 updatedAt;
    (, answer, , updatedAt, ) = feed.latestRoundData();
    require(answer > 0, "bad answer");
    require(updatedAt != 0 && updatedAt <= block.timestamp, "bad updatedAt");
    require(block.timestamp - updatedAt <= MAX_AGE, "stale price");
    return (answer, feed.decimals());
  }
}

TypeScript (viem)

Save as read-feed.mjs (or .mts and run with npx tsx), because the example uses top-level await. Some public XDC RPCs sit behind Cloudflare and refuse requests from Node's default User-Agent with HTTP 403, so the example sends a User-Agent and falls back across RPCs. In the browser, leave out the header (browsers send their own) and start with https://erpc.xinfin.network.

// read-feed.mjs (ES module: top-level await). Run: node read-feed.mjs
import { createPublicClient, http, fallback, parseAbi, formatUnits } from "viem";
import { xdc } from "viem/chains";

// Some public XDC RPCs refuse Node's default User-Agent (HTTP 403). In a browser, drop the headers option.
const UA = { "User-Agent": "Mozilla/5.0 (compatible; feed-reader/1.0)" };
const rpcs = ["https://erpc.xinfin.network", "https://rpc.xinfin.network", "https://rpc.xdcrpc.com"];
const client = createPublicClient({
  chain: xdc,
  transport: fallback(rpcs.map((u) => http(u, { fetchOptions: { headers: UA }, retryCount: 1, timeout: 8000 }))),
});
const abi = parseAbi([
  "function decimals() view returns (uint8)",
  "function latestRoundData() view returns (uint80,int256,uint256,uint256,uint80)",
]);
const feed = "0x2baa1AEfa06D58454c9c06C56436CdD5B7904248"; // PLI/USDT proxy, XDC mainnet (chainId 50)
const MAX_AGE = 3600; // seconds: pick per feed (FeedKit default for PLI/USDT)
const [, answer, , updatedAt] = await client.readContract({ address: feed, abi, functionName: "latestRoundData" });
const decimals = await client.readContract({ address: feed, abi, functionName: "decimals" });
const { timestamp } = await client.getBlock();
const age = Number(timestamp - updatedAt);
if (answer <= 0n) throw new Error("bad answer");
console.log(formatUnits(answer, decimals), "decimals", decimals, "age", age, "s", age <= MAX_AGE ? "fresh" : "STALE");

Public mainnet RPCs: https://erpc.xinfin.network, https://rpc.xinfin.network. https://rpc.xdcrpc.com answers without a key today, but XDC Gateway documents its endpoints as key-authenticated, so don't rely on it as a guaranteed keyless endpoint. The xinfin endpoints may return HTTP 403 to non-browser clients that send no User-Agent.

USD prices

Most crypto feeds quote USDT. For a USD value multiply by USDT/USD (0x5a792d09BAdeF90e599C13aff1552BF487Fe84B2) when both feeds are fresh, normalising decimals first: price18 = a18 * b18 / 1e18. For PLI: PLI/USD = PLI/USDT × USDT/USD. If you accept a stablecoin assumption (1 USDT = 1 USD), say so. As a sanity check, PLI/XDC × XDC/USD should agree with PLI/USDT × USDT/USD to within a few percent.

Public JSON API

The same snapshot this site uses, refreshed about every 5 minutes, CORS-enabled:

  • /api/feeds.json: all feeds with price, status, age, owner, aggregator
  • /api/summary.json: counts only
  • /api/history/<proxy-lowercase>.json: last 7 days of snapshots
curl -s https://feedplumb.com/api/feeds.json | jq '.feeds[] | select(.pair=="PLI/USDT")'

Free and best effort, without an SLA. Please cache responses and poll at most once a minute.