Skip to content

Commit 60cadf0

Browse files
committed
feat: add docs
1 parent bc336af commit 60cadf0

File tree

13 files changed

+284
-25
lines changed

13 files changed

+284
-25
lines changed

README.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# Subgraph Composition Example
2+
3+
This repository provides a minimal example of how to use the new subgraph composition feature of [The Graph][0].
4+
5+
## What is The Graph?
6+
7+
[The Graph][0] is a powerful, decentralized protocol that enables seamless querying and indexing of blockchain data.
8+
It simplifies the complex process of querying blockchain data, making DApp development faster and easier.
9+
10+
## What is subgraph composition?
11+
12+
Subgraph composition allows a subgraph to depend on another subgraph as a data source, allowing one subgraph to consume
13+
and react to the data or entity changes of another subgraph. This feature enables modular subgraph architectures.
14+
15+
Instead of interacting directly with on-chain data, a subgraph can be set up to listen to updates from another subgraph
16+
and react to entity changes. This can be used for a variety of use cases, such as aggregating data from multiple
17+
subgraphs or triggering actions based on changes in external subgraph entities.
18+
19+
## How to use subgraph composition?
20+
21+
The example defines 3 source subgraphs:
22+
- [block-time-subgraph](./block-time-subgraph) - a subgraph that calculates the block time for each block
23+
- [block-cost-subgraph](./block-cost-subgraph) - a subgraph that indexes the cost of each block
24+
- [block-size-subgraph](./block-size-subgraph) - a subgraph that indexes the size of each block
25+
26+
and a composed subgraph that combines and aggregates the information from the 3 source subgraphs:
27+
28+
- [block-stats-subgraph](./block-stats-subgraph) - a subgraph that collects statistics about blocks
29+
30+
[0]: https://thegraph.com/

block-cost-subgraph/README.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# Block Cost Subgraph
2+
3+
This is a subgraph that indexes the cost of each block.
4+
5+
## Local Deployment
6+
7+
### Requirements
8+
9+
These are minimal requirements to deploy this subgraph locally:
10+
11+
- A [graph-node][0] instance running locally
12+
- An [IPFS][1] instance running locally
13+
- [Node.js][2] and npm
14+
15+
### Deployment
16+
17+
To deploy this subgraph locally, run the following commands:
18+
19+
```bash
20+
npm install
21+
npm run codegen
22+
npm run build
23+
npm run create-local
24+
npm run deploy-local
25+
```
26+
27+
[0]: https://github.com/graphprotocol/graph-node
28+
[1]: https://docs.ipfs.tech/
29+
[2]: https://nodejs.org/

block-cost-subgraph/subgraph.yaml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
# Subgraph composition is supported as of 1.3.0 spec version.
12
specVersion: 1.3.0
23
description: A subgraph that indexes the cost of each block.
34
schema:
@@ -7,15 +8,22 @@ dataSources:
78
name: BlockDataSource
89
network: mainnet
910
source:
11+
# We are only using block data in the example, so an empty contract address is fine.
1012
address: "0x0000000000000000000000000000000000000000"
13+
14+
# The same is true for ABI, an empty event is provided.
1115
abi: BlockDataSource
16+
17+
# Randomly selected at a point of high activity on the network.
1218
startBlock: 10000000
1319
mapping:
1420
kind: ethereum/events
1521
apiVersion: 0.0.7
1622
language: wasm/assemblyscript
1723
entities:
1824
- BlockCost
25+
26+
# We are only using block data in the example, so an empty event is fine.
1927
abis:
2028
- name: BlockDataSource
2129
file: ./abis/BlockDataSource.json

block-size-subgraph/README.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# Block Size Subgraph
2+
3+
This is a subgraph that indexes the size of each block.
4+
5+
## Local Deployment
6+
7+
### Requirements
8+
9+
These are minimal requirements to deploy this subgraph locally:
10+
11+
- A [graph-node][0] instance running locally
12+
- An [IPFS][1] instance running locally
13+
- [Node.js][2] and npm
14+
15+
### Deployment
16+
17+
To deploy this subgraph locally, run the following commands:
18+
19+
```bash
20+
npm install
21+
npm run codegen
22+
npm run build
23+
npm run create-local
24+
npm run deploy-local
25+
```
26+
27+
[0]: https://github.com/graphprotocol/graph-node
28+
[1]: https://docs.ipfs.tech/
29+
[2]: https://nodejs.org/

block-size-subgraph/src/mapping.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { ethereum , BigInt} from "@graphprotocol/graph-ts";
1+
import { BigInt, ethereum } from "@graphprotocol/graph-ts";
22
import { BlockSize } from "../generated/schema";
33

