Verification checklist
- At one block, read the feed's decimals(), the answer from latestRoundData, and the token's decimals(). Do not substitute the token's decimals for the feed scale.
- The human price is the answer divided by 10 to the power of the feed's decimals. If the answer is less than or equal to 0, stop. Do not call it a price.
- If the note divided by the token's decimals and the two integers differ, the price is not usable. A fresh updatedAt, a finished grace period, and a quiet circuit breaker do not change that.
- If the two decimals happen to match, record both. Equality is not a reason to skip the feed's decimals().
1. The two decimals functions do not answer the same question
A common sentence in a note is “updatedAt is only a few seconds old, so the price is usable.” On a fixed-point feed that sentence is missing a unit. Chainlink's guide to consuming data feeds says the answer from latestRoundData is an integer, and the decimal point is not in it. The BTC/USD sample on that page uses 8 decimal places: an answer of 3030914000000 is a human price of 30309.14. The same passage says each feed uses its own number of decimal places. The API reference defines decimals() as the number of decimals in the stored answer, the fixed-point scale of the response.
Token decimals are a different unit. ERC-20 decimals say how to turn a token balance into a human amount. A return of 8 means divide the raw balance by 100000000. That function answers “how many tokens,” not “where does this price put its decimal point.” This note does not repeat the feed-delay, sequencer-grace, or circuit-breaker checks. Those assume the scale is already right. Here the round is assumed fresh, and the question is whether the divisor is wrong. It is also not the reserve-decimals step in a pool-depth note. That step converts two token balances into amounts before an implied price. This one converts the price answer itself before any collateral math.
2. One integer, two divisors, two orders of magnitude
Start with the documented sample. 3030914000000 divided by 10 to the 8th is 30309.14. If the note instead uses the collateral token's decimals, and that call returns 18, the same integer divided by 10 to the 18th is 0.000003030914. The factor is 10 to the power of (18 minus 8), which is 10 billion. The price has been written at another order of magnitude. Rounding does not explain it. The first row of the table is the documented sample. The second is that sample divided by an 18-decimal token scale, as a teaching step.
| Field | Value | Reading |
|---|---|---|
| Documented answer | 3030914000000 | Fixed-point integer; the point is not in it |
| Feed decimals | 8 | Scale of that BTC/USD sample |
| Human price | 30309.14 | Answer divided by 10 to the 8th |
| Token decimals | 18 | Teaching assumption, not the feed's return |
| Price after the wrong divisor | 0.000003030914 | Answer divided by 10 to the 18th |
| Error factor | 10 billion | 10 to the (18 minus 8); the price is shrunk |
The direction can reverse. A second teaching case, not aimed at any live feed: feed decimals are 8, token decimals are 6, and the answer is 99990000. Divided by 10 to the 8th, that is 0.9999. Divided by 10 to the 6th, that is 99.99. The factor is 100, and the price is enlarged. The rule has to state a direction, not a vague “decimals were a bit off.” When token decimals are greater than the feed scale, using the token scale shrinks the price. When token decimals are smaller, it enlarges the price.
The easy miss is when both calls return 8. Using the token's decimals then produces the same human price by accident. If the note promotes that accident into a template, the next pair is 18 against 8, or 6 against 8. The template still runs, and updatedAt still looks fresh. Collateral value, health factor, and net asset value all move by the same power of ten. One unit, already converted with the token's decimals, times the right price is about 30309.14 quote units. Times the wrong price it is about 0.000003030914. That gap is not slippage. It is a different market.
3. The scale lives on this feed, not on another page's 8
The getting-started page stresses that each feed can use a different number of decimals. Copying the 8 from the BTC/USD sample onto another pair, or onto a new deployment, is not yet a check. decimals() has to be read in the same block as latestRoundData. The aggregator behind a proxy can change. description helps confirm the pair name. It does not replace the integer you just read. A note that hard-codes “USD quotes are 8” will meet a feed quoted in another asset whose scale is not 8. Once the constant is frozen, a fresh updatedAt stamps the wrong scale.
The answer type is a signed integer. If it is less than or equal to 0, stop. Do not divide a non-positive value and call the result a price. A correct scale also does not replace freshness. The heartbeat, the grace period, and the circuit breaker still belong to their own notes. The reverse is also true: passing all three does not repair a wrong power of ten. A panel that already shows a human price is a separate hazard. Dividing 30309.14 by 10 to the 8th again is a second scaling. Treating the raw answer as whole quote units multiplies the price by 10 to the 8th. The record has to say whether the integer in hand is the raw answer or an already scaled result. Two numbers that look like prices can be this feed's human result and another feed's raw integer.
4. A screen: the wrong divisor means a fresh updatedAt is not usable
The teaching screen is ordered, and steps cannot be skipped. First, if the answer is less than or equal to 0, it is not a usable price. Second, the human price may use only the feed's decimals as the divisor. If the note used the token's decimals and the two integers differ, the price is not usable. Third, if the two integers match, record both and continue. A match is a result for this pair, not a reason to skip the feed read. Fourth, only after those three steps pass do updatedAt, the grace period, and a deviation go to the other screens. When the first two fail, a tidy heartbeat does not change the result.
Write the error factor as 10 to the power of (token decimals minus feed decimals). A difference of 0 gives a factor of 1, but only after both integers were read at the same block. A panel price with no raw answer and no decimals() is an unchecked cell, not a pass. The screen does not say how old the round is, whether a sequencer grace period has ended, or whether a deviation will trip a breaker. It only asks whether the decimal point on this human price was placed with this feed's own scale. Passing once does not mean the next read still has the same scale. After an aggregator change, read decimals() again. Do not reuse the 8 from the previous note.
5. Keep a record another researcher can replay
The minimum fields are: chain id, block number and hash, feed address, token address, feed decimals, token decimals, answer, the human price from the feed scale, and, if someone divided by the token scale, that other figure and the factor. Also record whether the answer is positive, and updatedAt. updatedAt is archived. It does not stand in for the scale. If someone hard-coded 8, record which page and which feed that 8 came from. Two RPCs a block apart are two states. Do not pair a new answer with an old decimals.
Reconcile in a fixed order: both decimals, then the sign of the answer, then division by the feed scale, then any formula that used the token scale, and only then freshness, grace, and the breaker. A note that never reads the feed's decimals() stops at “scale missing.” Do not write “price verified.” If reproducing a position's valuation means holding a small amount of the token, put the swap in a fee footnote. It does not belong in the scale verdict.
6. A usable price has its decimal point in the right place
The weak sentence is “updatedAt is fresh, so this price is usable.” A useful one is “feed decimals; token decimals; answer; human price equals the answer divided by 10 to the feed scale; if the divisor was the token scale and the two differ, the price is not usable.” A heartbeat, a grace period, and a circuit breaker do not repair the scale. Matching decimals only mean this pair matches now. They do not mean the template may drop one of the two reads.
This is a research framework. The figures 3030914000000 and 30309.14 are the BTC/USD sample in Chainlink's getting-started page. The other rows are hypothetical. No live feed was measured, and this is not investment advice.