Use this file to discover all available pages before exploring further.
Most integration errors fall into a small number of categories. This page covers every common failure mode — what causes it, how to diagnose it, and exactly how to fix it. Work through the relevant section before opening a support request.
Wrong symbol string format. Asset IDs are generated from "SYMBOL/USD" — uppercase, forward slash, no spaces. Any variation produces a different hash.
// ✅ Correctbytes32 id = keccak256(abi.encodePacked("USDT/USD"));// ❌ Wrong — lowercasebytes32 id = keccak256(abi.encodePacked("usdt/usd"));// ❌ Wrong — hyphen separatorbytes32 id = keccak256(abi.encodePacked("USDT-USD"));// ❌ Wrong — missing /USD suffixbytes32 id = keccak256(abi.encodePacked("USDT"));
Typo in hardcoded hex value. A single wrong character in a bytes32 hex string produces a completely different ID. Always verify hardcoded values against the Supported Assets page.Asset not yet supported. Not every stablecoin has a feed. Check the Supported Assets page for the current list. If the asset you need is not listed, request it.Wrong contract address. You may be pointing at the wrong network’s contract. Verify the address against the Contract Addresses page.
Threshold is tighter than the heartbeat interval. If your MAX_PRICE_AGE is shorter than the asset’s heartbeat interval, the price will always appear stale during calm market periods when no deviation trigger fires.
Asset
Heartbeat
Minimum Recommended MAX_PRICE_AGE
USDT/USD
1 hour
90 minutes (5400s)
USDC/USD
1 hour
90 minutes (5400s)
CNGN/USD
2 hours
3 hours (10800s)
ZARP/USD
2 hours
3 hours (10800s)
BRZ/USD
2 hours
3 hours (10800s)
ETH/USD
1 hour
90 minutes (5400s)
Genuine feed delay. If the actual feed age is significantly longer than the heartbeat interval across all assets, a relayer issue may be affecting the deployment. Check the IFÁ Labs Telegram for any incident announcements. If none exists, report it via support@ifalabs.com.Testing against testnet during low activity. Testnet relayers are less active than mainnet. Testnet feed ages can be significantly longer during low-traffic periods. Use wider thresholds on testnet and tighten for mainnet.
Forgot to apply decimal scaling.info.price is not a dollar amount. It is a scaled integer. 1000124000000000000 is $1.000124, not $1 quadrillion.
// ✅ Correctuint256 humanPrice = uint256(info.price) / 1e18;// ❌ Wrong — using raw price directly as a dollar amountuint256 humanPrice = uint256(info.price); // 1e18 times too large
Hardcoded divisor doesn’t match actual decimal. All current feeds use decimal = -18, but always read decimal dynamically. Hardcoding 1e18 when the actual decimal is different produces a wrong result.
// ✅ Future-proof — reads decimal dynamicallyuint256 humanPrice = uint256(info.price) / (10 ** uint8(-info.decimal));// ⚠️ Fragile — works now but breaks if a future feed uses different decimalsuint256 humanPrice = uint256(info.price) / 1e18;
Floating-point overflow in JavaScript.info.price as a BigInt from ethers v6 may overflow JavaScript’s Number type if converted naively. Use ethers.formatUnits or keep values as BigInt.
// ✅ Correct — using ethers.formatUnitsconst price = ethers.formatUnits(info.price, -info.decimal);// ✅ Correct — using BigInt arithmeticconst price = info.price * 1000000n / (10n ** BigInt(-info.decimal));// ❌ Wrong — Number() may lose precision for large BigInt valuesconst price = Number(info.price) / 1e18; // may lose precision
Multiplying two scaled values without normalizing. If both values are scaled by 1e18, the product is scaled by 1e36. Always divide after multiplying.
// ✅ Correctuint256 result = (amount * uint256(info.price)) / 1e18;// ❌ Wrong — result is 1e18 times too largeuint256 result = amount * uint256(info.price);
Oracle reads inside a loop. Reading from the oracle inside a loop multiplies the gas cost by the loop length.
// ✅ Correct — read once before the loop(IIfaPriceFeed.PriceFeed memory info,) = ORACLE.getAssetInfo(USDT_ASSET_ID);for (uint256 i = 0; i < positions.length; i++) { positions[i].value = _calculate(info.price, info.decimal, positions[i].amount);}// ❌ Wrong — reads oracle on every iterationfor (uint256 i = 0; i < positions.length; i++) { (IIfaPriceFeed.PriceFeed memory info,) = ORACLE.getAssetInfo(USDT_ASSET_ID); positions[i].value = _calculate(info.price, info.decimal, positions[i].amount);}
Multiple individual reads instead of batch. Use getAssetsInfo for any function that needs more than one price.
// ✅ Correct — one call for three pricesbytes32[] memory ids = new bytes32[](3);ids[0] = USDT_ASSET_ID;ids[1] = USDC_ASSET_ID;ids[2] = CNGN_ASSET_ID;(IIfaPriceFeed.PriceFeed[] memory infos,) = ORACLE.getAssetsInfo(ids);// ❌ Wrong — three separate calls(IIfaPriceFeed.PriceFeed memory usdt,) = ORACLE.getAssetInfo(USDT_ASSET_ID);(IIfaPriceFeed.PriceFeed memory usdc,) = ORACLE.getAssetInfo(USDC_ASSET_ID);(IIfaPriceFeed.PriceFeed memory cngn,) = ORACLE.getAssetInfo(CNGN_ASSET_ID);
Oracle address or asset IDs stored in storage. Declare constants at compile time to avoid cold SLOAD costs on every call.
// ✅ Correct — zero runtime costIIfaPriceFeed public constant ORACLE = IIfaPriceFeed(0xA9F17344689C2c2328F94464998db1d3e35B80dC);bytes32 public constant USDT_ASSET = 0x6ca0cef6107263f3b09a51448617b659278cff744f0e702c24a2f88c91e65a0d;// ❌ Wrong — 2,100 gas cold SLOAD on every accessIIfaPriceFeed public oracle;bytes32 public usdtAsset;
All input arrays to batch pair functions must be the same length. Check _assetIndexes0, _assetsIndexes1, and (for getPairsbyId) _directions — all must have identical lengths.
// ✅ Correct — all arrays same lengthbytes32[] memory assets0 = new bytes32[](2);bytes32[] memory assets1 = new bytes32[](2);assets0[0] = CNGN_ASSET_ID; assets1[0] = USDT_ASSET_ID;assets0[1] = ZARP_ASSET_ID; assets1[1] = USDC_ASSET_ID;// ❌ Wrong — mismatched lengthsbytes32[] memory assets0 = new bytes32[](2);bytes32[] memory assets1 = new bytes32[](1); // length mismatch → InvalidAssetIndexLength
Underlying feed missing or stale. If either underlying USD feed is unavailable or has a zero price, the derived pair calculation reverts. Verify both feeds exist and are fresh before calling pair functions.
// Check both feeds before calling getPairbyId(IIfaPriceFeed.PriceFeed memory info0, bool exists0) = ORACLE.getAssetInfo(assetId0);(IIfaPriceFeed.PriceFeed memory info1, bool exists1) = ORACLE.getAssetInfo(assetId1);require(exists0 && exists1, "IFA: one or both feeds missing");require(info0.price > 0, "IFA: feed0 price is zero");require(info1.price > 0, "IFA: feed1 price is zero");// Safe to call derived pairORACLE.getPairbyId(assetId0, assetId1, IIfaPriceFeed.PairDirection.Forward);
Block range too narrow. Stablecoin feeds update infrequently — a 1,000-block range may contain no updates. Use at least 50,000 blocks for historical queries.
Asset ID not passed as filter. Calling queryFilter without an asset ID filter returns all PriceUpdated events — not filtered by asset. Pass the asset ID as the first argument to the filter.
// ✅ Correct — filtered by asset IDoracle.filters.PriceUpdated(CNGN_ASSET_ID)// ❌ Returns all PriceUpdated events — may be a very large result setoracle.filters.PriceUpdated()
Wrong contract address. Querying a testnet contract for mainnet events returns nothing. Confirm the oracle address matches the network you intend.
The public Base RPC endpoint (https://mainnet.base.org) is rate-limited and not suitable for production monitoring or high-frequency queries. Switch to a dedicated provider:
Testnet oracle prices reflect testnet relayer activity — they are independent of mainnet. Testnet feeds may be less fresh, use slightly different source data, or differ from mainnet during low-activity periods. This is expected behaviour.Confirm you are using the correct addresses per network:
Network
Oracle Address
Base Mainnet
0xA9F17344689C2c2328F94464998db1d3e35B80dC
Base Sepolia
0xbF2ae81D8Adf3AA22401C4cC4f0116E936e1025b
AssetChain Testnet
0xBAc31e568883774A632275F9c8E7A5Bd117000F7
Asset IDs are the same across all networks — only the contract address changes.