Feel free to also use the template-specific
sqd scripts defined in commands.json to simplify your workflow. See the sqd CLI cheatsheet for a short intro.Prepare the environment
- Node.js 20 or newer
- Git
- Squid CLI
- Docker (if your squid will store its data to PostgreSQL)
Understand your technical requirements
Consider your business requirements and find out-
How the data should be delivered. Options:
- PostgreSQL with an optional GraphQL API - can be real-time
- file-based dataset - local or on S3
- Google BigQuery
- What data should be delivered
- Which Fuel networks you’ll be indexing - see the supported networks page
- What exact data should be retrieved from blockchain(s)
- Whether you need to mix in any off-chain data
Start from a template
Although it is possible to compose a squid from individual packages, in practice it is usually easier to start from a template.- A starter squid counting
LOG_DATAreceipts of Fuel contracts:
1
Install dependencies
- PostgreSQL+GraphQL
1
Start PostgreSQL
2
Build the squid
3
Apply the database migrations
4
Start the squid processor
5
Start the GraphQL server
docker compose down.The bottom-up development cycle
The advantage of this approach is that the code remains buildable at all times, making it easier to catch issues early.1
Regenerate task-specific utilities
Generate contract or runtime utilities for the data you need. See the detailed guidance.
2
Configure data requests
Choose the data source, filters, related data, and fields. See the detailed guidance.
3
Decode and normalize the data
Turn each batch into the records your application needs. See the detailed guidance.
4
Enrich the data when needed
Optionally add external data or direct chain state queries. See the detailed guidance.
5
Prepare the store
Define the destination schema, tables, and migrations. See the detailed guidance.
6
Persist transformed data
Write each processed batch efficiently to the selected destination. See the detailed guidance.
Regenerate the task-specific utilities
The starter Fuel squid decodes receipt data without generated utilities, so no code generation step is needed. See the receipts indexing tutorial for how it accesses the data.Configure the data requests
Edit the data source definition insrc/main.ts to request the receipts, transactions, inputs and outputs your task requires, and to select the data fields you need. See the Fuel data source reference for all available options.
Decode and normalize the data
Next, change the batch handler to decode and normalize your data. In templates, the batch handler is defined at therun() call in src/main.ts as an inline function. Its sole argument ctx contains:
- at
ctx.blocks: all the requested data for a batch of blocks - at
ctx.store: the means to save the processed data - at
ctx.isHead: a boolean indicating whether the batch is at the current chain head
ctx.blocks items varies. Note that there is no ctx.log - when your handler needs a Logger, create one explicitly with createLogger() from @subsquid/logger.
Each item in ctx.blocks contains the requested receipts, transactions, inputs and outputs for a particular block, plus some info on the block itself. See the Fuel batch context reference and the receipts indexing tutorial for a worked decoding example.
Mix in external data and chain state call output (optional)
If you need external (i.e. non-blockchain) data in your transformation, take a look at the External APIs and IPFS page. If any of the onchain data you need is unavailable from the processor or inconvenient to retrieve with it, you can query the network directly from within the batch handler using any suitable client library.Prepare the store
Atsrc/main.ts, change the Database object definition to accept your output data. The methods for saving data will be exposed by ctx.store within the batch handler.
- PostgreSQL+GraphQL
- Filesystem dataset
- BigQuery
-
Define the schema of the database (and the core schema of the OpenReader GraphQL API if it is used) at
schema.graphql. -
Regenerate the TypeORM model classes with
The classes will become available at
src/model. -
Compile the models code with
-
Ensure that the squid has access to a blank database. The easiest way to do so is to start PostgreSQL in a Docker container with
If the container is running, stop it and erase the database withbefore issuing a
docker compose up -d. The alternative is to connect to an external database. See this section to learn how to specify the connection parameters. -
Regenerate a migration with
ctx.store.upsert() and ctx.store.insert(), as well as various TypeORM lookup methods to access the database.See the Writing to PostgreSQL guide and the typeorm-store reference for more info.Persist the transformed data to your data sink
Once your data is decoded, optionally enriched with external data and transformed the way you need it to be, it is time to save it.- PostgreSQL+GraphQL
- Filesystem dataset
- BigQuery
For each batch, create all the instances of all TypeORM model classes at once, then save them with the minimal number of calls to It will often make sense to keep the entity instances in maps rather than arrays to make it easier to reuse them when defining instances of other entities with relations to the previous ones.If you perform any database lookups, try to do so in batches and make sure that the entity fields that you’re searching over are indexed.See also the patterns and anti-patterns sections of the Batch processing guide.
upsert() or insert(), e.g.:The top-down development cycle
The bottom-up development cycle described above is convenient for initial squid development and for trying out new things, but it has the disadvantage of not having the means of saving the data ready at hand when initially writing the data decoding/transformation code. That makes it necessary to come back to that code later, which is somewhat inconvenient, for example when adding new squid features incrementally. The alternative is to do the same steps in a different order:1
Update the store
Define the destination schema, tables, and migrations before changing the processor.
2
Regenerate utility classes
Regenerate the task-specific utilities if the new feature requires them.
3
Update the processor configuration
4
Decode and normalize the added data
Transform the new batch items into the required records.
5
Retrieve external data when needed
6
Add the persistence code
Write the transformed records to the prepared store.
GraphQL options
Store your data to PostgreSQL, then consult Serving GraphQL for options.Scaling up
If you’re developing a large squid, make sure to use batch processing throughout your code. A common mistake is to make handlers for individual event logs or transactions; for updates that require data retrieval that results in lots of small database lookups and ultimately in poor syncing performance. Collect all the relevant data and process it at once. You should also check the Cloud best practices page even if you’re not planning to deploy to SQD Cloud - it contains valuable performance-related tips. Many issues commonly arising when developing larger squids are addressed by the third party@belopash/typeorm-store package. Consider using it.
For complete examples of complex squids take a look at the Giant Squid Explorer and Thena Squid repos.
Next steps
- Learn about batch processing.
- Learn how squids deal with unfinalized blocks.
- Use external APIs and IPFS in your squid.
- See how squids should be set up for the multichain setting.
- Deploy your squid on your own infrastructure or to SQD Cloud.