Report schema
Every DataLink report carries report data: nine fields, ABI-encoded in a fixed order. This page describes those fields, how they're encoded, and how to decode them.
The nine fields
| # | Field | ABI type | Meaning |
|---|---|---|---|
| 1 | feedId | bytes32 | The feed this report belongs to |
| 2 | observationsTimestamp | uint32 | When the values were observed (Unix seconds) |
| 3 | benchmarkPrice | int192 | The main price, as an integer — see Decimals |
| 4 | bid | int192 | Bid price, same decimals |
| 5 | ask | int192 | Ask price, same decimals |
| 6 | currentBlockNum | uint64 | XDC block number recorded with the observation |
| 7 | currentBlockHash | bytes32 | Hash of that block |
| 8 | validFromBlockNum | uint64 | First block number this report covers |
| 9 | currentBlockTimestamp | uint64 | Timestamp of that block (Unix seconds) |
The order is fixed: decoding with a different order or different types produces wrong values without any error.
For sources without an order book (for example a median sale price), bid and
ask carry the same value as benchmarkPrice.
Decimals
Prices are integers. Divide by 10^decimals to get the real value. The number of
decimals is set per feed by its report schema:
- the decoded API returns it in
schema.fields[].decimals; - the Consumer portal shows it on the feed's Feed Access & Integration page.
With 8 decimals, 8157000000 means 81.57.
Example: decoded report
GET /api/v1/reports/decoded?feedID=… returns the report's fields as JSON. A real
report (feed "DataLink SOL Test Provider", 8 decimals):
JSON
{
"createdAt": 1794519775,
"decoded": {
"feedId": "0001d1fb86f34da1314b728dfd840d31cf3a25bae5a53bcd5251ce7e0e909918",
"observationsTimestamp": 1794519770,
"benchmarkPrice": "8157000000",
"bid": "8157000000",
"ask": "8158000000",
"currentBlockNum": 109335603,
"currentBlockHash": "9534790fceb7a7338d052818573dbff63546c0bd98969d709d1907cf1a5ae2e4",
"validFromBlockNum": 109335574,
"currentBlockTimestamp": 1794519768
}
}| Field | Raw | Value (8 decimals) |
|---|---|---|
benchmarkPrice | 8157000000 | 81.57 |
bid | 8157000000 | 81.57 |
ask | 8158000000 | 81.58 |
observationsTimestamp | 1794519770 | Unix seconds |
The decoded endpoint also returns a schema block with each field's label, type,
decimals and the decoded key that carries it.
createdAt is not part of the report
createdAt is API metadata: when the report was stored by the DataLink service.
It is not encoded in the report data and is not covered by the signatures.
The time the values were observed is observationsTimestamp — use that for
freshness checks. (In the example, the values were observed at 1794519770 and the
report stored five seconds later.)
The signed payload
The payload from GET /api/v1/reports/latest wraps the report data together with
the signatures:
Solidity
(bytes32[3] reportContext, bytes reportData, bytes32[] rs, bytes32[] ss, bytes32 rawVs)| Part | Meaning |
|---|---|
reportContext | reportContext[0] is the configuration digest; the VerifierProxy uses it to find the right Verifier |
reportData | The nine fields above (288 bytes: nine 32-byte words) |
rs, ss, rawVs | The oracle signatures |
You don't decode the payload yourself to verify it: pass it unchanged to the
VerifierProxy, which returns reportData once the signatures are valid.
Decoding with abi.decode
ABI encoding stores each of these nine fields in its own 32-byte word, in order.
abi.decode reads them back when you give it the same types in the same order.
In Solidity, after verification:
Solidity
struct Report {
bytes32 feedId;
uint32 observationsTimestamp;
int192 benchmarkPrice;
int192 bid;
int192 ask;
uint64 currentBlockNum;
bytes32 currentBlockHash;
uint64 validFromBlockNum;
uint64 currentBlockTimestamp;
}
bytes memory reportData = verifierProxy.verify(payload, "");
Report memory report = abi.decode(reportData, (Report));In JavaScript (with viem), straight from a signed payload — useful for inspecting a report off-chain (this does not check the signatures):
JavaScript
import { decodeAbiParameters } from "viem";
const [, reportData] = decodeAbiParameters(
[{ type: "bytes32[3]" }, { type: "bytes" }, { type: "bytes32[]" }, { type: "bytes32[]" }, { type: "bytes32" }],
payload // "0x…" from /api/v1/reports/latest
);
const [feedId, observationsTimestamp, benchmarkPrice, bid, ask, currentBlockNum, currentBlockHash, validFromBlockNum, currentBlockTimestamp] =
decodeAbiParameters(
[
{ name: "feedId", type: "bytes32" },
{ name: "observationsTimestamp", type: "uint32" },
{ name: "benchmarkPrice", type: "int192" },
{ name: "bid", type: "int192" },
{ name: "ask", type: "int192" },
{ name: "currentBlockNum", type: "uint64" },
{ name: "currentBlockHash", type: "bytes32" },
{ name: "validFromBlockNum", type: "uint64" },
{ name: "currentBlockTimestamp", type: "uint64" },
],
reportData
);
// benchmarkPrice is a BigInt, e.g. 8157000000n → 81.57 with 8 decimals