FeedPlumb FeedKit

Unaudited templates, testnet/fork/local only. Not an audit, not investment advice. VRF code under src/experimental/ is experimental and unsupported.

FeedKit: Foundry integration kit for Plugin (PLI) feeds on XDC

Small, MIT-licensed Solidity templates for reading Plugin oracle data on XDC Network. Testnet / fork / local only. Unaudited. No deploy scripts are included.

Contract Purpose
src/PluginOracleAdapter.sol Checked AggregatorV3-like read in the feed's native decimals (decimals(): most live feeds use 16 and some older ones 18, and Plugin's contracts allow others, such as 25; don't hard-code 8). Always-on round checks: answer > 0, updatedAt != 0 (and not in the future), answeredInRound >= roundId, max staleness. Optional min/max bounds (off by default). "No access" reverts → FeedAccessDenied(feed); other reverts → FeedReverted(feed, reason).
src/ChainlinkScaledAdapter.sol Optional wrapper that rescales any AggregatorV3-like source to 8 dp for Chainlink-style consumers.
src/PluginStalenessDefaults.sol Generated per-feed default max staleness (the adapter's revert cutoff; FeedPlumb's status rule is stale after 1.5 × heartbeat) (the 57 feeds that were live on XDC mainnet on 2026-10-21, with defaults from the update gaps observed that day; see the feed defaults table). Pass maxStaleness = 0 to use it; pass any non-zero value to override. Unknown feed + 0 reverts. Daily FX/stock/metal/RWA feeds default to 72 h (weekends) and marketHours(feed) flags them so you can apply a stricter weekday limit. knownReverting(feed) flags proxies that reverted on 2026-10-21. Defaults are a snapshot and will be refreshed from a longer observation window.
src/automation/PriceSnapshotUpkeep.sol Upkeep template (checkUpkeep/performUpkeep). performUpkeep is locked to an owner-set forwarder and re-checks its condition, so extra calls are harmless.
src/ERC4626RateGuard.sol Guarded ERC-4626 share price = asset price × convertToAssets(1 share). Bounds + max deviation vs a reference rate; only keeper/owner can move the reference rate, capped per update.
src/TwapFallback.sol Primary adapter price; falls back to a TWAP (ITwapSource) when the primary reverts or is out of bounds. Fallback is off by default (owner calls setFallbackEnabled(true)); window must be within [min, max]; checkpoint() is callable by anyone (bounded window, no value held).
src/XSwapV2TwapSource.sol ITwapSource over an XSwap V2 pair (UQ112x112 cumulative price), with a minimum quote-token reserve check.

Usage

forge build
forge test                                    # mock tests (fork tests skip)
# from the published archive (feedkit.tar.gz: src, test, foundry.toml, foundry.lock, LICENSE), first run:
#   forge install foundry-rs/forge-std@v1.17.0 --no-git
XDC_FORK_RPC=https://rpc.xinfin.network \
  forge test --match-path 'test/fork/*' -vv   # read-only XDC mainnet fork (alt: https://erpc.xinfin.network)
// XDC/USDT, 16 dp; maxStaleness 0 = table default (3600 s); no bounds
PluginOracleAdapter xdcUsdt = new PluginOracleAdapter(
    IPluginFeed(0x896CC6fE8AD04dF9f481332606c445768e286076), 0, 0, 0);
uint256 p16 = xdcUsdt.latestPrice();
// Chainlink-style 8 dp consumers:
ChainlinkScaledAdapter xdcUsdt8 = new ChainlinkScaledAdapter(IAggregatorV3Like(address(xdcUsdt)));

Note: this proxy's description() returns "0xUSDT", not "XDC/USDT", and it has a different owner from most feeds. Don't assert description() in constructors for this feed; check the proxy address instead.

Access-controlled feeds ("No access")

Some Plugin feed proxies only serve whitelisted callers and revert with "No access"; the adapter reports this as FeedAccessDenied(feed). Plugin's public docs do not describe a self-service whitelisting step. The routes they do give are the feeds portal (https://feeds.plugin.global, "Integrate Data Feeds" / request a feed) and the "Contact Us" link on the End data consumers pages (https://docs.plugin.global/plugin-data-feeds-platform/end-data-consumers-mainnet). Ask Plugin there to add your consumer contract address to the feed's access list, quoting the feed proxy address.

Licence

MIT (see the licence). Clean-room implementation. Code under src/experimental/ is unsupported.