Timeseries and aggregations enable your subgraph to track statistics like daily average price, hourly total transfers, etc.
This feature introduces two new types of subgraph entity. Timeseries entities record data points with timestamps. Aggregation entities perform pre-declared calculations on the Timeseries data points on an hourly or daily basis, then store the results for easy access via GraphQL.
Timeseries entities are defined with @entity(timeseries: true) in schema.graphql, while aggregation entities are defined with @aggregation.
Every timeseries entity must have a unique ID of the int8 type, a timestamp of the Timestamp type, and include data that will be used for calculation by aggregation entities.
These Timeseries entities can be saved in regular trigger handlers, and act as the “raw data” for the Aggregation entities.
Every aggregation entity defines the source from which it will gather data (which must be a Timeseries entity), sets the intervals (e.g., hour, day), and specifies the aggregation function it will use (e.g., sum, count, min, max, first, last).
Aggregation entities are automatically calculated on the basis of the specified source at the end of the required interval.
Example GraphQL query
{ stats(interval: "hour", where: { timestamp_gt: 1704085200 }) { id timestamp sum }}
Topic filters, also known as indexed argument filters, allow developers to precisely filter blockchain events based on the values of their indexed arguments.
When a smart contract emits an event, any arguments that are marked as indexed can be used as filters in a subgraph's manifest. This allows the subgraph to listen selectively for events that match these indexed arguments.
The Ethereum Virtual Machine (EVM) allows up to three indexed arguments per event.
The first indexed argument corresponds to topic1, the second to topic2, and third to topic3
Token.sol
// SPDX-License-Identifier: MITpragmasolidity ^0.8.0;contract Token {// Event declaration with indexed parameters for addresseseventTransfer(addressindexed from, addressindexed to, uint256 value);// Function to simulate transferring tokensfunctiontransfer(address to,uint256 value) public {// Emitting the Transfer event with from, to, and valueemitTransfer(msg.sender, to, value); }}
Topic filters are defined directly within the event handler configuration in the subgraph manifest.
subgraph.yaml with Topic 1 and Topic 2 filters for Transfer event
eventHandlers: - event:Transfer(indexed address,indexed address,uint256)handler:handleDirectedTransfertopic1: ['0xAddressA'] # Sender Address, can include >1 input address (comma separated)topic2: ['0xAddressB'] # Receiver Address, can include >1 input address (comma separated)
In this configuration:
topic1 is configured to filter Transfer events where 0xAddressA is the sender.
topic2 is configured to filter Transfer events where 0xAddressB is the receiver.
The subgraph will only index transactions that occur directly from 0xAddressA to 0xAddressB.
Declarative eth_calls
Allows eth_calls to be executed ahead of time, enabling graph-node to execute them in parallel.
Requires: SpecVersion >= 1.2.0. Currently, eth_calls can only be declared for event handlers.
Declared eth_calls can access the event.address of the underlying event as well as all the event.params.
Functionality that allows subgraphs to query and leverage data from other subgraphs. This can be achieved by creating a separate blockstream that fetches triggers from subgraph database tables. New entities will be triggers.
i.e. required subgraph definition files vs optional IPFS File Data Sources
Ideas
Early stage concepts that may never reach production
Plug-in system
Generic structure
subgraph.yaml
specVersion:x.y.zdescription:Subgraph with Foo and Bar plug-insrepository:https://github.com/graphprotocol/graph-toolingschema:file:./schema.graphqlindexerHints:prune:autoplugIns: - Foo - Bar
specVersion:x.y.zdescription:'Information about Ethereum Mainnet Blocks'repository:https://github.com/graphprotocol/graph-toolingschema:file:./schema.graphqlplugIns: - ChronicledataSources: - kind:ethereum/contractname:Contractnetwork:mainnetsource:# We don't realy need a contract, just use Gravatar to have something# hereaddress:'0x2E645469f354BB4F5c8a05B3b30A929361cf77eC'abi:ContractstartBlock:0mapping:kind:ethereum/eventsapiVersion:0.0.7language:wasm/assemblyscriptentities: - Blockabis: - name:Contractfile:./abis/Contract.jsonblockHandlers: - handler:handleBlockfile:./src/mappings/blocks.ts
schema.graphql
typeBlock@entity(timeseries: true) { # Timeseries must have an id of type Int8 id: Int8! # Timeseries must have a timestamp field timestamp: Timestamp! # The block hash hash: Bytes! # The block number number: Int! # Price in USD price: BigInt@chronicle(oracle: "0x46ef0071b1E2fF6B42d36e5A177EA43Ae5917f4E", arg: "number")}typeStats@aggregation(intervals: ["hour", "day"], source: "Block") { # The id; it is the id of one of the data points that were aggregated into # this bucket, but which one is undefined and should not be relied on id: Int8! # The timestamp of the bucket is always the timestamp of the beginning of # the interval timestamp: Timestamp! # The aggregates count: Int!@aggregate(fn: "count") maxPrice: BigInt@aggregate(fn: "max", arg: "price")}
src/mappings/blocks.ts
import { ethereum } from'@graphprotocol/graph-ts';import { Block } from'../../generated/schema';exportfunctionhandleBlock(block:ethereum.Block):void {// The id for timeseries is autogenerated; even if we set it to a real// value, it would be silently overwrittenlet blockEntity =newBlock('auto');let number =block.number.toI32();blockEntity.hash =block.hash;blockEntity.number = number;blockEntity.timestamp =block.timestamp.toI32();blockEntity.save();}
specVersion:x.y.zdescription:'Information about Ethereum Mainnet Blocks'repository:https://github.com/graphprotocol/graph-toolingschema:file:./schema.graphqlplugIns: - AggregatorV3InterfacedataSources: - kind:ethereum/contractname:Contractnetwork:mainnetsource:# We don't realy need a contract, just use Gravatar to have something# hereaddress:'0x2E645469f354BB4F5c8a05B3b30A929361cf77eC'abi:ContractstartBlock:0mapping:kind:ethereum/eventsapiVersion:0.0.7language:wasm/assemblyscriptentities: - Blockabis: - name:Contractfile:./abis/Contract.jsonblockHandlers: - handler:handleBlockfile:./src/mappings/blocks.ts
schema.graphql
typeBlock@entity(timeseries: true) { # Timeseries must have an id of type Int8 id: Int8! # Timeseries must have a timestamp field timestamp: Timestamp! # The block hash hash: Bytes! # The block number number: Int! # Price in USD price: BigInt@aggregatorv3interface(feed: "0x694AA1769357215DE4FAC081bf1f309aDC325306", arg: "number")}typeStats@aggregation(intervals: ["hour", "day"], source: "Block") { # The id; it is the id of one of the data points that were aggregated into # this bucket, but which one is undefined and should not be relied on id: Int8! # The timestamp of the bucket is always the timestamp of the beginning of # the interval timestamp: Timestamp! # The aggregates count: Int!@aggregate(fn: "count") maxPrice: BigInt@aggregate(fn: "max", arg: "price")}
src/mappings/blocks.ts
import { ethereum } from'@graphprotocol/graph-ts';import { Block } from'../../generated/schema';exportfunctionhandleBlock(block:ethereum.Block):void {// The id for timeseries is autogenerated; even if we set it to a real// value, it would be silently overwrittenlet blockEntity =newBlock('auto');let number =block.number.toI32();blockEntity.hash =block.hash;blockEntity.number = number;blockEntity.timestamp =block.timestamp.toI32();blockEntity.save();}