44
export function handleBlock(block: ethereum.Block): void {

block-size-subgraph/subgraph.yaml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
# Subgraph composition is supported as of 1.3.0 spec version.
12
specVersion: 1.3.0
23
description: A subgraph that indexes the size of each block.
34
schema:
@@ -7,15 +8,22 @@ dataSources:
78
name: BlockDataSource
89
network: mainnet
910
source:
11+
# We are only using block data in the example, so an empty contract address is fine.
1012
address: "0x0000000000000000000000000000000000000000"
13+
14+
# The same is true for ABI, an empty event is provided.
1115
abi: BlockDataSource
16+
17+
# Randomly selected at a point of high activity on the network.
1218
startBlock: 10000000
1319
mapping:
1420
kind: ethereum/events
1521
apiVersion: 0.0.7
1622
language: wasm/assemblyscript
1723
entities:
1824
- BlockSize
25+
26+
# We are only using block data in the example, so an empty event is fine.
1927
abis:
2028
- name: BlockDataSource
2129
file: ./abis/BlockDataSource.json

block-stats-subgraph/README.md

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# Block Stats Subgraph
2+
3+
This is a composed subgraph that combines and aggregates the information from the 3 source subgraphs.
4+
5+
To keep this example as simple as possible, all source subgraphs use only block handlers, but in a real environment
6+
each source subgraph will use data from different smart contracts.
7+
8+
> [!NOTE]
9+
> Any change to a source subgraph will most likely generate a new deployment ID. Be sure to update the deployment ID in
10+
> the data source address of the subgraph manifest to take advantage of the latest changes.
11+
12+
## Local Deployment
13+
14+
### Requirements
15+
16+
These are minimal requirements to deploy this subgraph locally:
17+
18+
- A [graph-node][0] instance running locally
19+
- An [IPFS][1] instance running locally
20+
- [Node.js][2] and npm
21+
22+
### Deployment
23+
24+
> [!IMPORTANT]
25+
> All source subgraphs should be deployed before the composed subgraph is deployed.
26+
27+
To deploy this subgraph locally, run the following commands:
28+
29+
```bash
30+
npm install
31+
npm run codegen
32+
npm run build
33+
npm run create-local
34+
npm run deploy-local
35+
```
36+
37+
[0]: https://github.com/graphprotocol/graph-node
38+
[1]: https://docs.ipfs.tech/
39+
[2]: https://nodejs.org/

block-stats-subgraph/schema.graphql

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
# This type is used to collect data from multiple source subgraphs
2+
# before saving the final immutable data for aggregation.
3+
#
4+
# Each trigger will partially fill the block data.
15
type BlockDataSource @entity {
26
id: ID!
37
number: BigInt!
@@ -6,8 +10,12 @@ type BlockDataSource @entity {
610
size: BigInt
711
}
812

13+
# The immutable data used as the source for aggregation.
914
type Block @entity(timeseries: true) {
15+
# This field is required and will be filled in automatically.
1016
id: Int8!
17+
18+
# This field is required and will be filled in automatically.
1119
timestamp: Timestamp!
1220

1321
hash: Bytes!
@@ -17,34 +25,58 @@ type Block @entity(timeseries: true) {
1725
size: BigInt!
1826
}
1927

28+
# This type defines our aggregation on the immutable block data.
29+
# Aggregation buckets are created automatically, and there is no need to define anything in the mappings.
30+
#
31+
# There is no hard limit on the number of aggregates an aggregation type can have,
32+
# but there may be a performance penalty if too many aggregates are created.
2033
type Stats @aggregation(intervals: ["hour", "day"], source: "Block") {
34+
# This field is required.
2135
id: Int8!
36+
37+
# This field is required.
2238
timestamp: Timestamp!
2339

40+
# The total number of source values used in an aggregation bucket.
2441
count: Int! @aggregate(fn: "count")
2542

43+
# By default, each aggregate starts at 0 for each new bucket.
44+
# Therefore, it only aggregates over the time interval for the bucket.
2645
minBlockTime: BigInt! @aggregate(fn: "min", arg: "blockTime")
2746
maxBlockTime: BigInt! @aggregate(fn: "max", arg: "blockTime")
2847
sumBlockTime: BigInt! @aggregate(fn: "sum", arg: "blockTime")
2948
firstBlockTime: BigInt! @aggregate(fn: "first", arg: "blockTime")
3049
lastBlockTime: BigInt! @aggregate(fn: "last", arg: "blockTime")
50+
51+
# Cumulative aggregations will aggregate over the entire time series
52+
# up to the end of the time interval for the bucket.
3153
allTimeMinBlockTime: BigInt! @aggregate(fn: "min", arg: "blockTime", cumulative: true)
3254
allTimeMaxBlockTime: BigInt! @aggregate(fn: "max", arg: "blockTime", cumulative: true)
3355

56+
# By default, each aggregate starts at 0 for each new bucket.
57+
# Therefore, it only aggregates over the time interval for the bucket.
3458
minGasUsed: BigInt! @aggregate(fn: "min", arg: "gasUsed")
3559
maxGasUsed: BigInt! @aggregate(fn: "max", arg: "gasUsed")
3660
sumGasUsed: BigInt! @aggregate(fn: "sum", arg: "gasUsed")
3761
firstGasUsed: BigInt! @aggregate(fn: "first", arg: "gasUsed")
3862
lastGasUsed: BigInt! @aggregate(fn: "last", arg: "gasUsed")
63+
64+
# Cumulative aggregations will aggregate over the entire time series
65+
# up to the end of the time interval for the bucket.
3966
totalGasUsed: BigInt! @aggregate(fn: "sum", arg: "gasUsed", cumulative: true)
4067
allTimeMinGasUsed: BigInt! @aggregate(fn: "min", arg: "gasUsed", cumulative: true)
4168
allTimeMaxGasUsed: BigInt! @aggregate(fn: "max", arg: "gasUsed", cumulative: true)
4269

70+
# By default, each aggregate starts at 0 for each new bucket.
71+
# Therefore, it only aggregates over the time interval for the bucket.
4372
minSize: BigInt! @aggregate(fn: "min", arg: "size")
4473
maxSize: BigInt! @aggregate(fn: "max", arg: "size")
4574
sumSize: BigInt! @aggregate(fn: "sum", arg: "size")
4675
firstSize: BigInt! @aggregate(fn: "first", arg: "size")
4776
lastSize: BigInt! @aggregate(fn: "last", arg: "size")
77+
78+
# Cumulative aggregations will aggregate over the entire time series
79+
# up to the end of the time interval for the bucket.
4880
totalSize: BigInt! @aggregate(fn: "sum", arg: "size", cumulative: true)
4981
allTimeMinSize: BigInt! @aggregate(fn: "min", arg: "size", cumulative: true)
5082
allTimeMaxSize: BigInt! @aggregate(fn: "max", arg: "size", cumulative: true)

block-stats-subgraph/src/mapping.ts

Lines changed: 15 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {Bytes, EntityOp, EntityTrigger, store} from "@graphprotocol/graph-ts";
1+
import {BigInt, Bytes, EntityOp, EntityTrigger, store} from "@graphprotocol/graph-ts";
22
import {Block, BlockDataSource} from "../generated/schema";
33
import {BlockTime} from "../generated/subgraph-QmcKB3XQyfNM2Uzzeyd9UmGqsw83Ysh8t9LGQD94DdfSS7";
44
import {BlockCost} from "../generated/subgraph-QmQ2kJphSSsSUXqnSAKLvxmhPGNxjVtrTsLTUPeCszns17";
@@ -10,12 +10,7 @@ export function handleBlockTime(trigger: EntityTrigger<BlockTime>): void {
1010
}
1111

1212
let blockTime = trigger.data;
13-
let blockData = BlockDataSource.load(blockTime.id);
14-
15-
if (!blockData) {
16-
blockData = new BlockDataSource(blockTime.id);
17-
blockData.number = blockTime.number;
18-
}
13+
let blockData = loadOrCreateBlockData(blockTime.id, blockTime.number);
1914

2015
blockData.blockTime = blockTime.blockTime;
2116
blockData.save();
@@ -29,12 +24,7 @@ export function handleBlockCost(trigger: EntityTrigger<BlockCost>): void {
2924
}
3025

3126
let blockCost = trigger.data;
32-
let blockData = BlockDataSource.load(blockCost.id);
33-
34-
if (!blockData) {
35-
blockData = new BlockDataSource(blockCost.id);
36-
blockData.number = blockCost.number;
37-
}
27+
let blockData = loadOrCreateBlockData(blockCost.id, blockCost.number);
3828

3929
blockData.gasUsed = blockCost.gasUsed;
4030
blockData.save();
@@ -48,19 +38,25 @@ export function handleBlockSize(trigger: EntityTrigger<BlockSize>): void {
4838
}
4939

5040
let blockSize = trigger.data;
51-
let blockData = BlockDataSource.load(blockSize.id);
52-
53-
if (!blockData) {
54-
blockData = new BlockDataSource(blockSize.id);
55-
blockData.number = blockSize.number;
56-
}
41+
let blockData = loadOrCreateBlockData(blockSize.id, blockSize.number);
5742

5843
blockData.size = blockSize.size;
5944
blockData.save();
6045

6146
maybeCreateBlock(blockData);
6247
}
6348

49+
function loadOrCreateBlockData(id: string, number: BigInt): BlockDataSource {
50+
let blockData = BlockDataSource.load(id);
51+
52+
if (!blockData) {
53+
blockData = new BlockDataSource(id);
54+
blockData.number = number;
55+
}
56+
57+
return blockData;
58+
}
59+
6460
function maybeCreateBlock(blockData: BlockDataSource): void {
6561
if (blockData.blockTime === null || blockData.gasUsed === null || blockData.size === null) {
6662
return;
@@ -73,7 +69,6 @@ function maybeCreateBlock(blockData: BlockDataSource): void {
7369
block.blockTime = blockData.blockTime!;
7470
block.gasUsed = blockData.gasUsed!;
7571
block.size = blockData.size!;
76-
7772
block.save();
7873

7974
store.remove("BlockDataSource", blockData.id);

0 commit comments

Comments
 (0)