Document not found (404)
+This URL is invalid, sorry. Please use the navigation bar or search to continue.
+ +diff --git a/v0.25.0/.nojekyll b/v0.25.0/.nojekyll new file mode 100644 index 000000000..f17311098 --- /dev/null +++ b/v0.25.0/.nojekyll @@ -0,0 +1 @@ +This file makes sure that Github Pages doesn't process mdBook's output. diff --git a/v0.25.0/404.html b/v0.25.0/404.html new file mode 100644 index 000000000..9d5f26abc --- /dev/null +++ b/v0.25.0/404.html @@ -0,0 +1,221 @@ + + +
+ + +This URL is invalid, sorry. Please use the navigation bar or search to continue.
+ +The Fuel indexer's authentication functionality offers users a range of options for verifying their identity. The system supports any arbitrary authentication scheme (in theory); however, in practice the service defaults to JWT authentication due to its stateless nature and popularity.
+To authenticate using JWT, users ask an indexer operator for a nonce, sign that nonce with their wallet, then send both the nonce and signature to the indexer operator for verification. Once the signature is verified on the backend, a valid JWT is produced and returned to the user, thus authenticating the user.
+It is important to note that authentication is disabled by default. However, if authentication is enabled, users will need to authenticate before performing operations that involve modifying the state of the service, such as uploading indexers.
+The new authentication functionality offers a flexible and secure way for users to authenticate and perform operations that affect the service's state.
+Below is a demonstration of basic JWT authentication using an indexer operator at "https://beta-5-indexer.fuel.network"
+forc index auth --url https://beta-5-indexer.fuel.network:29987
+
+You will first be prompted for the password for your wallet:
+Please enter your wallet password:
+
+After successfully entering your wallet password you should be presented with your new JWT token.
+✅ Successfully authenticated at https://beta-5-indexer.fuel.network:29987/api/auth/signature.
+
+Token: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiODNlNjhiOTFmNDhjYWM4M....
+
+Use this token in your Authorization
headers when making requests for operations such as uploading indexers, stopping indexers, and other operations that mutate state in this way.
Users can just pass this JWT token value to the --auth
flag, if using forc index
commands that support authentication (e.g., forc index deploy --auth $MY_JWT_TOKEN
).
++Per GraphQL: A directive is a keyword preceded by a @ character (optionally followed by a list of named arguments) which can appear after almost any form of syntax in the GraphQL query or schema languages.
+
As of this writing, the list of supported Fuel GraphQL schema directives includes:
+@indexed
: Denotes that a field should include a B-tree index in the database.@unique
: Denotes that field should include a unique index in the database.@join
: Denotes that a field has a "relationship" to another object type.@indexed
The @indexed
directive adds a database index to the underlying column for the indicated field of that type. Generally, a database index is a data structure that allows you to quickly locate data without having to search each row in a database table.
type Book @entity {
+ id: ID!
+ name: Bytes8! @indexed
+}
+
+type Library @entity {
+ id: ID!
+ book: Book!
+}
+
+In this example, a single BTREE INDEX
constraint will be created on the book
table's name
column, which allows for faster lookups on that field.
++Important: At the moment, database index constraint support is limited to
+BTREE
in Postgres withON DELETE
, andON UPDATE
actions not being supported.
@unique
The @unique
directive adds a UNIQUE
database constraint to the underlying database column for the indicated field of that type. A constraint specifies a rule for the data in a table and can be used to limit the type of data that can be placed in the table. In the case of a column with a UNIQUE
constraint, all values in the column must be different.
type Book @entity {
+ id: ID!
+ name: Bytes8! @unique
+}
+
+type Library @entity {
+ id: ID!
+ book: Book!
+}
+
+A UNIQUE
constraint will be created on the book
table's name
column, ensuring that no books can share the same name.
++Important: When using explicit or implicit foreign keys, it is required that the reference column name in your foreign key relationship be unique.
+ID
types are by default unique, but all other types will have to be explicitly specified as being unique via the@unique
directive.
@join
The @join
directive is used to relate a field in one type to others by referencing fields in another type. You can think of it as a link between two tables in your database. The field in the referenced type is called a foreign key and it is required to be unique.
type Book @entity {
+ id: ID!
+ name: String! @unique
+}
+
+type Library @entity {
+ id: ID!
+ book: Book! @join(on:name)
+}
+
+A foreign key constraint will be created on library.book
that references book.name
, which relates the Book
s in a Library
to the underlying Book
table. For more info on what exactly is happening here, please see the Relationships section.
The Fuel indexer uses GraphQL in order to allow users to query for indexed data. In this chapter, you can find information on how to leverage our supported features to efficiently get the data you want.
+++⚠️ Please note that the Fuel indexer does not support the full GraphQL specification; however, we do our best to reasonably support as much as we can.
+
While we do our best to maintain compliance with the GraphQL specification and parity with other implementations, there are a few things that are under development or will not be implemented. Here's a table describing our GraphQL functionality:
+Legend:
+Functionality | Status | Notes |
---|---|---|
Arguments | 🟩 | read the Search and Filtering section |
Aliases | 🟩 | |
Fragments | 🟨 | inline fragments are currently not supported |
Introspection | 🟩 | |
GraphQL Playground | 🟩 | read the Playground section |
Pagination | 🟨 | read the Pagination section |
Directives | 🟨 | read the Directives section |
List Types | 🟨 | |
Union Types | 🟨 | |
Federation | ⛔ | |
Variables | ⛔ | |
Mutations | ⛔ | |
Enums | 🟨 | |
Interfaces | ⛔ | |
Input Types | ⛔ |
The Fuel indexer service supports foreign key relationships and constraints. There are two types of relationship specifications: implicit and explicit.
+++IMPORTANT:
+Implicit foreign keys do not require a
+@join
directive. When using implicit foreign key references, merely add the referenced object as a field type (shown below). A lookup will automatically be done to add a foreign key constraint using this object's'id
field.Note that implicit foreign key relationships only use the
+id
field on the referenced table. If you plan to use implicit foreign keys, the object being referenced must have anid
field.In contrast, explicit foreign keys do require a
+@join
directive. Explicit foreign key references work similarly to implicit foreign keys; however, when using explicit foreign key references, you must add a@join
directive after your object type. This@join
directive includes the field in your foreign object that you would like to reference (shown below).
Let's learn how to use each foreign key type by looking at some GraphQL schema examples.
+type Library @entity {
+ id: ID!
+ name: String!
+}
+
+type Book @entity {
+ id: ID!
+ library: Library!
+}
+
+Given the above schema, two entities will be created: a Book
entity, and a Library
entity. As you can see, we add the Book
entity as an attribute on the Library
entity, thus conveying that we want a one-to-many or one-to-one relationship between Library
and Book
. This means that for a given Book
, we may also fetch the associated Library
entity. It also means that the field Book.library
will be an ID
scalar type that references Library.id
.
type Library @entity {
+ id: ID!
+ name: String! @unique
+}
+
+type Book @entity {
+ id: ID!
+ library: Library! join(on:name)
+}
+
+For the most part, this works the same way as implicit foreign key usage. However, as you can see, instead of implicitly using Library.id
as the reference column for our Library
field type on the Book
object, we're explicitly specifying that we want Library.name
to serve as our foreign key for the Book.library
field. Also, please note that since we're using Library.name
in our foreign key constraint, that column is required to be unique (via the @unique
directive).
The Fuel indexer has a collection of GraphQL scalars that cover virtually any value type in use on the Fuel network. The following list contains each GraphQL scalar type along with its equivalent Rust type.
+GraphQL Scalar | Rust Type | Notes |
---|---|---|
Address | u8[32] | |
AssetId | u8[32] | |
Boolean | bool | |
Bytes | Vec<u8> | Byte blob of arbitrary size |
Bytes32 | u8[32] | |
Bytes4 | u8[4] | |
Bytes64 | u8[64] | |
Bytes8 | u8[8] | |
ContractId | u8[32] | |
HexString | Vec<u8> | Byte blob of arbitrary size |
I128 | i128 | |
I16 | i16 | |
I32 | i32 | |
I64 | i64 | |
I8 | i8 | |
ID | SizedAsciiString<64> | Alias of UID |
Json | String | JSON string of arbitrary size |
String | String | String of arbitrary size |
U128 | u128 | |
U16 | u16 | |
U32 | u32 | |
U64 | u64 | |
U8 | u8 | |
UID | SizedAsciiString<64> | 32-byte unique ID |
Object types are the most commonly used type in indexer GraphQL schema. Each object type marked with an @entity
directive will be converted into a SQL table.
type Account @entity {
+ id: ID!
+ address: Address!
+ balance: U64!
+}
+
+This Account
object type from the GraphQL schema, might be used in an indexer module like so:
extern crate alloc;
+use fuel_indexer_utils::prelude::*;
+
+#[indexer(manifest = "indexer.manifest.yaml")]
+mod indexer_mod {
+ fn handle_event(event: Event) {
+ let address = Address::default();
+ let balance = 0;
+ let account = Account::new(address, balance);
+ account.save();
+ }
+}
+Enum types are simply implemented as String types.
+enum SignatureLabel {
+ Multi
+ Single
+}
+
+++Enum types in relation to Fuel indexer's implementation are just
+String
types used primarily to label object types. There is no other way thatenum
types should be used at this time. +ThisSignatureLabel
object type from the GraphQL schema, might be used in an indexer module like so:
extern crate alloc;
+use fuel_indexer_utils::prelude::*;
+
+#[indexer(manifest = "indexer.manifest.yaml")]
+mod indexer_mod {
+ fn handle_event(event: Event) {
+ let label = SignatureLabel::Multi;
+ assert_eq!(label.to_string(), "SignatureLabel::Multi".to_string());
+ }
+}
+Union types are unique in that any type marked as a union
will be converted into an Object type, who's fields are the unique set of fields over all members of the union.
enum TransactionLabel {
+ Create
+ Script
+ Mint
+}
+
+type CreateTransaction @entity {
+ id: ID!
+ bytecode_length: U64!
+ contract_id: ContractId!
+ label: TransactionLabel!
+}
+
+type ScriptTransaction @entity {
+ id: ID!
+ maturity: U64!
+ label: TransactionLabel!
+}
+
+type MintTransaction @entity {
+ id: ID!
+ metadata: Json
+ label: TransactionLabel!
+}
+
+union Transaction = CreateTransaction | ScriptTransaction | MintTransaction
+
+The Transaction
union type above, will internally produce the following object type:
type Transaction @entity {
+ id: ID!
+ bytecode_length: U64!
+ contract_id: ContractId!
+ label: TransactionLabel!
+ maturity: U64!
+ metadata: Json
+}
+
+++IMPORTANT: Note the order of the fields in the derived
+Transaction
object type: the fields are ordered according to the unique set of fields from each of the union's members.The
+id
,bytecode_length
,contract_id
, andlabel
fields come first, from theCreateTransaction
object type. Next comes thematurity
field from theScriptTransaction
object - because theScriptTransaction
'sid
andlabel
fields are already a part of the derivedTransaction
object, courtesy of theCreateTransaction
object type. Finally, comes themetadata
field, as part of theMintTransaction
object type. +ThisTransaction
union type from the GraphQL schema, might be used in an indexer module like so:
extern crate alloc;
+use fuel_indexer_utils::prelude::*;
+
+#[indexer(manifest = "indexer.manifest.yaml")]
+mod indexer_mod {
+ fn handle_event(event: Event) {
+ let bytecode_length = 1024;
+ let contract_id = ContractId::default();
+ let label = TransactionLabel::Create;
+ let maturity = 10000000;
+ let metadata = None;
+ let transaction = Transaction::new(bytecode_length, contract_id, label, maturity, metadata);
+ transaction.save();
+ }
+}
+
+ git clone git@github.com:FuelLabs/fuel-indexer.git
+
+cd packages/fuel-indexer-database/postgres
+DATABASE_URL=postgres://postgres@localhost sqlx migrate run
+
+cargo run --bin fuel-indexer
+
+cargo test --locked --workspace --all-features --all-targets
+
+
+ Thanks for your interest in contributing to Fuel Indexer! This document outlines some the conventions on building, running, and testing Fuel Indexer.
+Fuel Indexer has many dependent repositories. If you need any help or mentoring getting started, understanding the codebase, or anything else, please ask on our Discord.
+rustdoc
comments for the new code so others can better understand the change.++Future instructions assume you are in this repository
+
git clone https://github.com/FuelLabs/fuel-indexer
+cd fuel-indexer
+
+rustup
is the official toolchain manager for Rust.
We use some additional components such as clippy
and rustfmt
, to install those:
rustup component add clippy
+rustup component add rustfmt
+
+Fuel Indexer also uses a few other tools installed via cargo
cargo install sqlx-cli
+cargo install wasm-snip
+
+Fuel Indexer's two primary crates are fuel-indexer
and fuel-indexer-api-server
.
You can build Fuel Indexer:
+cargo build -p fuel-indexer -p fuel-indexer-api-server --release --locked
+
+Linting is done using rustfmt
and clippy
, which are each separate commands:
cargo fmt --all --check
+
+cargo clippy --all-features --all-targets -- -D warnings
+
+The test suite follows the Rust cargo standards.
+Testing is simply done using Cargo:
+RUSTFLAGS='-D warnings' SQLX_OFFLINE=1 cargo test --locked --all-targets --all-features
+
+This is a rough outline of what a contributor's workflow looks like:
+develop
1234/short-description
, where 1234
is the number of the associated issue.[commit type]: [short commit blurb]
+fix: database locking issue
enhancement: i added something super cool
chore: i helped do the chores
bug
: If fixing broken functionalityenhancement
: If adding new functionalitychore
: If finishing valuable work (that's no fun!)testing
: If only updating/writing testsdocs
: If just updating docsfeat
: If adding a non-trivial new featureThanks for your contributions!
+For beginners, we have prepared many suitable tasks for you. Checkout our Good First Issues for a list.
+If you are planning something that relates to multiple components or changes current behaviors, make sure to open an issue to discuss with us before continuing.
+ +Thanks for your interest in contributing to the Fuel indexer! Below we've compiled a list of sections that you may find useful as you work on a potential contribution:
+fuelup
docker
fuelup
We use fuelup in order to get the binaries produced by services in the Fuel ecosystem. Fuelup will install binaries related to the Fuel node, the Fuel indexer, the Fuel orchestrator (forc), and other components. fuelup
can be downloaded here.
docker
We use Docker to produce reproducible environments for users that may be concerned with installing components with large sets of dependencies (e.g. Postgres). Docker can be downloaded here.
+At this time, the Fuel indexer requires the use of a database. We currently support a single database option: Postgres. PostgreSQL is a database solution with a complex feature set and requires a database server.
+++Note: The following explanation is for demonstration purposes only. A production setup should use secure users, permissions, and passwords.
+
On macOS systems, you can install PostgreSQL through Homebrew. If it isn't present on your system, you can install it according to the instructions. Once installed, you can add PostgreSQL to your system by running brew install postgresql
. You can then start the service through brew services start postgresql
. You'll need to create a database for your indexed data, which you can do by running createdb [DATABASE_NAME]
. You may also need to create the postgres
role; you can do so by running createuser -s postgres
.
For Linux-based systems, the installation process is similar. First, you should install PostgreSQL according to your distribution's instructions. Once installed, there should be a new postgres
user account; you can switch to that account by running sudo -i -u postgres
. After you have switched accounts, you may need to create a postgres
database role by running createuser --interactive
. You will be asked a few questions; the name of the role should be postgres
and you should elect for the new role to be a superuser. Finally, you can create a database by running createdb [DATABASE_NAME]
.
In either case, your PostgreSQL database should now be accessible at postgres://postgres@localhost:5432/[DATABASE_NAME]
.
After setting up your database, you should install sqlx-cli
in order to run migrations for your indexer service. You can do so by running cargo install sqlx-cli --features postgres
. Once installed, you can run the migrations by running the following command after changing DATABASE_URL
to match your setup.
git clone git@github.com:FuelLabs/fuel-indexer.git && cd fuel-indexer/
+
+cd packages/fuel-indexer-database/postgres
+DATABASE_URL=postgres://postgres@localhost sqlx migrate run
+
+cargo run --bin fuel-indexer run
+
+You can also start the service with a fresh local node for development purposes:
+cargo run --features fuel-core-lib --bin fuel-indexer run
+
+++If no configuration file or other options are passed, the service will default to a
+postgres://postgres@localhost
database connection.
Fuel indexer tests are currently broken out by a database feature flag. In order to run tests with a Postgres backend, use --features postgres
.
cargo test --locked --workspace --all-targets
+
+cargo test --locked --workspace --all-targets --features postgres
+
+trybuild
testsFor tests related to the meta-programming used in the Fuel indexer, we use trybuild
.
RUSTFLAGS='-D warnings' cargo test -p fuel-indexer-macros --locked
+
+
+ v2.0.0
-> v3.0.0
v0.3.0
-> v0.4.0
v0.1.3
-> v0.1.4
There are a few system requirements related to compilation, tooling, and SQL backends that you'll need to be able to contribute to the Fuel indexer.
+apt update && apt install -y \
+ cmake \
+ pkg-config \
+ git \
+ gcc \
+ build-essential \
+ clang \
+ libclang-dev \
+ llvm \
+ libpq-dev
+
+Dependency | Required For |
---|---|
cmake | Manages the build process in an operating system and in a compiler-independent manner |
pkg-config | Language-agnostic helper tool used when compiling applications and libraries |
git | Version control system |
gcc | Compiler tools required to build various Fuel indexer crates |
clang /libclang-dev | Compiler tools required to build various Fuel indexer crates on Unix-like OSes |
llvm | Required for building Fuel indexer crate dependencies |
libpq-dev | Set of library function helping facilitate interaction with the PostgreSQL backend |
brew update && brew install \
+ cmake \
+ llvm \
+ libpq \
+ postgresql
+
+Dependency | Required For |
---|---|
cmake | Manages the build process in an operating system and in a compiler-independent manner |
llvm | Compiler infrastructure for building Fuel indexer crate dependencies |
libpq | Postgres C API library |
postgresql | Installs the command line console (psql ) as well as a PostgreSQL server locally |
pacman -Syu --needed --noconfirm \
+ cmake \
+ gcc \
+ pkgconf \
+ git \
+ clang \
+ llvm11 \
+ llvm11-libs \
+ postgresql-libs
+
+Dependency | Required For |
---|---|
cmake | Manages the build process in an operating system and in a compiler-independent manner |
git | Version control system |
gcc | Compiler tools required to build various Fuel indexer crates |
llvm11 | Compiler infrastructure for building Fuel indexer crate dependencies |
llvm11-libs | Compiler infrastructure libraries for building Fuel indexer crate dependencies |
pkgconf | System for configuring build dependency information |
postgresql-libs | Provides the essential shared libraries for any PostgreSQL client program or interface |
clang | Compiler required to build various Fuel indexer crates Unix-like OSes |
forc index auth
Authenticate against an indexer operator.
+++IMPORTANT: There must be an indexer service running at
+--url
in order for this to work.
forc index auth --account 0
+
+Authenticate against an indexer service
+
+USAGE:
+ forc-index auth [OPTIONS]
+
+OPTIONS:
+ --account <ACCOUNT> Index of account to use for signing. [default: 0]
+ -h, --help Print help information
+ --url <URL> URL at which to deploy indexer assets. [default:
+ http://127.0.0.1:29987]
+ -v, --verbose Verbose output.
+
+
+ forc index build
Build an indexer.
+forc index build
+
+Build an indexer
+
+USAGE:
+ forc-index build [OPTIONS]
+
+OPTIONS:
+ -d, --debug Build artifacts with the debug profile.
+ -h, --help Print help information
+ --locked Ensure that the Cargo.lock file is up-to-date.
+ -m, --manifest <MANIFEST> Manifest file name of indexer being built.
+ -p, --path <PATH> Path to the indexer project.
+ -v, --verbose Enable verbose output.
+
+
+
+ forc index check
Check to see which indexer components you have installed.
+forc index check
+
+Check for Fuel indexer components
+
+USAGE:
+ forc-index check
+
+OPTIONS:
+ -h, --help Print help information
+
+
+ forc index deploy
Deploy an indexer to an indexer service.
+forc index deploy --url https://beta-5-indexer.fuel.network
+
+Deploy an indexer to an indexer service
+
+USAGE:
+ forc-index deploy [OPTIONS]
+
+OPTIONS:
+ --auth <AUTH> Authentication header value.
+ -d, --debug Build optimized artifacts with the debug profile.
+ -h, --help Print help information
+ --locked Ensure that the Cargo.lock file is up-to-date.
+ -m, --manifest <MANIFEST> Path to the manifest of indexer project being deployed.
+ -p, --path <PATH> Path to the indexer project.
+ --remove-data Remove all indexed data when replacing an existing indexer.
+ --replace-indexer If an indexer with the same UID exists, remove it.
+ --skip-build Do not build before deploying.
+ --url <URL> URL at which to deploy indexer assets. [default:
+ http://127.0.0.1:29987]
+ -v, --verbose Enable verbose logging.
+
+
+
+ forc index
is the recommended method for end users to interact with the Fuel indexer. After you have installed fuelup
, you can run the forc index help
command in your terminal to view the available commands.
<!-- markdownlint-disable MD033 -->
+<!-- markdownlint-disable MD025 -->
+<!-- markdownlint-disable MD041 -->
+<p align="center">
+ <picture>
+ <source media="(prefers-color-scheme: dark)" srcset="./img/fuel-indexer-logo-dark.png">
+ <img alt="Fuel Indexer logo" width="400px" src="./img/fuel-indexer-logo-light.png">
+ </picture>
+
+</p>
+<p align="center">
+ <a href="https://github.com/FuelLabs/fuel-indexer/actions/workflows/ci.yml" alt="CI">
+ <img src="https://img.shields.io/github/actions/workflow/status/FuelLabs/fuel-indexer/ci.yml?event=release" />
+ </a>
+ <a href="https://docs.rs/fuel-indexer/" alt="docs.rs">
+ <img src="https://docs.rs/fuel-indexer/badge.svg" />
+ </a>
+ <a href="https://crates.io/crates/fuel-indexer" alt="crates.io">
+ <img src="https://img.shields.io/crates/v/fuel-indexer?label=latest" />
+ </a>
+ <a href="https://crates.io/crates/fuel-indexer" alt="img-shields">
+ <img alt="GitHub commits since latest release (by date including pre-releases)" src="https://img.shields.io/github/commits-since/FuelLabs/fuel-indexer/latest?include_prereleases">
+ </a>
+ <a href="https://discord.gg/xfpK4Pe" alt="Discord">
+ <img src="https://img.shields.io/badge/chat%20on-discord-orange?&logo=discord&logoColor=ffffff&color=7389D8&labelColor=6A7EC2" />
+ </a>
+</p>
+
+### [➡️ Read the Quickstart! ➡️](https://docs.fuel.network/docs/indexer/getting-started/quickstart/)
+
+## What is the Fuel indexer?
+
+The Fuel indexer is a standalone service that can be used to index various components of the blockchain. These indexable components include blocks, transactions, receipts, and state within the Fuel network, allowing for high-performance read-only access to the blockchain for advanced dApp use-cases.
+
+> TLDR: It's Infrastructure as a service (IaaS) for Web3 dApp backends.
+
+## Install
+
+Fuel's indexer supports Linux (x64 & arm64) and macOS (x64 & Apple Silicon).
+
+> If you don't want to deal with dependency issues we recommend just using Fuel's indexer with Docker, [via the included docker-compose file](https://github.com/FuelLabs/fuel-indexer/blob/develop/scripts/docker-compose.yaml).
+
+Install Fuel's toolchain manager - fuelup.
+
+```bash
+curl --proto '=https' --tlsv1.2 -sSf https://install.fuel.network/fuelup-init.sh | sh
+
+++The
+fuel-indexer
andforc-index
binaries should now be available in your$PATH
For development, users will primarily use the forc index
command line utility made available after installing fuelup.
forc index --help
+
+forc index 0.0.0
+Fuel Indexer Orchestrator
+
+USAGE:
+ forc-index <SUBCOMMAND>
+
+OPTIONS:
+ -h, --help Print help information
+ -V, --version Print version information
+
+SUBCOMMANDS:
+ auth Authenticate against an indexer service
+ build Build an indexer
+ check Check for Fuel indexer components
+ deploy Deploy an indexer to an indexer service
+ help Print this message or the help of the given subcommand(s)
+ kill Kill the indexer process. Note that this command will kill any process
+ listening on the default indexer port or the port specified by the `--port`
+ flag
+ new Create a new indexer project in a new directory
+ postgres Fuel Postgres Orchestrator
+ remove Stop and remove a running indexer
+ start Standalone binary for the Fuel indexer service
+ status Check the status of a registered indexer
+
+If you're interested in contributing PRs to make the Fuel indexer a better project, feel free to read our contributors document.
+
+
+ forc index kill
Kill the indexer process. Note that this command will kill any process listening on the default indexer port or the port specified by the --port
flag.
forc index kill --port 29987
+
+Kill the indexer process. Note that this command will kill any process listening on the default
+indexer port or the port specified by the `--port` flag
+
+USAGE:
+ forc-index kill [OPTIONS]
+
+OPTIONS:
+ -9 Terminate or kill
+ -h, --help Print help information
+ --port <PORT> Port at which to detect indexer service API is running. [default: 29987]
+
+
+ forc index new
Create a new indexer project in a new directory.
+forc index new --namespace fuel /home/fuel/projects/example_indexer
+
+Create a new indexer project in a new directory
+
+USAGE:
+ forc-index new [OPTIONS] <PATH>
+
+ARGS:
+ <PATH> Path at which to create indexer
+
+OPTIONS:
+ --absolute-paths Resolve indexer asset filepaths using absolute paths.
+ -h, --help Print help information
+ --name <NAME> Name of indexer.
+ --namespace <NAMESPACE> Namespace to which indexer belongs.
+ -v, --verbose Enable verbose output.
+
+
+ forc index remove
Stop and remove a running indexer.
+forc index remove --url https://beta-5-indexer.fuel.network
+
+Stop and remove a running indexer
+
+USAGE:
+ forc-index remove [OPTIONS]
+
+OPTIONS:
+ --auth <AUTH> Authentication header value.
+ -h, --help Print help information
+ -m, --manifest <MANIFEST> Path to the manifest of the indexer project being removed.
+ -p, --path <PATH> Path to the indexer project.
+ --url <URL> URL at which indexer is deployed. [default: http://127.0.0.1:29987]
+ -v, --verbose Enable verbose output.
+
+
+ forc index start
Start a local Fuel Indexer service.
+forc index start
+
+Standalone binary for the Fuel indexer service
+
+USAGE:
+ forc-index start [OPTIONS]
+
+OPTIONS:
+ --accept-sql-queries
+ Allow the web server to accept raw SQL queries.
+
+ --allow-non-sequential-blocks
+ Allow missing blocks or non-sequential block processing.
+
+ --auth-enabled
+ Require users to authenticate for some operations.
+
+ --auth-strategy <AUTH_STRATEGY>
+ Authentication scheme used.
+
+ --block-page-size <BLOCK_PAGE_SIZE>
+ Amount of blocks to return in a request to a Fuel node. [default: 20]
+
+ -c, --config <FILE>
+ Indexer service config file.
+
+ --client-request-delay <CLIENT_REQUEST_DELAY>
+ Make the service wait for the given duration between block requests to a Fuel client.
+
+ --database <DATABASE>
+ Database type. [default: postgres] [possible values: postgres]
+
+ --disable-toolchain-version-check
+ By default, Fuel Indexer will only accept WASM indexer modules compiled with the same
+ toolchain version as the version of Fuel Indexer.
+
+ --embedded-database
+ Automatically create and start database using provided options or defaults.
+
+ --fuel-node-host <FUEL_NODE_HOST>
+ Host of the running Fuel node. [default: localhost]
+
+ --fuel-node-port <FUEL_NODE_PORT>
+ Listening port of the running Fuel node. [default: 4000]
+
+ -h, --help
+ Print help information
+
+ --indexer-net-config
+ Allow network configuration via indexer manifests.
+
+ --jwt-expiry <JWT_EXPIRY>
+ Amount of time (seconds) before expiring token (if JWT scheme is specified).
+
+ --jwt-issuer <JWT_ISSUER>
+ Issuer of JWT claims (if JWT scheme is specified).
+
+ --jwt-secret <JWT_SECRET>
+ Secret used for JWT scheme (if JWT scheme is specified).
+
+ --local-fuel-node
+ Start a local Fuel node.
+
+ --log-level <LOG_LEVEL>
+ Log level passed to the Fuel Indexer service. [default: info] [possible values: info,
+ debug, error, warn]
+
+ -m, --manifest <FILE>
+ Indexer config file.
+
+ --max-body-size <MAX_BODY_SIZE>
+ Max body size for web server requests. [default: 5242880]
+
+ --metering-points <METERING_POINTS>
+ The number of WASM opcodes after which the indexer's event handler will stop execution.
+ [default: 30000000000]
+
+ --metrics
+ Use Prometheus metrics reporting.
+
+ --network <NETWORK>
+ Use a network alias when connecting to a Fuel client. [default: beta-3 beta-5 beta-5]
+
+ --postgres-database <POSTGRES_DATABASE>
+ Postgres database.
+
+ --postgres-host <POSTGRES_HOST>
+ Postgres host.
+
+ --postgres-password <POSTGRES_PASSWORD>
+ Postgres password.
+
+ --postgres-port <POSTGRES_PORT>
+ Postgres port.
+
+ --postgres-user <POSTGRES_USER>
+ Postgres username.
+
+ --rate-limit
+ Enable rate limiting.
+
+ --rate-limit-request-count <RATE_LIMIT_REQUEST_COUNT>
+ Maximum number of requests to allow over --rate-limit-window..
+
+ --rate-limit-window-size <RATE_LIMIT_WINDOW_SIZE>
+ Number of seconds over which to allow --rate-limit-rps.
+
+ --remove-data
+ When replacing an indexer, also remove the indexed data.
+
+ --replace-indexer
+ Whether to allow replacing an existing indexer. If not specified, an attempt to deploy
+ over an existing indexer results in an error.
+
+ --run-migrations
+ Run database migrations before starting service.
+
+ --stop-idle-indexers
+ Prevent indexers from running without handling any blocks.
+
+ -v, --verbose
+ Enable verbose logging.
+
+ -V, --version
+ Print version information
+
+ --web-api-host <WEB_API_HOST>
+ Web API host. [default: localhost]
+
+ --web-api-port <WEB_API_PORT>
+ Web API port. [default: 29987]
+
+
+ forc index status
Check the status of a registered indexer.
+forc index status --url https://beta-5-indexer.fuel.network
+
+Check the status of a registered indexer
+
+USAGE:
+ forc-index status [OPTIONS]
+
+OPTIONS:
+ --auth <AUTH> Authentication header value.
+ -h, --help Print help information
+ --url <URL> URL at which to find indexer service. [default: http://127.0.0.1:29987]
+ -v, --verbose Enable verbose logging.
+
+
+
+ forc index postgres create
Create a new database.
+forc index postgres create example_database
+
+USAGE:
+ forc-index postgres create [OPTIONS] <NAME>
+
+ARGS:
+ <NAME> Name of database.
+
+OPTIONS:
+ --auth-method <AUTH_METHOD>
+ Authentication method. [default: plain] [possible values: plain, md5, scram-sha-256]
+
+ -c, --config <CONFIG>
+ Fuel indexer configuration file.
+
+ --database-dir <DATABASE_DIR>
+ Where to store the PostgreSQL database.
+
+ -h, --help
+ Print help information
+
+ --migration-dir <MIGRATION_DIR>
+ The directory containing migration scripts.
+
+ -p, --password <PASSWORD>
+ Database password. [default: postgres]
+
+ -p, --port <PORT>
+ Port to use. [default: 5432]
+
+ --persistent
+ Do not clean up files and directories on database drop.
+
+ --postgres-version <POSTGRES_VERSION>
+ PostgreSQL version to use. [default: v14] [possible values: v15, v14, v13, v12, v11,
+ v10, v9]
+
+ --start
+ Start the PostgreSQL instance after creation.
+
+ --timeout <TIMEOUT>
+ Duration to wait before terminating process execution for pg_ctl start/stop and initdb.
+
+ -u, --user <USER>
+ Database user. [default: postgres]
+
+
+ forc index postgres drop
Drop a database.
+forc index postgres drop example_database
+
+USAGE:
+ forc-index postgres drop [OPTIONS] <NAME>
+
+ARGS:
+ <NAME> Name of database.
+
+OPTIONS:
+ -c, --config <CONFIG>
+ Fuel indexer configuration file.
+
+ --database-dir <DATABASE_DIR>
+ Where the PostgreSQL database is stored.
+
+ -h, --help
+ Print help information
+
+ --remove-persisted
+ Remove all database files that might have been persisted to disk.
+
+
+ forc index postgres
forc index postgres
is provided as a way to simplify the setup and management of an embedded Postgres database. After you have installed fuelup
, you can run the forc index postgres help
command in your terminal to view the available commands.
forc index postgres help
+
+USAGE:
+ forc-index postgres <SUBCOMMAND>
+
+OPTIONS:
+ -h, --help Print help information
+ -V, --version Print version information
+
+SUBCOMMANDS:
+ create Create a new database
+ drop Drop a database
+ help Print this message or the help of the given subcommand(s)
+ start Start PostgreSQL with a database
+ stop Stop PostgreSQL
+
+
+ forc index postgres start
Start PostgreSQL with a database.
+forc index postgres start example_database
+
+USAGE:
+ forc-index postgres start [OPTIONS] <NAME>
+
+ARGS:
+ <NAME> Name of database.
+
+OPTIONS:
+ -c, --config <CONFIG> Fuel indexer configuration file.
+ --database-dir <DATABASE_DIR> Where the PostgreSQL database is stored.
+ -h, --help Print help information
+
+
+ forc index postgres stop
Stop PostgreSQL.
+forc index postgres stop example_database
+
+USAGE:
+ forc-index postgres stop [OPTIONS] <NAME>
+
+ARGS:
+ <NAME> Name of database.
+
+OPTIONS:
+ -c, --config <CONFIG> Fuel indexer configuration file.
+ --database-dir <DATABASE_DIR> Where the PostgreSQL database is stored.
+ -h, --help Print help information
+
+
+ ++This guide covers some of the basics with regard to installing dependencies for the Fuel indexer service. However, note that this guide is meant to be a general overview for most platforms and by no means covers all platforms.
+If you're having trouble with dependencies on your system, we recommend that you use
+docker
.
To run the Fuel indexer, you'll need to install a few dependencies on your system:
+wasm32-unknown-unknown
rustup
targetwasm-snip
, a utility for stripping symbols from WebAssembly binaries.++If you don't want to install a database directly onto your system, you can use Docker to run a database in an isolated container. You can install Docker by following its installation instructions.
+For reference purposes, we provide a
+docker compose
file that comes with a PostgreSQL server and a Fuel indexer service.
Fuel
toolchainPlease visit the Fuel installation guide to install the Fuel toolchain, which includes binaries for the Fuel indexer.
+The Fuel indexer requires the use of a database. We currently support PostgreSQL.
+++IMPORTANT: Fuel Indexer users on most platforms don't need to explicitly install PostgreSQL software via a package manager. When starting the indexer service via
+forc index start
simply pass the--embedded-database
flag in order to have the indexer service download and start an embedded PostgreSQL instance viaforc index postgres
.However note that this
+--embedded-database
functionality can be a bit brittle or flaky on some platforms, so alternative methods of installing or using PostgreSQL are briefly mentioned below.
On macOS systems, you can install PostgreSQL through Homebrew. If it isn't present on your system, you can install it according to the instructions.
+Once installed, you can add PostgreSQL to your system by running brew install postgresql
.
Two additional cargo components will be required to build your indexers: wasm-snip
and the wasm32-unknown-unknown
target.
++As of this writing, there is a small bug in newly built Fuel indexer WASM modules that produces a WASM runtime error due an errant upstream dependency. For now, you can use
+wasm-snip
to remove the errant symbols from the WASM module, and prevent this issue from happening. An example can be found in the related script here.Note that since
+wasm-snip
strips Web Assembly related symbols, users will temporarily not be allowed to include other WASM-friendly crates (e.g.,chrono
) in their indexers.
wasm-snip
To install the wasm-snip
:
cargo install wasm-snip
+
+wasm32
targetTo install the wasm32-unknown-unknown
target via rustup
:
rustup target add wasm32-unknown-unknown
+
+++ +IMPORTANT: Users on Apple Silicon macOS systems may experience trouble when trying to build WASM modules due to its
+clang
binary not supporting WASM targets. If encountered, you can install a binary with better support from Homebrew (brew install llvm
) and instructrustc
to leverage it by setting the following environment variables:+
+- +
AR=/opt/homebrew/opt/llvm/bin/llvm-ar
- +
CC=/opt/homebrew/opt/llvm/bin/clang
Additionally, on some systems you need to explicitly link
+clang
tollvm
.+
+- +
LIBCLANG_PATH="/opt/homebrew/opt/llvm/lib"
- +
LDFLAGS="-L/opt/homebrew/opt/llvm/lib"
- +
CPPFLAGS="-I/opt/homebrew/opt/llvm/include"
Since many users may be familiar with indexing by using a solution like The Graph, it may be helpful to provide a comparison between The Graph and the Fuel indexer.
+Generally, the biggest conceptual differences between Fuel's indexer service and other indexer services (such as The Graph) are indexing speed, general ease of use, and indexable data types.
+Using Fuel's indexers, users can index about 30 blocks per second on a standard Macbook Pro on an M1 chip. This type of indexing speed is a boon to smart contract authors who need to iterate quickly while building dApps.
+Unlike other indexing services, users can use the forc index CLI tool to create, deploy, update, re-deploy, remove, and check the status of their indexers. The ability to completely manage, maintain, and improve remote indexers without having to open any files, or edit any source code completely sets Fuel's indexer apart from other services in the space.
+The Fuel indexer is tailored for compatibility with the FuelVM. This means that instead of being limited to the primitives of the Ethereum virtual machine (EVM), users of the Fuel indexer gain access to a much richer set of indexable abstractions provided by the FuelVM (e.g. predicates, transaction receipts, etc).
+Legend:
+Feature | The Graph | Fuel Indexer | Notes |
---|---|---|---|
Hosted Indexers | 🟩 | 🟩 | |
WASM Execution | 🟩 | 🟩 | |
Handlers | 🟩 | 🟩 | see Indexing Fuel Types and Indexing Custom Types |
Updatable Schemas | 🟩 | 🟩 | |
API Authentication | 🟩 | 🟩 | |
Starting Block Configuration | 🟩 | 🟩 | |
Native Unit Testing Framework | 🟩 | 🟥 | Users are able to use cargo test |
GraphQL: Sorting, Pagination, Filtering | 🟩 | 🟩 | |
Schema: Enum, Object, and Union types | 🟩 | 🟩 | |
Schema: One-to-one, one-to-many, many-to-many relationships | 🟩 | 🟩 | |
AssemblyScript Support | 🟩 | 🟥 | |
Admin Portal UI | 🟩 | 🟥 | |
Stop, Remove, Re-deploy indexers without smart contract changes | 🟥 | 🟩 | |
Update & redeploy indexers with 0 downtime | 🟥 | 🟩 | |
Use third party dependencies in your indexers | 🟥 | 🟩 |
A Fuel indexer service instance requires just three components:
+a Fuel Node: Custom indexers monitor incoming blocks via a Fuel GraphQL server and extract information about the state of the Fuel blockchain.
+a PostgreSQL database server: Extracted information is saved into a database.
+a Web Server: dApps can query indexers for up-to-date information and operators can deploy/remove indexers as needed.
+Component | Default Host | Default Port | CLI Argument | Environment Variable |
---|---|---|---|---|
Fuel Node | localhost | 4000 | --fuel-node-{host,port} | $FUEL_NODE_{HOST,PORT} |
Database Server | localhost | 5432 | --postgres-{username,database,password,host,port} | $POSTGRES_{USERNAME,DATABASE,PASSWORD,HOST,PORT} |
Indexer Service Web API | localhost | 29987 | --web-api-{host,port} | $WEB_API_{HOST,PORT} |
The Fuel indexer service will connect to any Fuel GraphQL server, which means you can run your own node or use a node provided by Fuel. The indexer service web server is included with the Fuel indexer; it's available as soon as the indexer is started through fuel-indexer run
. The only component that isn't provided for you is a Postgres database server. You should set up a server according to your own needs and specifications.
++You can start the indexer service with an array of CLI options. Note that most (if not all) of these options include sensible defaults.
+
fuel-indexer run --help
+
+Standalone binary for the fuel indexer service.
+
+USAGE:
+ fuel-indexer run [OPTIONS]
+
+OPTIONS:
+ --accept-sql-queries
+ Allow the web server to accept raw SQL queries.
+
+ --allow-non-sequential-blocks
+ Allow missing blocks or non-sequential block processing.
+
+ --auth-enabled
+ Require users to authenticate for some operations.
+
+ --auth-strategy <AUTH_STRATEGY>
+ Authentication scheme used.
+
+ --block-page-size <BLOCK_PAGE_SIZE>
+ Amount of blocks to return in a request to a Fuel node. [default: 20]
+
+ -c, --config <FILE>
+ Indexer service config file.
+
+ --client-request-delay <CLIENT_REQUEST_DELAY>
+ Make the service wait for the given duration between block requests to a Fuel client.
+
+ --database <DATABASE>
+ Database type. [default: postgres] [possible values: postgres]
+
+ --disable-toolchain-version-check
+ By default, Fuel Indexer will only accept WASM indexer modules compiled with the same
+ toolchain version as the version of Fuel Indexer.
+
+ --embedded-database
+ Automatically create and start database using provided options or defaults.
+
+ --fuel-node-host <FUEL_NODE_HOST>
+ Host of the running Fuel node. [default: localhost]
+
+ --fuel-node-port <FUEL_NODE_PORT>
+ Listening port of the running Fuel node. [default: 4000]
+
+ -h, --help
+ Print help information
+
+ --indexer-net-config
+ Allow network configuration via indexer manifests.
+
+ --jwt-expiry <JWT_EXPIRY>
+ Amount of time (seconds) before expiring token (if JWT scheme is specified).
+
+ --jwt-issuer <JWT_ISSUER>
+ Issuer of JWT claims (if JWT scheme is specified).
+
+ --jwt-secret <JWT_SECRET>
+ Secret used for JWT scheme (if JWT scheme is specified).
+
+ --local-fuel-node
+ Start a local Fuel node.
+
+ --log-level <LOG_LEVEL>
+ Log level passed to the Fuel Indexer service. [default: info] [possible values: info,
+ debug, error, warn]
+
+ -m, --manifest <FILE>
+ Indexer config file.
+
+ --max-body-size <MAX_BODY_SIZE>
+ Max body size for web server requests. [default: 5242880]
+
+ --metering-points <METERING_POINTS>
+ The number of WASM opcodes after which the indexer's event handler will stop execution.
+ [default: 30000000000]
+
+ --metrics
+ Use Prometheus metrics reporting.
+
+ --network <NETWORK>
+ Use a network alias when connecting to a Fuel client. [possible values: beta-3, beta-5,
+ beta-5]
+
+ --postgres-database <POSTGRES_DATABASE>
+ Postgres database.
+
+ --postgres-host <POSTGRES_HOST>
+ Postgres host.
+
+ --postgres-password <POSTGRES_PASSWORD>
+ Postgres password.
+
+ --postgres-port <POSTGRES_PORT>
+ Postgres port.
+
+ --postgres-user <POSTGRES_USER>
+ Postgres username.
+
+ --rate-limit
+ Enable rate limiting.
+
+ --rate-limit-request-count <RATE_LIMIT_REQUEST_COUNT>
+ Maximum number of requests to allow over --rate-limit-window..
+
+ --rate-limit-window-size <RATE_LIMIT_WINDOW_SIZE>
+ Number of seconds over which to allow --rate-limit-rps.
+
+ --remove-data
+ When replacing an indexer, also remove the indexed data.
+
+ --replace-indexer
+ Whether to allow replacing an existing indexer. If not specified, an attempt to deploy
+ over an existing indexer results in an error.
+
+ --run-migrations
+ Run database migrations before starting service.
+
+ --stop-idle-indexers
+ Prevent indexers from running without handling any blocks.
+
+ -v, --verbose
+ Enable verbose logging.
+
+ -V, --version
+ Print version information
+
+ --web-api-host <WEB_API_HOST>
+ Web API host. [default: localhost]
+
+ --web-api-port <WEB_API_PORT>
+ Web API port. [default: 29987]
+
+# The following is an example Fuel indexer configuration file.
+#
+# This configuration spec is intended to be used for a single instance
+# of a Fuel indexer node or service.
+#
+# For more info on how the Fuel indexer works, read the docs: https://docs.fuel.network/docs/indexer/
+# or specifically read up on these configuration options: https://docs.fuel.network/docs/indexer/getting-started/indexer-service-infrastructure/
+
+# Whether to allow replacing an indexer.
+#
+# If this is disabled, then an HTTP 409 Conflict will be returned if an indexer with the same name already exists.
+replace_indexer: false
+
+# Log level passed to the Fuel Indexer service.
+log_level: info
+
+# Use Prometheus metrics reporting.
+metrics: true
+
+# Prevent indexers from running without handling any blocks.
+stop_idle_indexers: false
+
+# Run database migrations before starting service.
+run_migrations: true
+
+# Enable verbose logging.
+verbose: false
+
+# Start a local Fuel node.
+local_fuel_node: false
+
+# Allow network configuration via indexer manifests.
+indexer_net_config: false
+
+# The number of WASM opcodes after which the indexer will stop execution.
+metering_points: 30000000000
+
+# Allow the web server to accept raw SQL queries.
+accept_sql_queries: false
+
+# Amount of blocks to return in a request to a Fuel node.
+block_page_size: 20
+
+# Make the service wait for the given duration between block requests to a Fuel client
+client_request_delay: ~
+
+# ***********************
+# Fuel Node configuration
+# ************************
+
+fuel_node:
+
+ # Host of the running Fuel node.
+ host: localhost
+
+ # Listening port of the running Fuel node.
+ port: 4000
+
+ # Use a network alias when connecting to a Fuel client.
+ network: ~
+
+# *************************
+# Web API configuration
+# *************************
+
+web_api:
+ # Web API host.
+ host: localhost
+
+ # Web API port.
+ port: 29987
+
+ # Max body size for web server requests.
+ max_body_size: "5242880"
+
+# ******************************
+# Database configuration options
+# ******************************
+
+database:
+
+ postgres:
+ # Postgres username.
+ user: postgres
+
+ # Postgres database.
+ database: postgres
+
+ # Postgres password.
+ password: postgres
+
+ # Postgres host.
+ host: localhost
+
+ # Postgres port.
+ port: 5432
+
+# ******************************
+# Indexer service authentication
+# ******************************
+
+authentication:
+ # Require users to authenticate for some operations.
+ enabled: false
+
+ # Which authentication scheme to use.
+ strategy: jwt
+
+ # Secret used if JWT authentication is specified.
+ jwt_secret: abcdefghijklmnopqrstuvwxyz1234567890
+
+ # JWT issuer if JWT authentication is specified.
+ jwt_issuer: FuelLabs
+
+ # Amount of time (seconds) before expiring token if JWT authentication is specified.
+ jwt_expiry: 2592000
+
+# ********************************
+# Rate limit configuration options
+# ********************************
+
+rate_limit:
+ # Enable rate limiting.
+ enabled: false
+
+ # Maximum number of requests to allow over --rate-limit-window..
+ request_count: 10
+
+ # Number of seconds over which to allow --rate-limit-rps.
+ window_size: 5
+
+
+The fuel-indexer-api-server
crate of the Fuel indexer contains a standalone web server that acts as a queryable endpoint on top of the database. Note that the main fuel-indexer
binary of the indexer project also contains the same web server endpoint.
++The
+fuel-indexer-api-server
crate offers a standalone web server endpoint, whereas the API endpoint offered infuel-indexer
is bundled with other Fuel indexer functionality (e.g., execution, handling, data-layer construction, etc). Offering the API server as a separate piece allows users to separate components and run them on different systems, if desired.
++You can start the indexer service with an array of CLI options. Note that most (if not all) of these options include sensible defaults.
+
fuel-indexer-api-server run --help
+
+Fuel indexer web server
+
+USAGE:
+ fuel-indexer-api-server run [OPTIONS]
+
+OPTIONS:
+ --accept-sql-queries
+ Allow the web server to accept raw SQL queries.
+
+ --auth-enabled
+ Require users to authenticate for some operations.
+
+ --auth-strategy <AUTH_STRATEGY>
+ Authentication scheme used. [possible values: jwt]
+
+ -c, --config <CONFIG>
+ API server config file.
+
+ --database <DATABASE>
+ Database type. [default: postgres] [possible values: postgres]
+
+ --disable-toolchain-version-check
+ By default, Fuel Indexer will only accept WASM indexer modules compiled with the same
+ toolchain version as the version of Fuel Indexer.
+
+ --fuel-node-host <FUEL_NODE_HOST>
+ Host of the running Fuel node. [default: localhost]
+
+ --fuel-node-port <FUEL_NODE_PORT>
+ Listening port of the running Fuel node. [default: 4000]
+
+ -h, --help
+ Print help information
+
+ --jwt-expiry <JWT_EXPIRY>
+ Amount of time (seconds) before expiring token (if JWT scheme is specified).
+
+ --jwt-issuer <JWT_ISSUER>
+ Issuer of JWT claims (if JWT scheme is specified).
+
+ --jwt-secret <JWT_SECRET>
+ Secret used for JWT scheme (if JWT scheme is specified).
+
+ --log-level <LOG_LEVEL>
+ Log level passed to the Fuel Indexer service. [default: info] [possible values: info,
+ debug, error, warn]
+
+ --max-body-size <MAX_BODY_SIZE>
+ Max body size for web requests. [default: 5242880]
+
+ --metrics
+ Use Prometheus metrics reporting.
+
+ --network <NETWORK>
+ Use a network alias when connecting to a Fuel client. [possible values: beta-3, beta-5,
+ beta-5]
+
+ --postgres-database <POSTGRES_DATABASE>
+ Postgres database.
+
+ --postgres-host <POSTGRES_HOST>
+ Postgres host.
+
+ --postgres-password <POSTGRES_PASSWORD>
+ Postgres password.
+
+ --postgres-port <POSTGRES_PORT>
+ Postgres port.
+
+ --postgres-user <POSTGRES_USER>
+ Postgres username.
+
+ --rate-limit
+ Enable rate limiting.
+
+ --rate-limit-request-count <RATE_LIMIT_REQUEST_COUNT>
+ Maximum number of requests to allow over --rate-limit-window..
+
+ --rate-limit-window-size <RATE_LIMIT_WINDOW_SIZE>
+ Number of seconds over which to allow --rate-limit-rps.
+
+ --run-migrations
+ Run database migrations before starting service.
+
+ -v, --verbose
+ Enable verbose logging.
+
+ -V, --version
+ Print version information
+
+ --web-api-host <WEB_API_HOST>
+ Web API host. [default: localhost]
+
+ --web-api-port <WEB_API_PORT>
+ Web API port. [default: 29987]
+
+To run the standalone Fuel indexer web server server using a configuration file:
+fuel-indexer-api-server run --config config.yaml
+
+In the above example, config.yaml
is based on the default service configuration file.
In this tutorial you will:
+beta-5
testnet.In this Quickstart, we'll use Fuel's toolchain manager fuelup
in order to install the forc-index
component that we'll use to develop our indexer.
fuelup
To install fuelup with the default features/options, use the following command to download the fuelup installation script and run it interactively.
+curl --proto '=https' --tlsv1.2 -sSf https://install.fuel.network/fuelup-init.sh | sh
+
+++If you require a non-default
+fuelup
installation, please read thefuelup
installation docs.
Indexers are typically compiled to WASM so you'll need to have the proper WASM compilation target available on your system. You can install this target using rustup
:
rustup target add wasm32-unknown-unknown
+
+Additionally, you'll need the wasm-snip
utility in order to remove errant symbols from your compiled WASM binary. You can install this tool using cargo
:
cargo install wasm-snip
+
+forc-index
pluginThe primary means of interfacing with the Fuel indexer for indexer development is the forc-index
CLI tool. forc-index
is a forc
plugin specifically created to interface with the Fuel indexer service. Since we already installed fuelup
in a previous step 1.1, we should be able to check that our forc-index
binary was successfully installed and added to our PATH
.
which forc-index
+
+/Users/me/.fuelup/bin/forc-index
+
+++IMPORTANT:
+fuelup
will install several binaries from the Fuel ecosystem and add them into yourPATH
, including thefuel-indexer
binary. Thefuel-indexer
binary is the primary binary that users can use to spin up a Fuel indexer service.
which fuel-indexer
+
+/Users/me/.fuelup/bin/fuel-indexer
+
+Once the forc-index
plugin is installed, let's go ahead and see what indexer components we have installed.
++Many of these components are required for development work (e.g.,
+fuel-core
,psql
) but some are even required for non-development usage as well (e.g.,wasm-snip
,fuelup
).
forc index check
+
++--------+------------------------+---------------------------------------------------------+
+| Status | Component | Details |
++--------+------------------------+---------------------------------------------------------+
+| ⛔️ | fuel-indexer binary | Can't locate fuel-indexer. |
++--------+------------------------+---------------------------------------------------------+
+| ✅ | fuel-indexer service | Local service found: PID(63967) | Port(29987). |
++--------+------------------------+---------------------------------------------------------+
+| ✅ | psql | /usr/local/bin/psql |
++--------+------------------------+---------------------------------------------------------+
+| ✅ | fuel-core | /Users/me/.cargo/bin/fuel-core |
++--------+------------------------+---------------------------------------------------------+
+| ✅ | docker | /usr/local/bin/docker |
++--------+------------------------+---------------------------------------------------------+
+| ⛔️ | fuelup | Can't locate fuelup. |
++--------+------------------------+---------------------------------------------------------+
+| ✅ | wasm-snip | /Users/me/.cargo/bin/wasm-snip |
++--------+------------------------+---------------------------------------------------------+
+| ⛔️ | forc-postgres | Can't locate fuelup. |
++--------+------------------------+---------------------------------------------------------+
+| ✅ | rustc | /Users/me/.cargo/bin/rustc |
++--------+------------------------+---------------------------------------------------------+
+| ✅ | forc-wallet | /Users/me/.cargo/bin/forc-wallet |
++--------+------------------------+---------------------------------------------------------+
+
+To quickly setup and bootstrap the PostgreSQL database that we'll need, we'll use forc index
.
We can quickly create a bootstrapped database and start the Fuel indexer service by running the following command:
+++IMPORTANT: Below we're specifying our Postgres
+hostname
as--postgres-host postgresql
, but you might need to change this based on your own Postgres instance details (seeforc index start --help
for more details).Additionally, you can try using the
+--embedded-database
flag in order to quickly use an embedded instance of Postgres, but this flag can be flaky, and its ease of use often depends on what platform you're using.If you find that
+--embedded-database
isn't working on your machine (for whatever reason), we strongly recommend that you simply used the Dockerized Fuel indexer components included in the project by using thedocker compose
script included in the project.
forc index start --network beta-5 --run-migrations --postgres-host postgresql
+
+You should see output indicating the successful creation of a database and start of the indexer service; there may be much more content in your session, but it should generally contain output similar to the following lines:
+✅ Successfully started the indexer service at PID 39407
+
+2023-07-31T15:57:28.942954Z INFO fuel_indexer::commands::run: 109: Configuration: IndexerConfig { metering_points: Some(30000000000), log_level: "info", verbose: false, local_fuel_node: false, indexer_net_config: false, fuel_node: FuelClientConfig { host: "beta-5.fuel.network", port: "80" }, web_api: WebApiConfig { host: "localhost", port: "29987", max_body_size: 5242880 }, database: PostgresConfig { user: "postgres", password: "XXXX", host: "localhost", port: "5432", database: "postgres", verbose: "false" }, metrics: false, stop_idle_indexers: false, run_migrations: true, authentication: AuthenticationConfig { enabled: false, strategy: None, jwt_secret: "XXXX", jwt_issuer: None, jwt_expiry: None }, rate_limit: RateLimitConfig { enabled: false, request_count: None, window_size: None }, replace_indexer: false, accept_sql_queries: false }
+2023-07-31T15:57:28.948657Z INFO sqlx::postgres::notice: 157: relation "_sqlx_migrations" already exists, skipping
+2023-07-31T15:57:28.976258Z INFO fuel_indexer::service: 378: Resuming Indexer(fuel.indexer_test) from block 81188
+2023-07-31T15:57:29.077928Z INFO fuel_indexer::database: 187: Loading schema for Indexer(fuel.indexer_test) with Version(2738d221cf1e926d28e62bc93604a96ec6f7c5093e766f45a4555ed06e437b7f).
+2023-07-31T15:57:29.081302Z WARN fuel_indexer::executor: 87: No end_block specified in manifest. Indexer will run forever.
+2023-07-31T15:57:29.081311Z INFO fuel_indexer::executor: 109: Indexer(fuel.indexer_test) subscribing to Fuel node at beta-5.fuel.network:80
+2023-07-31T15:57:29.081424Z INFO fuel_indexer::service: 194: Registered Indexer(fuel.indexer_test)
+2023-07-31T15:57:29.082150Z INFO fuel_indexer_lib::utils: 132: Parsed SocketAddr '127.0.0.1:29987' from 'localhost:29987
+
+Now that we have our development environment set up, the next step is to create an indexer.
+forc index new hello-indexer --namespace fuellabs && cd hello-indexer
+
+++The
+namespace
of your project is a required option. You can think of anamespace
as your organization name or company name. Your project might contain one or many indexers all under the samenamespace
. For a complete list of options passed toforc index new
, see here.
forc index new hello-indexer --namespace FuelLabs
+
+✅ Successfully created indexer
+
+
+███████╗██╗ ██╗███████╗██╗ ██╗███╗ ██╗██████╗ ███████╗██╗ ██╗███████╗██████╗
+██╔════╝██║ ██║██╔════╝██║ ██║████╗ ██║██╔══██╗██╔════╝╚██╗██╔╝██╔════╝██╔══██╗
+█████╗ ██║ ██║█████╗ ██║ ██║██╔██╗ ██║██║ ██║█████╗ ╚███╔╝ █████╗ ██████╔╝
+██╔══╝ ██║ ██║██╔══╝ ██║ ██║██║╚██╗██║██║ ██║██╔══╝ ██╔██╗ ██╔══╝ ██╔══██╗
+██║ ╚██████╔╝███████╗███████╗ ██║██║ ╚████║██████╔╝███████╗██╔╝ ██╗███████╗██║ ██║
+╚═╝ ╚═════╝ ╚══════╝╚══════╝ ╚═╝╚═╝ ╚═══╝╚═════╝ ╚══════╝╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝
+
+
+An easy-to-use, flexible indexing service built to go fast. 🚗💨
+
+
+----
+
+Read the Docs:
+- Fuel Indexer: https://github.com/FuelLabs/fuel-indexer
+- Fuel Indexer Book: https://docs.fuel.network/docs/indexer/
+- Sway Book: https://docs.fuel.network/docs/sway/
+- Rust SDK Book: https://rust.fuel.network
+
+
+Join the Community:
+- Follow us @Fuel: https://twitter.com/fuel_network
+- Ask questions in dev-chat on Discord: https://discord.com/invite/xfpK4Pe
+
+Report Bugs:
+- Fuel Indexer Issues: https://github.com/FuelLabs/fuel-indexer/issues/new
+
+Take a quick tour.
+
+`forc index auth`
+ Authenticate against an indexer service.
+`forc index build`
+ Build an indexer.
+`forc index check`
+ List indexer components.
+`forc index deploy`
+ Deploy an indexer.
+`forc index kill`
+ Kill a running Fuel indexer process on a given port.
+`forc index new`
+ Create a new indexer.
+`forc index remove`
+ Stop a running indexer.
+`forc index start`
+ Start a local indexer service.
+`forc index status`
+ Check the status of an indexer.
+
+At this point, we have a brand new indexer that will index some blocks and transactions. And with both our database and Fuel indexer services up and running, all that's left to do is to build and deploy the indexer in order to see it in action. Let's build and deploy our indexer:
+forc index deploy
+
+++IMPORTANT:
+forc index deploy
by defaults runsforc index build
prior to deploying the indexer. The same result can be produced by runningforc index build
then subsequently runningforc index deploy
. For more info, checkout theforc index deploy
command.
If all goes well, you should see the following:
+▹▹▹▹▹ ⏰ Building... Finished dev [unoptimized + debuginfo] target(s) in 0.96s
+▪▪▪▪▪ ✅ Build succeeded. Deploying indexer
+▪▪▪▪▪ ✅ Successfully deployed indexer.
+
+And we can check the status of our newly deployed indexer using:
+forc index status
+
+Which should show:
+✅ Successfully fetched service health:
+
+client status: OK
+database status: OK
+uptime: 1m 30s
+
+Indexers:
+
+─ fuellabs
+ └─ hello_world
+ • id: 1
+ • created at: 2023-11-08 15:09:49.205698 UTC (52s ago)
+ • status: running
+ • status message:
+ Indexed 5440 blocks
+
+++What is a "deployment" exactly?
+A deployment within the context of Fuel's indexer is a series of steps taken to get your indexer project running in the wild.
+This series of steps involves compiling your indexer project to a
+wasm32-unknown-unknown
target and uploading the indexer to a running Fuel indexer service. The service will then register an executor and build database tables for this indexer. Once this series of steps has completed, your indexer is considered to be "deployed".Users will often find that they're simply deploying their indexers to a Fuel indexer service running on their local machine; this is just one valid use-case described in our infrastructure docs. Keep in mind that the intended use of a Fuel indexer service is as a standalone remote service that may run many different indexers at any given time.
+
With our indexer deployed, we should be able to query for newly indexed data after a few seconds.
+Below, we write a simple GraphQL query that returns a few fields from all transactions that we've indexed.
+You can open your GraphQL query playground at http://127.0.0.1:29987/api/playground/fuellabs/hello_indexer
and submit the following GraphQL query.
query {
+ transaction {
+ id,
+ hash,
+ block {
+ id
+ }
+ }
+}
+
+The response you get should resemble:
+[
+ {
+ "block": {
+ "id": "24002b29ef4331f5ee75a38bf6381f2c8e8d2d5b4d78470706dde7ab0b8d54c0"
+ },
+ "hash": "82b36dce26d926921b8e79597899d8712fdabf2553f28b45ef3851a968efb4b9",
+ "id": "eb7e14822e18e71ba7c92c266b0976acda2344dfbef7a60099d400cc243394fb"
+ },
+ {
+ "block": {
+ "id": "1309ee2cb0846b1a7e45313e1c39b2a24ffd552a381f2f627225256f725a93e3"
+ },
+ "hash": "f0c7c778faa6eb2a8bf03c9c47bb3f836bd4fe37e69c18e30f853ff146522dcb",
+ "id": "182b6343bbbca2fcecf97020ea3f3767b8f5c370a6b853d2add46853e542a113"
+ },
+ {
+ "block": {
+ "id": "95588e20296969a76576d519d301c6cabe1e009675e430da93e18ba2a0d38a49"
+ },
+ "hash": "e729045198ee10dcf49e431f50c2ffe8c37129cbe47e003a59aff81a88b03b50",
+ "id": "6910ebc30a1037b83336c956c95f7fc470c4b76750a93f6a1f6d19a21d058b19"
+ }
+]
+
+Congrats, you just created, built, and deployed your first indexer on the world's fastest execution layer.
+For more info on how indexers work, please checkout the reference guide.
+ +Here is a list of terms and their definitions in order to help users properly understand certain concepts about the Fuel indexer.
+asset
: a component that is used to create and operate an indexerexecutor
: an async task run by an indexerindex
/indices
: data produced by an indexerindexer service
: a service that runs one or more indexersindexer
: an abstraction that takes data from Fuel virtual machine and produces indicesThe Fuel indexer is a standalone service that can be used to index various components of the blockchain. These indexable components include blocks, transactions, receipts, and state within the Fuel network, allowing for high-performance read-only access to the blockchain for advanced dApp use-cases.
+By using a combination of Fuel-flavored GraphQL schema, a SQL backend, and indexers written in Rust, users of the Fuel indexer can get started creating production-ready backends for their dApps, meant to go fast 🚗💨.
+For those wanting to build dApp backends right away, feel free to check out the Quickstart. And for those willing to contribute to the Fuel indexer project, please feel free to read our contributor guidelines as well as the For Contributors chapter of the book.
+ +The Fuel indexer is meant to run alongside a Fuel node and a database. Generally, the typical flow of information through the indexer is as follows:
+++In addition to Fuel-specific types, you can also index custom types triggered in your Sway smart contract.
+
To index custom types from a Sway smart contract, you'll need that specific contract's ABI in JSON format; the JSON ABI is generated as a result of running forc build
to build your contract. After that, the process is similar to indexing Fuel types.
Let's cover some of these concepts in an example below.
+First, let's create a Sway contract with some simple types.
+contract;
+
+use std::logging::log;
+
+struct Addition {
+ added_value: u64,
+ updated_total: u64,
+}
+
+struct Subtraction {
+ subtracted_value: u64,
+ updated_total: u64,
+}
+
+abi ValueStore {
+ #[storage(read, write)]
+ fn add(value: u64);
+
+ #[storage(read, write)]
+ fn subtract(value: u64) -> Subtraction;
+}
+
+storage {
+ total: u64 = 1000,
+}
+
+impl ValueStore for Contract {
+ #[storage(read, write)]
+ fn add(value: u64) {
+ let updated_total = storage.total.read() + value;
+ storage.total.write(updated_total);
+ log(
+ Addition {
+ added_value: value,
+ updated_total
+ }
+ )
+ }
+
+ #[storage(read, write)]
+ fn subtract(value: u64) -> Subtraction {
+ let updated_total = storage.total.read() - value;
+ storage.total.write(updated_total);
+
+ Subtraction {
+ subtracted_value: value,
+ updated_total
+ }
+ }
+}
+
+Addition
and Subtraction
. As we'll soon see, indexers can process custom types that are logged or returned as part of a function.forc build
generates a JSON ABI similar to the lightly-edited one below:{
+ "types": [
+ {
+ "typeId": 0,
+ "type": "()",
+ "components": [],
+ "typeParameters": null
+ },
+ {
+ "typeId": 1,
+ "type": "struct Addition",
+ "components": [
+ {
+ "name": "added_value",
+ "type": 3,
+ "typeArguments": null
+ },
+ {
+ "name": "updated_total",
+ "type": 3,
+ "typeArguments": null
+ }
+ ],
+ "typeParameters": null
+ },
+ {
+ "typeId": 2,
+ "type": "struct Subtraction",
+ "components": [
+ {
+ "name": "subtracted_value",
+ "type": 3,
+ "typeArguments": null
+ },
+ {
+ "name": "updated_total",
+ "type": 3,
+ "typeArguments": null
+ }
+ ],
+ "typeParameters": null
+ },
+ {
+ "typeId": 3,
+ "type": "u64",
+ "components": null,
+ "typeParameters": null
+ }
+ ],
+ "functions": [...],
+ "loggedTypes": [
+ {
+ "logId": 0,
+ "loggedType": {
+ "name": "",
+ "type": 1,
+ "typeArguments": []
+ }
+ }
+ ],
+ "messagesTypes": [...],
+ "configurables": [...]
+}
+
+
+Now that we've discussed how to generate the JSON ABI for our Sway smart contract, let's now cover how to create an associated GraphQL schema.
+To index the contracts and store information about our Sway types in the database, we should create a schema. Let's design a schema that has an entity for each Sway type:
+type AddEntity @entity {
+ id: ID!
+ value: U64!
+ updated_total: U64!
+}
+
+type SubtractEntity @entity {
+ id: ID!
+ value: U64!
+ updated_total: U64!
+}
+
+++Note how the types used here, match the types used in our Sway smart contract. For a detailed mapping of these types, please see the Storing Records section.
+
So far we've covered how to (1) write your Sway smart contract and generate its JSON ABI, and (2) create types in your GraphQL schema that align with your Sway types.
+Next, we'll cover how to write the manifest file for your indexer.
+Before writing any of the handler code for your indexer, we need to make sure that our indexer manifest contains the necessary information to allow for the compiler to parse our contract types.
+Specifically, we should ensure that the contract_abi
and graphql_schema
fields point to the correct locations, respectively.
# A namespace is a logical grouping of declared names. Think of the namespace
+# as an organization identifier
+namespace: fuellabs
+
+# The identifier field is used to identify the given index.
+identifier: custom_types_example
+
+# The abi option is used to provide a link to the Sway JSON ABI that is generated when you
+# build your project.
+abi: path/to/custom/type/example/contract-abi.json
+
+# The particular start block after which you'd like your indexer to start indexing events.
+start_block: ~
+
+# The particular end block after which you'd like your indexer to stop indexing events.
+end_block: ~
+
+# The `fuel_client` denotes the address (host, port combination) of the running Fuel client
+# that you would like your indexer to index events from. In order to use this per-indexer
+# `fuel_client` option, the indexer service at which your indexer is deployed will have to run
+# with the `--indexer_net_config` option.
+fuel_client: ~
+
+# The contract_id specifies which particular contract you would like your index to subscribe to.
+contract_id: ~
+
+# The graphql_schema field contains the file path that points to the GraphQL schema for the
+# given index.
+graphql_schema: path/to/custom/type/example/indexer.schema.graphql
+
+# The module field contains a file path that points to code that will be run as an executor inside
+# of the indexer.
+# Important: At this time, wasm is the preferred method of execution.
+module:
+ wasm: ~
+
+# The resumable field contains a boolean that specifies whether or not the indexer should, synchronise
+# with the latest block if it has fallen out of sync.
+resumable: true
+
+Finally, we can create handlers to index these particular types and store them in the database. Let's look at the following example:
+use fuel_indexer_utils::prelude::*;
+
+#[indexer(manifest = "indexer.manifest.yaml")]
+mod indexer_mod {
+ fn index_addition(addition_event: Addition) {
+ let addition = AddEntity {
+ id: 123,
+ value: addition_event.added_value,
+ updated_total: addition_event.updated_total
+ };
+ addition.save();
+ }
+
+ fn index_subtraction(subtraction_event: Subtraction) {
+ let subtraction = SubtractEntity {
+ id: 123,
+ value: subtraction_event.subtracted_value,
+ updated_total: subtraction_event.updated_total
+ };
+ subtraction.save();
+ }
+}
+Regardless of whether a custom type was logged (e.g. Addition
) or returned (e.g. Subtraction
), the type will be available for you to use in your functions. Just include the type(s) you want your function to use in the parameters, and the function will be executed whenever each of the parameters have been satisfied by an instance of the type(s).
BlockData
++The
+BlockData
struct is how blocks are represented in the Fuel indexer. It contains metadata such as the ID, height, and time, as well as a list of the transactions it contains (represented byTransactionData
). It also contains the public key hash of the block producer, if present.
pub struct BlockData {
+ pub height: u32,
+ pub id: Bytes32,
+ pub header: Header,
+ pub producer: Option<Bytes32>,
+ pub time: i64,
+ pub consensus: Consensus,
+ pub transactions: Vec<TransactionData>,
+}
+extern crate alloc;
+use fuel_indexer_utils::prelude::*;
+
+#[indexer(manifest = "indexer.manifest.yaml")]
+mod indexer_mod {
+ fn handle_block(block_data: BlockData) {
+ let height = block_data.header.height;
+ info!("This block #{height}");
+ }
+}
+
+ This document provides information about Fuel-specific types and provides examples on how to index each type.
+Every transaction in the Fuel network contains a list of receipts with information about that transaction, including what contract function was called, logged data, data returned from a function, etc.
+There are several types of receipts that can be attached to a transaction and indexed. You can learn more about each of these in the sections below.
+Burn
Call
Log
LogData
MessageOut
Mint
Panic
Return
ReturnData
Revert
ScriptResult
Transfer
TransferOut
Burn
A Burn
receipt is generated whenever an asset is burned in a Sway contract. Read more about Burn
in the Fuel protocol ABI spec.
use fuel_types::{AssetId, ContractId};
+pub struct Burn {
+ pub sub_id: AssetId,
+ pub contract_id: ContractId,
+ pub val: u64,
+ pub pc: u64,
+ pub is: u64,
+}
+mod indexer_mod {
+ fn handle_burn_receipt(block_data: BlockData) {
+ let height = block_data.header.height;
+ if !block_data.transactions.is_empty() {
+ let transaction = block_data.transactions[0];
+ for receipt in transaction.receipts {
+ match receipt {
+ fuel::Receipt::Burn { contract_id, .. } => {
+ info!("Found burn receipt from contract {contract_id:?}");
+ }
+ }
+ }
+ }
+ }
+}
+Call
A Call
receipt is generated whenever a function is called in a Sway contract. The fn_name
field contains the name of the called function from the aforementioned contract. Read more about Call
in the Fuel protocol ABI spec.
use fuel_types::{AssetId, ContractId};
+pub struct Call {
+ pub contract_id: ContractId,
+ pub to: ContractId,
+ pub amount: u64,
+ pub asset_id: AssetId,
+ pub gas: u64,
+ pub fn_name: String,
+}
+extern crate alloc;
+use fuel_indexer_utils::prelude::*;
+
+#[indexer(manifest = "indexer.manifest.yaml")]
+mod indexer_mod {
+ fn handle_call_receipt(block_data: BlockData) {
+ let height = block_data.header.height;
+ if !block_data.transactions.is_empty() {
+ let transaction = block_data.transactions[0];
+ for receipt in transaction.receipts {
+ match receipt {
+ fuel::Receipt::Call { contract_id, .. } => {
+ info!("Found call receipt from contract {contract_id:?}");
+ }
+ }
+ }
+ }
+ }
+}
+Log
A Log
receipt is generated when calling log()
on a non-reference types in a Sway contracts - specifically bool
, u8
, u16
, u32
, and u64
. The ra
field includes the value being logged while rb
may include a non-zero value representing a unique ID for the log
instance. Read more about Log
in the Fuel protocol ABI spec.
use fuel_types::ContractId;
+pub struct Log {
+ pub contract_id: ContractId,
+ pub ra: u64,
+ pub rb: u64,
+}
+extern crate alloc;
+use fuel_indexer_utils::prelude::*;
+
+#[indexer(manifest = "indexer.manifest.yaml")]
+mod indexer_mod {
+ fn handle_log_receipt(block_data: BlockData) {
+ let height = block_data.header.height;
+ if !block_data.transactions.is_empty() {
+ let transaction = block_data.transactions[0];
+ for receipt in transaction.receipts {
+ match receipt {
+ fuel::Receipt::Log { contract_id, .. } => {
+ info!("Found log receipt from contract {contract_id:?}");
+ }
+ }
+ }
+ }
+ }
+}
+LogData
A LogData
receipt is generated when calling log()
in a Sway contract on a reference type; this includes all types except non-reference types. The data
field will include the logged value as a hexadecimal. The rb
field will contain a unique ID that can be used to look up the logged data type. Read more about LogData
in the Fuel protocol ABI spec.
+>
use fuel_types::ContractId;
+pub struct LogData {
+ pub contract_id: ContractId,
+ pub data: Vec<u8>,
+ pub rb: u64,
+ pub len: u64,
+ pub ptr: u64,
+}
+++Note: the example below will run both when the type
+MyEvent
is logged as well as whenMyEvent
is returned from a function.
extern crate alloc;
+use fuel_indexer_utils::prelude::*;
+
+#[indexer(manifest = "indexer.manifest.yaml")]
+mod indexer_mod {
+ fn handle_log_data(event: MyEvent) {
+ info!("Event {event:?} was logged in the contract");
+ }
+}
+MessageOut
A MessageOut
receipt is generated as a result of the send_typed_message()
Sway method in which a message is sent to a recipient address along with a certain amount of coins. The data
field supports data of an arbitrary type T
and will be decoded by the indexer upon receipt. Read more about MessageOut
in the Fuel protocol ABI spec.
use fuel_types::{MessageId, Bytes32, Address};
+pub struct MessageOut {
+ pub message_id: MessageId,
+ pub sender: Address,
+ pub recipient: Address,
+ pub amount: u64,
+ pub nonce: Bytes32,
+ pub len: u64,
+ pub digest: Bytes32,
+ pub data: Vec<u8>,
+}
+extern crate alloc;
+use fuel_indexer_utils::prelude::*;
+
+#[indexer(manifest = "indexer.manifest.yaml")]
+mod indexer_mod {
+ fn handle_message_out(event: MyEvent) {
+ info!("Event {event:?} was logged in the contract");
+ }
+}
+Mint
A Mint
receipt is generated whenever an asset is burned in a Sway contract. Read more about Mint
in the Fuel protocol ABI spec.
use fuel_types::{AssetId, ContractId};
+pub struct Mint {
+ pub sub_id: AssetId,
+ pub contract_id: ContractId,
+ pub val: u64,
+ pub pc: u64,
+ pub is: u64,
+}
+extern crate alloc;
+use fuel_indexer_utils::prelude::*;
+
+#[indexer(manifest = "indexer.manifest.yaml")]
+mod indexer_mod {
+ fn handle_mint_receipt(block_data: BlockData) {
+ let height = block_data.header.height;
+ if !block_data.transactions.is_empty() {
+ let transaction = block_data.transactions[0];
+ for receipt in transaction.receipts {
+ match receipt {
+ fuel::Receipt::Mint { contract_id, .. } => {
+ info!("Found mint receipt from contract {contract_id:?}");
+ }
+ }
+ }
+ }
+ }
+}
+Panic
A Panic
receipt is produced when a Sway smart contract call fails for a reason that doesn't produce a revert. The reason field records the reason for the panic, which is represented by a number between 0 and 255. You can find the mapping between the values and their meanings here in the FuelVM source code. Read more about Panic
in the Fuel protocol spec.
use fuel_types::ContractId;
+pub struct Panic {
+ pub contract_id: ContractId,
+ pub reason: u32,
+}
+extern crate alloc;
+use fuel_indexer_utils::prelude::*;
+
+#[indexer(manifest = "indexer.manifest.yaml")]
+mod indexer_mod {
+ fn handle_panic_receipt(block_data: BlockData) {
+ let height = block_data.header.height;
+ if !block_data.transactions.is_empty() {
+ let transaction = block_data.transactions[0];
+ for receipt in transaction.receipts {
+ match receipt {
+ fuel::Receipt::Panic { contract_id, .. } => {
+ info!("Found panic receipt from contract {contract_id:?}");
+ }
+ }
+ }
+ }
+ }
+}
+Return
A Return
receipt is generated when returning a non-reference type in a Sway contract, specifically bool
, u8
, u16
, u32
, and u64
. The val
field includes the value being returned. Read more about Return
in the Fuel protocol spec.
use fuel_types::ContractId;
+pub struct Return {
+ pub contract_id: ContractId,
+ pub val: u64,
+ pub pc: u64,
+ pub is: u64,
+}
+You can handle functions that produce a Return
receipt type by adding a parameter with the type Return
.
extern crate alloc;
+use fuel_indexer_utils::prelude::*;
+
+#[indexer(manifest = "indexer.manifest.yaml")]
+mod indexer_mod {
+ fn handle_return_receipt(block_data: BlockData) {
+ let height = block_data.header.height;
+ if !block_data.transactions.is_empty() {
+ let transaction = block_data.transactions[0];
+ for receipt in transaction.receipts {
+ match receipt {
+ fuel::Receipt::Return { contract_id, .. } => {
+ info!("Found return receipt from contract {contract_id:?}");
+ }
+ }
+ }
+ }
+ }
+}
+ReturnData
A ReturnData
receipt is generated when returning a reference type in a Sway contract; this includes all types except non-reference types. The data
field will include the returned value as a hexadecimal. Read more about ReturnData
in the Fuel protocol ABI spec.
use fuel_types::ContractId;
+pub struct ReturnData {
+ id: ContractId,
+ data: Vec<u8>,
+}
+++Note: the example below will run both when the type
+MyStruct
is logged as well as whenMyStruct
is returned from a function.
extern crate alloc;
+use fuel_indexer_utils::prelude::*;
+
+#[indexer(manifest = "indexer.manifest.yaml")]
+mod indexer_mod {
+ fn handle_return_data(event: MyStruct) {
+ info!("MyStruct is: {event:#}");
+ }
+}
+Revert
A Revert
receipt is produced when a Sway smart contract function call fails. The table below lists possible reasons for the failure and their values. The error_val
field records these values, enabling your indexer to identify the specific cause of the reversion. Read more about Revert
in the Fuel protocol spec.
use fuel_types::ContractId;
+pub struct Revert {
+ pub contract_id: ContractId,
+ pub error_val: u64,
+}
+Reason | Value |
---|---|
FailedRequire | 0 |
FailedTransferToAddress | 1 |
FailedSendMessage | 2 |
FailedAssertEq | 3 |
FailedAssert | 4 |
extern crate alloc;
+use fuel_indexer_utils::prelude::*;
+
+#[indexer(manifest = "indexer.manifest.yaml")]
+mod indexer_mod {
+ fn handle_revert_receipt(block_data: BlockData) {
+ let height = block_data.header.height;
+ if !block_data.transactions.is_empty() {
+ let transaction = block_data.transactions[0];
+ for receipt in transaction.receipts {
+ match receipt {
+ fuel::Receipt::Revert { contract_id, .. } => {
+ info!("Found return receipt from contract {contract_id:?}");
+ }
+ }
+ }
+ }
+ }
+}
+ScriptResult
A ScriptResult
receipt is generated when a contract call resolves; that is, it's generated as a result of the RET
, RETD
, and RVRT
instructions. The result
field will contain a 0
for success, and a non-zero value otherwise. Read more about ScriptResult
in the Fuel protocol spec.
pub struct ScriptResult {
+ pub result: u64,
+ pub gas_used: u64,
+}
+extern crate alloc;
+use fuel_indexer_utils::prelude::*;
+
+#[indexer(manifest = "indexer.manifest.yaml")]
+mod indexer_mod {
+ fn handle_script_result_receipt(block_data: BlockData) {
+ let height = block_data.header.height;
+ if !block_data.transactions.is_empty() {
+ let transaction = block_data.transactions[0];
+ for receipt in transaction.receipts {
+ match receipt {
+ fuel::Receipt::ScriptResult { result, .. } => {
+ info!("Result from script: {result:?}");
+ }
+ }
+ }
+ }
+ }
+}
+Transfer
A Transfer
receipt is generated when coins are transferred to a contract as part of a Sway contract. The asset_id
field contains the asset ID of the transferred coins, as the FuelVM has built-in support for working with multiple assets. The pc
and is
fields aren't currently used for anything, but are included for completeness. Read more about Transfer
in the Fuel protocol spec.
use fuel_types::{ContractId, AssetId};
+pub struct Transfer {
+ pub contract_id: ContractId,
+ pub to: ContractId,
+ pub amount: u64,
+ pub asset_id: AssetId,
+ pub pc: u64,
+ pub is: u64,
+}
+extern crate alloc;
+use fuel_indexer_utils::prelude::*;
+
+#[indexer(manifest = "indexer.manifest.yaml")]
+mod indexer_mod {
+ fn handle_transfer_receipt(block_data: BlockData) {
+ let height = block_data.header.height;
+ if !block_data.transactions.is_empty() {
+ let transaction = block_data.transactions[0];
+ for receipt in transaction.receipts {
+ match receipt {
+ fuel::Receipt::Transfer { contract_id, .. } => {
+ info!("Found transfer receipt from contract {contract_id:?}");
+ }
+ }
+ }
+ }
+ }
+}
+TransferOut
A TransferOut
receipt is generated when coins are transferred to an address rather than a contract. Every other field of the receipt works the same way as it does in the Transfer
receipt. Read more about TransferOut
in the Fuel protocol spec.
use fuel_types::{ContractId, AssetId, Address};
+pub struct TransferOut {
+ pub contract_id: ContractId,
+ pub to: Address,
+ pub amount: u64,
+ pub asset_id: AssetId,
+ pub pc: u64,
+ pub is: u64,
+}
+extern crate alloc;
+use fuel_indexer_utils::prelude::*;
+
+#[indexer(manifest = "indexer.manifest.yaml")]
+mod indexer_mod {
+ fn handle_transfer_out_receipt(block_data: BlockData) {
+ let height = block_data.header.height;
+ if !block_data.transactions.is_empty() {
+ let transaction = block_data.transactions[0];
+ for receipt in transaction.receipts {
+ match receipt {
+ fuel::Receipt::TransferOut { contract_id, .. } => {
+ info!("Found transfer_out receipt from contract {contract_id:?}");
+ }
+ }
+ }
+ }
+ }
+}
+
+ TransactionData
The TransactionData
struct contains important information about a transaction in the Fuel network. The id
field is the transaction hash, which is a 32-byte string. The receipts
field contains a list of Receipts
, which are generated by a Fuel node during the execution of a Sway smart contract; you can find more information in the Receipts section.
pub struct TransactionData {
+ pub transaction: Transaction,
+ pub status: TransactionStatus,
+ pub receipts: Vec<Receipt>,
+ pub id: TxId,
+}
+extern crate alloc;
+use fuel_indexer_utils::prelude::*;
+
+#[indexer(manifest = "indexer.manifest.yaml")]
+mod indexer_mod {
+ fn handle_transaction(block_data: BlockData) {
+ let height = block_data.header.height;
+ if !block_data.transactions.is_empty() {
+ let transaction = block_data.transactions[0];
+ info!(
+ "Transaction {} in block at height {} has {} receipts",
+ transaction.id,
+ block_data.header.height,
+ transaction.receipts.len()
+ );
+ }
+ }
+}
+TransactionStatus
TransactionStatus
refers to the status of a Transaction
in the Fuel network.
pub enum TransactionStatus {
+ Failure {
+ block_id: String,
+ time: DateTime<Utc>,
+ reason: String,
+ },
+ SqueezedOut {
+ reason: String,
+ },
+ Submitted {
+ submitted_at: DateTime<Utc>,
+ },
+ Success {
+ block_id: String,
+ time: DateTime<Utc>,
+ },
+}
+extern crate alloc;
+use fuel_indexer_utils::prelude::*;
+
+#[indexer(manifest = "indexer.manifest.yaml")]
+mod indexer_mod {
+ fn handle_transaction(block_data: BlockData) {
+ let height = block_data.header.height;
+ if !block_data.transactions.is_empty() {
+ let transaction = block_data.transactions[0];
+ match transaction.transaction {
+ fuel::Transaction::Script(tx) => match tx.status {
+ fuel::TransactionStatus::Success { block_id, time } => {
+ info!(
+ "Transaction {} in block {} was successful at {}",
+ tx.id, block_id, time
+ );
+ }
+ },
+ _ => {
+ info!("We don't care about this transaction type");
+ }
+ }
+ }
+ }
+}
+
+ The Fuel indexer is a standalone service that can be used to index various components of the blockchain. These indexable components include blocks, transactions, receipts, and state within the Fuel network, allowing for high-performance read-only access to the blockchain for advanced dApp use-cases.
+By using a combination of Fuel-flavored GraphQL schema, a SQL backend, and indexers written in Rust, users of the Fuel indexer can get started creating production-ready backends for their dApps, meant to go fast 🚗💨.
+For those wanting to build dApp backends right away, feel free to check out the Quickstart. And for those willing to contribute to the Fuel indexer project, please feel free to read our contributor guidelines as well as the For Contributors chapter of the book.
+ +The Fuel indexer is meant to run alongside a Fuel node and a database. Generally, the typical flow of information through the indexer is as follows:
+++This guide covers some of the basics with regard to installing dependencies for the Fuel indexer service. However, note that this guide is meant to be a general overview for most platforms and by no means covers all platforms.
+If you're having trouble with dependencies on your system, we recommend that you use
+docker
.
To run the Fuel indexer, you'll need to install a few dependencies on your system:
+wasm32-unknown-unknown
rustup
targetwasm-snip
, a utility for stripping symbols from WebAssembly binaries.++If you don't want to install a database directly onto your system, you can use Docker to run a database in an isolated container. You can install Docker by following its installation instructions.
+For reference purposes, we provide a
+docker compose
file that comes with a PostgreSQL server and a Fuel indexer service.
Fuel
toolchainPlease visit the Fuel installation guide to install the Fuel toolchain, which includes binaries for the Fuel indexer.
+The Fuel indexer requires the use of a database. We currently support PostgreSQL.
+++IMPORTANT: Fuel Indexer users on most platforms don't need to explicitly install PostgreSQL software via a package manager. When starting the indexer service via
+forc index start
simply pass the--embedded-database
flag in order to have the indexer service download and start an embedded PostgreSQL instance viaforc index postgres
.However note that this
+--embedded-database
functionality can be a bit brittle or flaky on some platforms, so alternative methods of installing or using PostgreSQL are briefly mentioned below.
On macOS systems, you can install PostgreSQL through Homebrew. If it isn't present on your system, you can install it according to the instructions.
+Once installed, you can add PostgreSQL to your system by running brew install postgresql
.
Two additional cargo components will be required to build your indexers: wasm-snip
and the wasm32-unknown-unknown
target.
++As of this writing, there is a small bug in newly built Fuel indexer WASM modules that produces a WASM runtime error due an errant upstream dependency. For now, you can use
+wasm-snip
to remove the errant symbols from the WASM module, and prevent this issue from happening. An example can be found in the related script here.Note that since
+wasm-snip
strips Web Assembly related symbols, users will temporarily not be allowed to include other WASM-friendly crates (e.g.,chrono
) in their indexers.
wasm-snip
To install the wasm-snip
:
cargo install wasm-snip
+
+wasm32
targetTo install the wasm32-unknown-unknown
target via rustup
:
rustup target add wasm32-unknown-unknown
+
+++IMPORTANT: Users on Apple Silicon macOS systems may experience trouble when trying to build WASM modules due to its
+clang
binary not supporting WASM targets. If encountered, you can install a binary with better support from Homebrew (brew install llvm
) and instructrustc
to leverage it by setting the following environment variables:+
+- +
AR=/opt/homebrew/opt/llvm/bin/llvm-ar
- +
CC=/opt/homebrew/opt/llvm/bin/clang
Additionally, on some systems you need to explicitly link
+clang
tollvm
.+
+- +
LIBCLANG_PATH="/opt/homebrew/opt/llvm/lib"
- +
LDFLAGS="-L/opt/homebrew/opt/llvm/lib"
- +
CPPFLAGS="-I/opt/homebrew/opt/llvm/include"
A Fuel indexer service instance requires just three components:
+a Fuel Node: Custom indexers monitor incoming blocks via a Fuel GraphQL server and extract information about the state of the Fuel blockchain.
+a PostgreSQL database server: Extracted information is saved into a database.
+a Web Server: dApps can query indexers for up-to-date information and operators can deploy/remove indexers as needed.
+Component | Default Host | Default Port | CLI Argument | Environment Variable |
---|---|---|---|---|
Fuel Node | localhost | 4000 | --fuel-node-{host,port} | $FUEL_NODE_{HOST,PORT} |
Database Server | localhost | 5432 | --postgres-{username,database,password,host,port} | $POSTGRES_{USERNAME,DATABASE,PASSWORD,HOST,PORT} |
Indexer Service Web API | localhost | 29987 | --web-api-{host,port} | $WEB_API_{HOST,PORT} |
The Fuel indexer service will connect to any Fuel GraphQL server, which means you can run your own node or use a node provided by Fuel. The indexer service web server is included with the Fuel indexer; it's available as soon as the indexer is started through fuel-indexer run
. The only component that isn't provided for you is a Postgres database server. You should set up a server according to your own needs and specifications.
++You can start the indexer service with an array of CLI options. Note that most (if not all) of these options include sensible defaults.
+
fuel-indexer run --help
+
+Standalone binary for the fuel indexer service.
+
+USAGE:
+ fuel-indexer run [OPTIONS]
+
+OPTIONS:
+ --accept-sql-queries
+ Allow the web server to accept raw SQL queries.
+
+ --allow-non-sequential-blocks
+ Allow missing blocks or non-sequential block processing.
+
+ --auth-enabled
+ Require users to authenticate for some operations.
+
+ --auth-strategy <AUTH_STRATEGY>
+ Authentication scheme used.
+
+ --block-page-size <BLOCK_PAGE_SIZE>
+ Amount of blocks to return in a request to a Fuel node. [default: 20]
+
+ -c, --config <FILE>
+ Indexer service config file.
+
+ --client-request-delay <CLIENT_REQUEST_DELAY>
+ Make the service wait for the given duration between block requests to a Fuel client.
+
+ --database <DATABASE>
+ Database type. [default: postgres] [possible values: postgres]
+
+ --disable-toolchain-version-check
+ By default, Fuel Indexer will only accept WASM indexer modules compiled with the same
+ toolchain version as the version of Fuel Indexer.
+
+ --embedded-database
+ Automatically create and start database using provided options or defaults.
+
+ --fuel-node-host <FUEL_NODE_HOST>
+ Host of the running Fuel node. [default: localhost]
+
+ --fuel-node-port <FUEL_NODE_PORT>
+ Listening port of the running Fuel node. [default: 4000]
+
+ -h, --help
+ Print help information
+
+ --indexer-net-config
+ Allow network configuration via indexer manifests.
+
+ --jwt-expiry <JWT_EXPIRY>
+ Amount of time (seconds) before expiring token (if JWT scheme is specified).
+
+ --jwt-issuer <JWT_ISSUER>
+ Issuer of JWT claims (if JWT scheme is specified).
+
+ --jwt-secret <JWT_SECRET>
+ Secret used for JWT scheme (if JWT scheme is specified).
+
+ --local-fuel-node
+ Start a local Fuel node.
+
+ --log-level <LOG_LEVEL>
+ Log level passed to the Fuel Indexer service. [default: info] [possible values: info,
+ debug, error, warn]
+
+ -m, --manifest <FILE>
+ Indexer config file.
+
+ --max-body-size <MAX_BODY_SIZE>
+ Max body size for web server requests. [default: 5242880]
+
+ --metering-points <METERING_POINTS>
+ The number of WASM opcodes after which the indexer's event handler will stop execution.
+ [default: 30000000000]
+
+ --metrics
+ Use Prometheus metrics reporting.
+
+ --network <NETWORK>
+ Use a network alias when connecting to a Fuel client. [possible values: beta-3, beta-5,
+ beta-5]
+
+ --postgres-database <POSTGRES_DATABASE>
+ Postgres database.
+
+ --postgres-host <POSTGRES_HOST>
+ Postgres host.
+
+ --postgres-password <POSTGRES_PASSWORD>
+ Postgres password.
+
+ --postgres-port <POSTGRES_PORT>
+ Postgres port.
+
+ --postgres-user <POSTGRES_USER>
+ Postgres username.
+
+ --rate-limit
+ Enable rate limiting.
+
+ --rate-limit-request-count <RATE_LIMIT_REQUEST_COUNT>
+ Maximum number of requests to allow over --rate-limit-window..
+
+ --rate-limit-window-size <RATE_LIMIT_WINDOW_SIZE>
+ Number of seconds over which to allow --rate-limit-rps.
+
+ --remove-data
+ When replacing an indexer, also remove the indexed data.
+
+ --replace-indexer
+ Whether to allow replacing an existing indexer. If not specified, an attempt to deploy
+ over an existing indexer results in an error.
+
+ --run-migrations
+ Run database migrations before starting service.
+
+ --stop-idle-indexers
+ Prevent indexers from running without handling any blocks.
+
+ -v, --verbose
+ Enable verbose logging.
+
+ -V, --version
+ Print version information
+
+ --web-api-host <WEB_API_HOST>
+ Web API host. [default: localhost]
+
+ --web-api-port <WEB_API_PORT>
+ Web API port. [default: 29987]
+
+# The following is an example Fuel indexer configuration file.
+#
+# This configuration spec is intended to be used for a single instance
+# of a Fuel indexer node or service.
+#
+# For more info on how the Fuel indexer works, read the docs: https://docs.fuel.network/docs/indexer/
+# or specifically read up on these configuration options: https://docs.fuel.network/docs/indexer/getting-started/indexer-service-infrastructure/
+
+# Whether to allow replacing an indexer.
+#
+# If this is disabled, then an HTTP 409 Conflict will be returned if an indexer with the same name already exists.
+replace_indexer: false
+
+# Log level passed to the Fuel Indexer service.
+log_level: info
+
+# Use Prometheus metrics reporting.
+metrics: true
+
+# Prevent indexers from running without handling any blocks.
+stop_idle_indexers: false
+
+# Run database migrations before starting service.
+run_migrations: true
+
+# Enable verbose logging.
+verbose: false
+
+# Start a local Fuel node.
+local_fuel_node: false
+
+# Allow network configuration via indexer manifests.
+indexer_net_config: false
+
+# The number of WASM opcodes after which the indexer will stop execution.
+metering_points: 30000000000
+
+# Allow the web server to accept raw SQL queries.
+accept_sql_queries: false
+
+# Amount of blocks to return in a request to a Fuel node.
+block_page_size: 20
+
+# Make the service wait for the given duration between block requests to a Fuel client
+client_request_delay: ~
+
+# ***********************
+# Fuel Node configuration
+# ************************
+
+fuel_node:
+
+ # Host of the running Fuel node.
+ host: localhost
+
+ # Listening port of the running Fuel node.
+ port: 4000
+
+ # Use a network alias when connecting to a Fuel client.
+ network: ~
+
+# *************************
+# Web API configuration
+# *************************
+
+web_api:
+ # Web API host.
+ host: localhost
+
+ # Web API port.
+ port: 29987
+
+ # Max body size for web server requests.
+ max_body_size: "5242880"
+
+# ******************************
+# Database configuration options
+# ******************************
+
+database:
+
+ postgres:
+ # Postgres username.
+ user: postgres
+
+ # Postgres database.
+ database: postgres
+
+ # Postgres password.
+ password: postgres
+
+ # Postgres host.
+ host: localhost
+
+ # Postgres port.
+ port: 5432
+
+# ******************************
+# Indexer service authentication
+# ******************************
+
+authentication:
+ # Require users to authenticate for some operations.
+ enabled: false
+
+ # Which authentication scheme to use.
+ strategy: jwt
+
+ # Secret used if JWT authentication is specified.
+ jwt_secret: abcdefghijklmnopqrstuvwxyz1234567890
+
+ # JWT issuer if JWT authentication is specified.
+ jwt_issuer: FuelLabs
+
+ # Amount of time (seconds) before expiring token if JWT authentication is specified.
+ jwt_expiry: 2592000
+
+# ********************************
+# Rate limit configuration options
+# ********************************
+
+rate_limit:
+ # Enable rate limiting.
+ enabled: false
+
+ # Maximum number of requests to allow over --rate-limit-window..
+ request_count: 10
+
+ # Number of seconds over which to allow --rate-limit-rps.
+ window_size: 5
+
+
+The fuel-indexer-api-server
crate of the Fuel indexer contains a standalone web server that acts as a queryable endpoint on top of the database. Note that the main fuel-indexer
binary of the indexer project also contains the same web server endpoint.
++The
+fuel-indexer-api-server
crate offers a standalone web server endpoint, whereas the API endpoint offered infuel-indexer
is bundled with other Fuel indexer functionality (e.g., execution, handling, data-layer construction, etc). Offering the API server as a separate piece allows users to separate components and run them on different systems, if desired.
++You can start the indexer service with an array of CLI options. Note that most (if not all) of these options include sensible defaults.
+
fuel-indexer-api-server run --help
+
+Fuel indexer web server
+
+USAGE:
+ fuel-indexer-api-server run [OPTIONS]
+
+OPTIONS:
+ --accept-sql-queries
+ Allow the web server to accept raw SQL queries.
+
+ --auth-enabled
+ Require users to authenticate for some operations.
+
+ --auth-strategy <AUTH_STRATEGY>
+ Authentication scheme used. [possible values: jwt]
+
+ -c, --config <CONFIG>
+ API server config file.
+
+ --database <DATABASE>
+ Database type. [default: postgres] [possible values: postgres]
+
+ --disable-toolchain-version-check
+ By default, Fuel Indexer will only accept WASM indexer modules compiled with the same
+ toolchain version as the version of Fuel Indexer.
+
+ --fuel-node-host <FUEL_NODE_HOST>
+ Host of the running Fuel node. [default: localhost]
+
+ --fuel-node-port <FUEL_NODE_PORT>
+ Listening port of the running Fuel node. [default: 4000]
+
+ -h, --help
+ Print help information
+
+ --jwt-expiry <JWT_EXPIRY>
+ Amount of time (seconds) before expiring token (if JWT scheme is specified).
+
+ --jwt-issuer <JWT_ISSUER>
+ Issuer of JWT claims (if JWT scheme is specified).
+
+ --jwt-secret <JWT_SECRET>
+ Secret used for JWT scheme (if JWT scheme is specified).
+
+ --log-level <LOG_LEVEL>
+ Log level passed to the Fuel Indexer service. [default: info] [possible values: info,
+ debug, error, warn]
+
+ --max-body-size <MAX_BODY_SIZE>
+ Max body size for web requests. [default: 5242880]
+
+ --metrics
+ Use Prometheus metrics reporting.
+
+ --network <NETWORK>
+ Use a network alias when connecting to a Fuel client. [possible values: beta-3, beta-5,
+ beta-5]
+
+ --postgres-database <POSTGRES_DATABASE>
+ Postgres database.
+
+ --postgres-host <POSTGRES_HOST>
+ Postgres host.
+
+ --postgres-password <POSTGRES_PASSWORD>
+ Postgres password.
+
+ --postgres-port <POSTGRES_PORT>
+ Postgres port.
+
+ --postgres-user <POSTGRES_USER>
+ Postgres username.
+
+ --rate-limit
+ Enable rate limiting.
+
+ --rate-limit-request-count <RATE_LIMIT_REQUEST_COUNT>
+ Maximum number of requests to allow over --rate-limit-window..
+
+ --rate-limit-window-size <RATE_LIMIT_WINDOW_SIZE>
+ Number of seconds over which to allow --rate-limit-rps.
+
+ --run-migrations
+ Run database migrations before starting service.
+
+ -v, --verbose
+ Enable verbose logging.
+
+ -V, --version
+ Print version information
+
+ --web-api-host <WEB_API_HOST>
+ Web API host. [default: localhost]
+
+ --web-api-port <WEB_API_PORT>
+ Web API port. [default: 29987]
+
+To run the standalone Fuel indexer web server server using a configuration file:
+fuel-indexer-api-server run --config config.yaml
+
+In the above example, config.yaml
is based on the default service configuration file.
Since many users may be familiar with indexing by using a solution like The Graph, it may be helpful to provide a comparison between The Graph and the Fuel indexer.
+Generally, the biggest conceptual differences between Fuel's indexer service and other indexer services (such as The Graph) are indexing speed, general ease of use, and indexable data types.
+Using Fuel's indexers, users can index about 30 blocks per second on a standard Macbook Pro on an M1 chip. This type of indexing speed is a boon to smart contract authors who need to iterate quickly while building dApps.
+Unlike other indexing services, users can use the forc index CLI tool to create, deploy, update, re-deploy, remove, and check the status of their indexers. The ability to completely manage, maintain, and improve remote indexers without having to open any files, or edit any source code completely sets Fuel's indexer apart from other services in the space.
+The Fuel indexer is tailored for compatibility with the FuelVM. This means that instead of being limited to the primitives of the Ethereum virtual machine (EVM), users of the Fuel indexer gain access to a much richer set of indexable abstractions provided by the FuelVM (e.g. predicates, transaction receipts, etc).
+Legend:
+Feature | The Graph | Fuel Indexer | Notes |
---|---|---|---|
Hosted Indexers | 🟩 | 🟩 | |
WASM Execution | 🟩 | 🟩 | |
Handlers | 🟩 | 🟩 | see Indexing Fuel Types and Indexing Custom Types |
Updatable Schemas | 🟩 | 🟩 | |
API Authentication | 🟩 | 🟩 | |
Starting Block Configuration | 🟩 | 🟩 | |
Native Unit Testing Framework | 🟩 | 🟥 | Users are able to use cargo test |
GraphQL: Sorting, Pagination, Filtering | 🟩 | 🟩 | |
Schema: Enum, Object, and Union types | 🟩 | 🟩 | |
Schema: One-to-one, one-to-many, many-to-many relationships | 🟩 | 🟩 | |
AssemblyScript Support | 🟩 | 🟥 | |
Admin Portal UI | 🟩 | 🟥 | |
Stop, Remove, Re-deploy indexers without smart contract changes | 🟥 | 🟩 | |
Update & redeploy indexers with 0 downtime | 🟥 | 🟩 | |
Use third party dependencies in your indexers | 🟥 | 🟩 |
In this tutorial you will:
+beta-5
testnet.In this Quickstart, we'll use Fuel's toolchain manager fuelup
in order to install the forc-index
component that we'll use to develop our indexer.
fuelup
To install fuelup with the default features/options, use the following command to download the fuelup installation script and run it interactively.
+curl --proto '=https' --tlsv1.2 -sSf https://install.fuel.network/fuelup-init.sh | sh
+
+++If you require a non-default
+fuelup
installation, please read thefuelup
installation docs.
Indexers are typically compiled to WASM so you'll need to have the proper WASM compilation target available on your system. You can install this target using rustup
:
rustup target add wasm32-unknown-unknown
+
+Additionally, you'll need the wasm-snip
utility in order to remove errant symbols from your compiled WASM binary. You can install this tool using cargo
:
cargo install wasm-snip
+
+forc-index
pluginThe primary means of interfacing with the Fuel indexer for indexer development is the forc-index
CLI tool. forc-index
is a forc
plugin specifically created to interface with the Fuel indexer service. Since we already installed fuelup
in a previous step 1.1, we should be able to check that our forc-index
binary was successfully installed and added to our PATH
.
which forc-index
+
+/Users/me/.fuelup/bin/forc-index
+
+++IMPORTANT:
+fuelup
will install several binaries from the Fuel ecosystem and add them into yourPATH
, including thefuel-indexer
binary. Thefuel-indexer
binary is the primary binary that users can use to spin up a Fuel indexer service.
which fuel-indexer
+
+/Users/me/.fuelup/bin/fuel-indexer
+
+Once the forc-index
plugin is installed, let's go ahead and see what indexer components we have installed.
++Many of these components are required for development work (e.g.,
+fuel-core
,psql
) but some are even required for non-development usage as well (e.g.,wasm-snip
,fuelup
).
forc index check
+
++--------+------------------------+---------------------------------------------------------+
+| Status | Component | Details |
++--------+------------------------+---------------------------------------------------------+
+| ⛔️ | fuel-indexer binary | Can't locate fuel-indexer. |
++--------+------------------------+---------------------------------------------------------+
+| ✅ | fuel-indexer service | Local service found: PID(63967) | Port(29987). |
++--------+------------------------+---------------------------------------------------------+
+| ✅ | psql | /usr/local/bin/psql |
++--------+------------------------+---------------------------------------------------------+
+| ✅ | fuel-core | /Users/me/.cargo/bin/fuel-core |
++--------+------------------------+---------------------------------------------------------+
+| ✅ | docker | /usr/local/bin/docker |
++--------+------------------------+---------------------------------------------------------+
+| ⛔️ | fuelup | Can't locate fuelup. |
++--------+------------------------+---------------------------------------------------------+
+| ✅ | wasm-snip | /Users/me/.cargo/bin/wasm-snip |
++--------+------------------------+---------------------------------------------------------+
+| ⛔️ | forc-postgres | Can't locate fuelup. |
++--------+------------------------+---------------------------------------------------------+
+| ✅ | rustc | /Users/me/.cargo/bin/rustc |
++--------+------------------------+---------------------------------------------------------+
+| ✅ | forc-wallet | /Users/me/.cargo/bin/forc-wallet |
++--------+------------------------+---------------------------------------------------------+
+
+To quickly setup and bootstrap the PostgreSQL database that we'll need, we'll use forc index
.
We can quickly create a bootstrapped database and start the Fuel indexer service by running the following command:
+++IMPORTANT: Below we're specifying our Postgres
+hostname
as--postgres-host postgresql
, but you might need to change this based on your own Postgres instance details (seeforc index start --help
for more details).Additionally, you can try using the
+--embedded-database
flag in order to quickly use an embedded instance of Postgres, but this flag can be flaky, and its ease of use often depends on what platform you're using.If you find that
+--embedded-database
isn't working on your machine (for whatever reason), we strongly recommend that you simply used the Dockerized Fuel indexer components included in the project by using thedocker compose
script included in the project.
forc index start --network beta-5 --run-migrations --postgres-host postgresql
+
+You should see output indicating the successful creation of a database and start of the indexer service; there may be much more content in your session, but it should generally contain output similar to the following lines:
+✅ Successfully started the indexer service at PID 39407
+
+2023-07-31T15:57:28.942954Z INFO fuel_indexer::commands::run: 109: Configuration: IndexerConfig { metering_points: Some(30000000000), log_level: "info", verbose: false, local_fuel_node: false, indexer_net_config: false, fuel_node: FuelClientConfig { host: "beta-5.fuel.network", port: "80" }, web_api: WebApiConfig { host: "localhost", port: "29987", max_body_size: 5242880 }, database: PostgresConfig { user: "postgres", password: "XXXX", host: "localhost", port: "5432", database: "postgres", verbose: "false" }, metrics: false, stop_idle_indexers: false, run_migrations: true, authentication: AuthenticationConfig { enabled: false, strategy: None, jwt_secret: "XXXX", jwt_issuer: None, jwt_expiry: None }, rate_limit: RateLimitConfig { enabled: false, request_count: None, window_size: None }, replace_indexer: false, accept_sql_queries: false }
+2023-07-31T15:57:28.948657Z INFO sqlx::postgres::notice: 157: relation "_sqlx_migrations" already exists, skipping
+2023-07-31T15:57:28.976258Z INFO fuel_indexer::service: 378: Resuming Indexer(fuel.indexer_test) from block 81188
+2023-07-31T15:57:29.077928Z INFO fuel_indexer::database: 187: Loading schema for Indexer(fuel.indexer_test) with Version(2738d221cf1e926d28e62bc93604a96ec6f7c5093e766f45a4555ed06e437b7f).
+2023-07-31T15:57:29.081302Z WARN fuel_indexer::executor: 87: No end_block specified in manifest. Indexer will run forever.
+2023-07-31T15:57:29.081311Z INFO fuel_indexer::executor: 109: Indexer(fuel.indexer_test) subscribing to Fuel node at beta-5.fuel.network:80
+2023-07-31T15:57:29.081424Z INFO fuel_indexer::service: 194: Registered Indexer(fuel.indexer_test)
+2023-07-31T15:57:29.082150Z INFO fuel_indexer_lib::utils: 132: Parsed SocketAddr '127.0.0.1:29987' from 'localhost:29987
+
+Now that we have our development environment set up, the next step is to create an indexer.
+forc index new hello-indexer --namespace fuellabs && cd hello-indexer
+
+++The
+namespace
of your project is a required option. You can think of anamespace
as your organization name or company name. Your project might contain one or many indexers all under the samenamespace
. For a complete list of options passed toforc index new
, see here.
forc index new hello-indexer --namespace FuelLabs
+
+✅ Successfully created indexer
+
+
+███████╗██╗ ██╗███████╗██╗ ██╗███╗ ██╗██████╗ ███████╗██╗ ██╗███████╗██████╗
+██╔════╝██║ ██║██╔════╝██║ ██║████╗ ██║██╔══██╗██╔════╝╚██╗██╔╝██╔════╝██╔══██╗
+█████╗ ██║ ██║█████╗ ██║ ██║██╔██╗ ██║██║ ██║█████╗ ╚███╔╝ █████╗ ██████╔╝
+██╔══╝ ██║ ██║██╔══╝ ██║ ██║██║╚██╗██║██║ ██║██╔══╝ ██╔██╗ ██╔══╝ ██╔══██╗
+██║ ╚██████╔╝███████╗███████╗ ██║██║ ╚████║██████╔╝███████╗██╔╝ ██╗███████╗██║ ██║
+╚═╝ ╚═════╝ ╚══════╝╚══════╝ ╚═╝╚═╝ ╚═══╝╚═════╝ ╚══════╝╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝
+
+
+An easy-to-use, flexible indexing service built to go fast. 🚗💨
+
+
+----
+
+Read the Docs:
+- Fuel Indexer: https://github.com/FuelLabs/fuel-indexer
+- Fuel Indexer Book: https://docs.fuel.network/docs/indexer/
+- Sway Book: https://docs.fuel.network/docs/sway/
+- Rust SDK Book: https://rust.fuel.network
+
+
+Join the Community:
+- Follow us @Fuel: https://twitter.com/fuel_network
+- Ask questions in dev-chat on Discord: https://discord.com/invite/xfpK4Pe
+
+Report Bugs:
+- Fuel Indexer Issues: https://github.com/FuelLabs/fuel-indexer/issues/new
+
+Take a quick tour.
+
+`forc index auth`
+ Authenticate against an indexer service.
+`forc index build`
+ Build an indexer.
+`forc index check`
+ List indexer components.
+`forc index deploy`
+ Deploy an indexer.
+`forc index kill`
+ Kill a running Fuel indexer process on a given port.
+`forc index new`
+ Create a new indexer.
+`forc index remove`
+ Stop a running indexer.
+`forc index start`
+ Start a local indexer service.
+`forc index status`
+ Check the status of an indexer.
+
+At this point, we have a brand new indexer that will index some blocks and transactions. And with both our database and Fuel indexer services up and running, all that's left to do is to build and deploy the indexer in order to see it in action. Let's build and deploy our indexer:
+forc index deploy
+
+++IMPORTANT:
+forc index deploy
by defaults runsforc index build
prior to deploying the indexer. The same result can be produced by runningforc index build
then subsequently runningforc index deploy
. For more info, checkout theforc index deploy
command.
If all goes well, you should see the following:
+▹▹▹▹▹ ⏰ Building... Finished dev [unoptimized + debuginfo] target(s) in 0.96s
+▪▪▪▪▪ ✅ Build succeeded. Deploying indexer
+▪▪▪▪▪ ✅ Successfully deployed indexer.
+
+And we can check the status of our newly deployed indexer using:
+forc index status
+
+Which should show:
+✅ Successfully fetched service health:
+
+client status: OK
+database status: OK
+uptime: 1m 30s
+
+Indexers:
+
+─ fuellabs
+ └─ hello_world
+ • id: 1
+ • created at: 2023-11-08 15:09:49.205698 UTC (52s ago)
+ • status: running
+ • status message:
+ Indexed 5440 blocks
+
+++What is a "deployment" exactly?
+A deployment within the context of Fuel's indexer is a series of steps taken to get your indexer project running in the wild.
+This series of steps involves compiling your indexer project to a
+wasm32-unknown-unknown
target and uploading the indexer to a running Fuel indexer service. The service will then register an executor and build database tables for this indexer. Once this series of steps has completed, your indexer is considered to be "deployed".Users will often find that they're simply deploying their indexers to a Fuel indexer service running on their local machine; this is just one valid use-case described in our infrastructure docs. Keep in mind that the intended use of a Fuel indexer service is as a standalone remote service that may run many different indexers at any given time.
+
With our indexer deployed, we should be able to query for newly indexed data after a few seconds.
+Below, we write a simple GraphQL query that returns a few fields from all transactions that we've indexed.
+You can open your GraphQL query playground at http://127.0.0.1:29987/api/playground/fuellabs/hello_indexer
and submit the following GraphQL query.
query {
+ transaction {
+ id,
+ hash,
+ block {
+ id
+ }
+ }
+}
+
+The response you get should resemble:
+[
+ {
+ "block": {
+ "id": "24002b29ef4331f5ee75a38bf6381f2c8e8d2d5b4d78470706dde7ab0b8d54c0"
+ },
+ "hash": "82b36dce26d926921b8e79597899d8712fdabf2553f28b45ef3851a968efb4b9",
+ "id": "eb7e14822e18e71ba7c92c266b0976acda2344dfbef7a60099d400cc243394fb"
+ },
+ {
+ "block": {
+ "id": "1309ee2cb0846b1a7e45313e1c39b2a24ffd552a381f2f627225256f725a93e3"
+ },
+ "hash": "f0c7c778faa6eb2a8bf03c9c47bb3f836bd4fe37e69c18e30f853ff146522dcb",
+ "id": "182b6343bbbca2fcecf97020ea3f3767b8f5c370a6b853d2add46853e542a113"
+ },
+ {
+ "block": {
+ "id": "95588e20296969a76576d519d301c6cabe1e009675e430da93e18ba2a0d38a49"
+ },
+ "hash": "e729045198ee10dcf49e431f50c2ffe8c37129cbe47e003a59aff81a88b03b50",
+ "id": "6910ebc30a1037b83336c956c95f7fc470c4b76750a93f6a1f6d19a21d058b19"
+ }
+]
+
+Congrats, you just created, built, and deployed your first indexer on the world's fastest execution layer.
+For more info on how indexers work, please checkout the reference guide.
+ +The Fuel indexer project can currently be used in a number of different ways:
+We'll describe these three different use cases below.
+The Fuel indexer provides functionality to make it easy to build and compile arbitrary indexers by using the forc index
CLI tool. Using forc index
, users can create, build, deploy, and remove indexers, as well as authenticate against a running indexer service, and check the status of running indexers.
Create, deploy, and check the status of a new indexer.
+forc index new fuel && \
+ cd fuel && forc index deploy --url http://indexer.fuel.network && \
+ forc index status --url http://indexer.fuel.network --auth $MY_TOKEN
+
+You can also start the Fuel indexer as a standalone service that connects to a Fuel node in order to monitor the Fuel blockchain for new blocks and transactions. To do so, run the requisite database migrations, adjust the configuration to connect to a Fuel node, and start the service.
+Create, deploy, and check the status of a new indexer.
+fuel-indexer run \
+ --network beta-5 \
+ --run-migrations \
+ --accept-sql-queries \
+ --replace-indexer
+
+Finally, you can run the Fuel indexer as part of a project that uses other components of the Fuel ecosystem, such as Sway. The convention for a Fuel project layout including an indexer is as follows:
+.
+├── contracts
+│ └── hello-contract
+│ ├── Forc.toml
+│ └── src
+│ └── main.sw
+├── frontend
+│ └── index.html
+└── indexer
+ └── hello-indexer
+ ├── Cargo.toml
+ ├── hello_indexer.manifest.yaml
+ ├── schema
+ │ └── hello_indexer.schema.graphql
+ └── src
+ └── lib.rs
+
+Every Fuel indexer project requires three components:
+A manifest is a YAML configuration file that specifies various aspects of how an indexer should function: Where should the indexer start? Where should the indexer end? What contract should the indexer subscribe to?
+Below is a sample indexer manifest file
+namespace: fuellabs
+identifier: order_book_v1
+fuel_client: beta-5.fuel.network:80
+abi: path/to/my/contract-abi.json
+contract_id: "fuels0x39150017c9e38e5e280432d546fae345d6ce6d8fe4710162c2e3a95a6faff051"
+graphql_schema: path/to/my/schema.graphql
+start_block: 1564
+end_block: 310000
+module:
+ wasm: path/to/my/wasm_module.wasm
+
+namespace
Required.
+The namespace
is the topmost organizational level of an indexer. You can think of different namespaces as separate and distinct collections comprised of indexers. A namespace is unique to a given indexer operator -- i.e., indexer operators will not be able to support more than one namespace of the same name.
identifier
Required.
+The identifier
field is used to (quite literally) identify the given indexer. If a namespace describes a collection of indexers, then an identifier describes a unique indexer inside that collection. As an example, if a provided namespace
is "fuel"
and a provided identifier
is "index1"
, then the full identifier for the given indexer will be fuel.index1
.
fuel_client
Optional.
+The fuel_client
denotes the address (host, port combination) of the running Fuel client that you would like your indexer to index events from. In order to use this per-indexer fuel_client
option, the indexer service at which your indexer is deployed will have to run with the --indexer_net_config
option.
abi
Optional.
+The abi
option is used to provide a link to the Sway JSON application binary interface (ABI) that is generated when you build your Sway project. This generated ABI contains all types, type IDs, logged types, and message types used in your Sway contract.
contract_id
Optional.
+The contract_id
specifies the particular contract to which you would like an indexer to subscribe. Setting this field to an empty string will index events from any contract that is currently executing on the network. This field accepts either a single string, or a list of strings. The indexer will index events from all IDs if a list is passed.
++Important: Contract IDs are unique to the content of a contract. If you are subscribing to a certain contract and then the contract itself is changed or updated, you will need to change the
+contract_id
field of the manifest to the new ID. +Note: This parameter supports bothBech32
contract IDs and non-Bech32
contract IDs
graphql_schema
Required.
+The graphql_schema
field contains the file path pointing to the corresponding GraphQL schema for a given indexer. This schema file holds the structures of the data that will eventually reside in your database. You can read more about the format of the schema file here.
++Important: The objects defined in your GraphQL schema are called 'entities'. These entities are what will be eventually be stored in the database.
+
start_block
Optional.
+The start_block
field indicates the block height after which you'd like your indexer to start indexing events.
end_block
Optional.
+The end_block
field indicates the block height after which the indexer should stop indexing blocks.
++Important: If no
+end_block
is added the indexer will keep listening to new blocks indefinitely.
module
Required.
+The module
field contains a file path that points to code that will be run as an executor inside of the indexer.
resumable
Optional.
+The resumable
field contains a boolean value and specifies whether the indexer should synchronise with the latest block if it has fallen out of sync.
The GraphQL schema is a required component of the Fuel indexer. When data is indexed into the database, the actual values that are persisted to the database will be values created using the data structures defined in the GraphQL schema.
+Below is a sample GraphQL schema for a Fuel indexer.
+type Metadata @entity(virtual: true) {
+ imageUrl: String!
+ data: Bytes
+}
+
+type Account @entity {
+ id: ID!
+ address: Address!
+ index: U64!
+ metadata: Metadata
+}
+
+type Wallet @entity {
+ id: ID!
+ name: String!
+ accounts: [Account!]!
+}
+
+For a complete list of all scalars that can be used in a Fuel indexer, please see the GraphQL Scalars section.
+Further, for a complete list of how Sway data types, GraphQL scalar types, and Fuel indexer database types map to each other, please see the Database Types section.
+Finally, for a more in-depth explanation on the schema being used above 👆🏽, please read the GraphQL section.
+Indexer modules are compiled binaries that process data from the Fuel blockchain into entity types defined in your schema so that the data can be stored in a database. The Fuel indexer supports both WebAssembly (WASM) and native binaries; however, we strongly recommend using WASM binaries.
+This document describes the process of creating an indexer module.
+Prior to creating a module for an indexer, both the manifest and schema should be created. At compile time, information will be extracted from both of those assets and combined it with your defined logic to create handlers that save data to storage. Let's look at the following example of a module that will be compiled to WASM:
+use fuel_indexer_utils::prelude::*;
+
+#[indexer(manifest = "indexer.manifest.yaml")]
+mod indexer_mod {
+
+ // This `log_the_greeting` function will be called, when we find
+ // a `Greeting` in a block.
+ fn log_the_greeting(greeting: Greeting) {
+ info!("The greeting is: {greeting:?}");
+ }
+}
+The first line imports the prelude from fuel_indexer_utils
; this allows you to quickly bootstrap an indexer by using common types and traits. Then, we have a module decorated with the #[indexer]
macro.
Finally, we have an example handler function. You can define which functions handle different events by using the function parameters. If you add a function parameter of a certain type T
, the function will be triggered whenever that type is found as part of a block, transaction, or receipt.
Greeting
struct. When that function executes as part of a transaction, the logged struct will be included in the data that is processed from the Fuel blockchain. Your indexer module will see the struct and execute log_the_greeting
.++You can learn more about what data can be indexed and find example handlers in the Indexing Fuel Types and Indexing Custom Types sections.
+
To compile your indexer code to WASM, you'll first need to install the wasm32-unknown-unknown
target platform through rustup
, if you haven't done so already.
rustup add target wasm32-unknown-unknown
+
+After that, you can conveniently use the forc index
plugin to manager your indexers. Simply use forc index build
to build your indexer or checkout the forc index build
docs for more options.
++Notes on Web Assembly modules
+There are a few points that Fuel indexer users should know when using WASM:
++
+- +
+WASM modules are only used if the execution mode specified in your manifest file is
+wasm
.- +
+Developers should be aware of what things may not work off-the-shelf in a module: file I/O, thread spawning, and anything that depends on system libraries or makes system calls. This is due to the technological limitations of WASM as a whole; more information can be found here.
+- +
+As of this writing, there is a small bug in newly built Fuel indexer WASM modules that produces a WASM runtime error due to an errant upstream dependency. For now, a quick workaround requires the use of
+wasm-snip
to remove the errant symbols from the WASM module. More info can be found in the related script here.- +
+Users on Apple Silicon macOS systems may experience trouble when trying to build WASM modules due to its
+clang
binary not supporting WASM targets. If encountered, you can install a binary with better support from Homebrew (brew install llvm
) and instructrustc
to leverage it by setting the following environment variables:+
+- +
AR=/opt/homebrew/opt/llvm/bin/llvm-ar
- +
CC=/opt/homebrew/opt/llvm/bin/clang
The Fuel indexer uses GraphQL in order to allow users to query for indexed data. In this chapter, you can find information on how to leverage our supported features to efficiently get the data you want.
+++⚠️ Please note that the Fuel indexer does not support the full GraphQL specification; however, we do our best to reasonably support as much as we can.
+
While we do our best to maintain compliance with the GraphQL specification and parity with other implementations, there are a few things that are under development or will not be implemented. Here's a table describing our GraphQL functionality:
+Legend:
+Functionality | Status | Notes |
---|---|---|
Arguments | 🟩 | read the Search and Filtering section |
Aliases | 🟩 | |
Fragments | 🟨 | inline fragments are currently not supported |
Introspection | 🟩 | |
GraphQL Playground | 🟩 | read the Playground section |
Pagination | 🟨 | read the Pagination section |
Directives | 🟨 | read the Directives section |
List Types | 🟨 | |
Union Types | 🟨 | |
Federation | ⛔ | |
Variables | ⛔ | |
Mutations | ⛔ | |
Enums | 🟨 | |
Interfaces | ⛔ | |
Input Types | ⛔ |
Object types are the most commonly used type in indexer GraphQL schema. Each object type marked with an @entity
directive will be converted into a SQL table.
type Account @entity {
+ id: ID!
+ address: Address!
+ balance: U64!
+}
+
+This Account
object type from the GraphQL schema, might be used in an indexer module like so:
extern crate alloc;
+use fuel_indexer_utils::prelude::*;
+
+#[indexer(manifest = "indexer.manifest.yaml")]
+mod indexer_mod {
+ fn handle_event(event: Event) {
+ let address = Address::default();
+ let balance = 0;
+ let account = Account::new(address, balance);
+ account.save();
+ }
+}
+Enum types are simply implemented as String types.
+enum SignatureLabel {
+ Multi
+ Single
+}
+
+++Enum types in relation to Fuel indexer's implementation are just
+String
types used primarily to label object types. There is no other way thatenum
types should be used at this time. +ThisSignatureLabel
object type from the GraphQL schema, might be used in an indexer module like so:
extern crate alloc;
+use fuel_indexer_utils::prelude::*;
+
+#[indexer(manifest = "indexer.manifest.yaml")]
+mod indexer_mod {
+ fn handle_event(event: Event) {
+ let label = SignatureLabel::Multi;
+ assert_eq!(label.to_string(), "SignatureLabel::Multi".to_string());
+ }
+}
+Union types are unique in that any type marked as a union
will be converted into an Object type, who's fields are the unique set of fields over all members of the union.
enum TransactionLabel {
+ Create
+ Script
+ Mint
+}
+
+type CreateTransaction @entity {
+ id: ID!
+ bytecode_length: U64!
+ contract_id: ContractId!
+ label: TransactionLabel!
+}
+
+type ScriptTransaction @entity {
+ id: ID!
+ maturity: U64!
+ label: TransactionLabel!
+}
+
+type MintTransaction @entity {
+ id: ID!
+ metadata: Json
+ label: TransactionLabel!
+}
+
+union Transaction = CreateTransaction | ScriptTransaction | MintTransaction
+
+The Transaction
union type above, will internally produce the following object type:
type Transaction @entity {
+ id: ID!
+ bytecode_length: U64!
+ contract_id: ContractId!
+ label: TransactionLabel!
+ maturity: U64!
+ metadata: Json
+}
+
+++IMPORTANT: Note the order of the fields in the derived
+Transaction
object type: the fields are ordered according to the unique set of fields from each of the union's members.The
+id
,bytecode_length
,contract_id
, andlabel
fields come first, from theCreateTransaction
object type. Next comes thematurity
field from theScriptTransaction
object - because theScriptTransaction
'sid
andlabel
fields are already a part of the derivedTransaction
object, courtesy of theCreateTransaction
object type. Finally, comes themetadata
field, as part of theMintTransaction
object type. +ThisTransaction
union type from the GraphQL schema, might be used in an indexer module like so:
extern crate alloc;
+use fuel_indexer_utils::prelude::*;
+
+#[indexer(manifest = "indexer.manifest.yaml")]
+mod indexer_mod {
+ fn handle_event(event: Event) {
+ let bytecode_length = 1024;
+ let contract_id = ContractId::default();
+ let label = TransactionLabel::Create;
+ let maturity = 10000000;
+ let metadata = None;
+ let transaction = Transaction::new(bytecode_length, contract_id, label, maturity, metadata);
+ transaction.save();
+ }
+}
+The Fuel indexer has a collection of GraphQL scalars that cover virtually any value type in use on the Fuel network. The following list contains each GraphQL scalar type along with its equivalent Rust type.
+GraphQL Scalar | Rust Type | Notes |
---|---|---|
Address | u8[32] | |
AssetId | u8[32] | |
Boolean | bool | |
Bytes | Vec<u8> | Byte blob of arbitrary size |
Bytes32 | u8[32] | |
Bytes4 | u8[4] | |
Bytes64 | u8[64] | |
Bytes8 | u8[8] | |
ContractId | u8[32] | |
HexString | Vec<u8> | Byte blob of arbitrary size |
I128 | i128 | |
I16 | i16 | |
I32 | i32 | |
I64 | i64 | |
I8 | i8 | |
ID | SizedAsciiString<64> | Alias of UID |
Json | String | JSON string of arbitrary size |
String | String | String of arbitrary size |
U128 | u128 | |
U16 | u16 | |
U32 | u32 | |
U64 | u64 | |
U8 | u8 | |
UID | SizedAsciiString<64> | 32-byte unique ID |
++Per GraphQL: A directive is a keyword preceded by a @ character (optionally followed by a list of named arguments) which can appear after almost any form of syntax in the GraphQL query or schema languages.
+
As of this writing, the list of supported Fuel GraphQL schema directives includes:
+@indexed
: Denotes that a field should include a B-tree index in the database.@unique
: Denotes that field should include a unique index in the database.@join
: Denotes that a field has a "relationship" to another object type.@indexed
The @indexed
directive adds a database index to the underlying column for the indicated field of that type. Generally, a database index is a data structure that allows you to quickly locate data without having to search each row in a database table.
type Book @entity {
+ id: ID!
+ name: Bytes8! @indexed
+}
+
+type Library @entity {
+ id: ID!
+ book: Book!
+}
+
+In this example, a single BTREE INDEX
constraint will be created on the book
table's name
column, which allows for faster lookups on that field.
++Important: At the moment, database index constraint support is limited to
+BTREE
in Postgres withON DELETE
, andON UPDATE
actions not being supported.
@unique
The @unique
directive adds a UNIQUE
database constraint to the underlying database column for the indicated field of that type. A constraint specifies a rule for the data in a table and can be used to limit the type of data that can be placed in the table. In the case of a column with a UNIQUE
constraint, all values in the column must be different.
type Book @entity {
+ id: ID!
+ name: Bytes8! @unique
+}
+
+type Library @entity {
+ id: ID!
+ book: Book!
+}
+
+A UNIQUE
constraint will be created on the book
table's name
column, ensuring that no books can share the same name.
++Important: When using explicit or implicit foreign keys, it is required that the reference column name in your foreign key relationship be unique.
+ID
types are by default unique, but all other types will have to be explicitly specified as being unique via the@unique
directive.
@join
The @join
directive is used to relate a field in one type to others by referencing fields in another type. You can think of it as a link between two tables in your database. The field in the referenced type is called a foreign key and it is required to be unique.
type Book @entity {
+ id: ID!
+ name: String! @unique
+}
+
+type Library @entity {
+ id: ID!
+ book: Book! @join(on:name)
+}
+
+A foreign key constraint will be created on library.book
that references book.name
, which relates the Book
s in a Library
to the underlying Book
table. For more info on what exactly is happening here, please see the Relationships section.
The Fuel indexer service supports foreign key relationships and constraints. There are two types of relationship specifications: implicit and explicit.
+++IMPORTANT:
+Implicit foreign keys do not require a
+@join
directive. When using implicit foreign key references, merely add the referenced object as a field type (shown below). A lookup will automatically be done to add a foreign key constraint using this object's'id
field.Note that implicit foreign key relationships only use the
+id
field on the referenced table. If you plan to use implicit foreign keys, the object being referenced must have anid
field.In contrast, explicit foreign keys do require a
+@join
directive. Explicit foreign key references work similarly to implicit foreign keys; however, when using explicit foreign key references, you must add a@join
directive after your object type. This@join
directive includes the field in your foreign object that you would like to reference (shown below).
Let's learn how to use each foreign key type by looking at some GraphQL schema examples.
+type Library @entity {
+ id: ID!
+ name: String!
+}
+
+type Book @entity {
+ id: ID!
+ library: Library!
+}
+
+Given the above schema, two entities will be created: a Book
entity, and a Library
entity. As you can see, we add the Book
entity as an attribute on the Library
entity, thus conveying that we want a one-to-many or one-to-one relationship between Library
and Book
. This means that for a given Book
, we may also fetch the associated Library
entity. It also means that the field Book.library
will be an ID
scalar type that references Library.id
.
type Library @entity {
+ id: ID!
+ name: String! @unique
+}
+
+type Book @entity {
+ id: ID!
+ library: Library! join(on:name)
+}
+
+For the most part, this works the same way as implicit foreign key usage. However, as you can see, instead of implicitly using Library.id
as the reference column for our Library
field type on the Book
object, we're explicitly specifying that we want Library.name
to serve as our foreign key for the Book.library
field. Also, please note that since we're using Library.name
in our foreign key constraint, that column is required to be unique (via the @unique
directive).
This document provides information about Fuel-specific types and provides examples on how to index each type.
+BlockData
++The
+BlockData
struct is how blocks are represented in the Fuel indexer. It contains metadata such as the ID, height, and time, as well as a list of the transactions it contains (represented byTransactionData
). It also contains the public key hash of the block producer, if present.
pub struct BlockData {
+ pub height: u32,
+ pub id: Bytes32,
+ pub header: Header,
+ pub producer: Option<Bytes32>,
+ pub time: i64,
+ pub consensus: Consensus,
+ pub transactions: Vec<TransactionData>,
+}
+extern crate alloc;
+use fuel_indexer_utils::prelude::*;
+
+#[indexer(manifest = "indexer.manifest.yaml")]
+mod indexer_mod {
+ fn handle_block(block_data: BlockData) {
+ let height = block_data.header.height;
+ info!("This block #{height}");
+ }
+}
+
+TransactionData
The TransactionData
struct contains important information about a transaction in the Fuel network. The id
field is the transaction hash, which is a 32-byte string. The receipts
field contains a list of Receipts
, which are generated by a Fuel node during the execution of a Sway smart contract; you can find more information in the Receipts section.
pub struct TransactionData {
+ pub transaction: Transaction,
+ pub status: TransactionStatus,
+ pub receipts: Vec<Receipt>,
+ pub id: TxId,
+}
+extern crate alloc;
+use fuel_indexer_utils::prelude::*;
+
+#[indexer(manifest = "indexer.manifest.yaml")]
+mod indexer_mod {
+ fn handle_transaction(block_data: BlockData) {
+ let height = block_data.header.height;
+ if !block_data.transactions.is_empty() {
+ let transaction = block_data.transactions[0];
+ info!(
+ "Transaction {} in block at height {} has {} receipts",
+ transaction.id,
+ block_data.header.height,
+ transaction.receipts.len()
+ );
+ }
+ }
+}
+TransactionStatus
TransactionStatus
refers to the status of a Transaction
in the Fuel network.
pub enum TransactionStatus {
+ Failure {
+ block_id: String,
+ time: DateTime<Utc>,
+ reason: String,
+ },
+ SqueezedOut {
+ reason: String,
+ },
+ Submitted {
+ submitted_at: DateTime<Utc>,
+ },
+ Success {
+ block_id: String,
+ time: DateTime<Utc>,
+ },
+}
+extern crate alloc;
+use fuel_indexer_utils::prelude::*;
+
+#[indexer(manifest = "indexer.manifest.yaml")]
+mod indexer_mod {
+ fn handle_transaction(block_data: BlockData) {
+ let height = block_data.header.height;
+ if !block_data.transactions.is_empty() {
+ let transaction = block_data.transactions[0];
+ match transaction.transaction {
+ fuel::Transaction::Script(tx) => match tx.status {
+ fuel::TransactionStatus::Success { block_id, time } => {
+ info!(
+ "Transaction {} in block {} was successful at {}",
+ tx.id, block_id, time
+ );
+ }
+ },
+ _ => {
+ info!("We don't care about this transaction type");
+ }
+ }
+ }
+ }
+}
+Every transaction in the Fuel network contains a list of receipts with information about that transaction, including what contract function was called, logged data, data returned from a function, etc.
+There are several types of receipts that can be attached to a transaction and indexed. You can learn more about each of these in the sections below.
+Burn
Call
Log
LogData
MessageOut
Mint
Panic
Return
ReturnData
Revert
ScriptResult
Transfer
TransferOut
Burn
A Burn
receipt is generated whenever an asset is burned in a Sway contract. Read more about Burn
in the Fuel protocol ABI spec.
use fuel_types::{AssetId, ContractId};
+pub struct Burn {
+ pub sub_id: AssetId,
+ pub contract_id: ContractId,
+ pub val: u64,
+ pub pc: u64,
+ pub is: u64,
+}
+mod indexer_mod {
+ fn handle_burn_receipt(block_data: BlockData) {
+ let height = block_data.header.height;
+ if !block_data.transactions.is_empty() {
+ let transaction = block_data.transactions[0];
+ for receipt in transaction.receipts {
+ match receipt {
+ fuel::Receipt::Burn { contract_id, .. } => {
+ info!("Found burn receipt from contract {contract_id:?}");
+ }
+ }
+ }
+ }
+ }
+}
+Call
A Call
receipt is generated whenever a function is called in a Sway contract. The fn_name
field contains the name of the called function from the aforementioned contract. Read more about Call
in the Fuel protocol ABI spec.
use fuel_types::{AssetId, ContractId};
+pub struct Call {
+ pub contract_id: ContractId,
+ pub to: ContractId,
+ pub amount: u64,
+ pub asset_id: AssetId,
+ pub gas: u64,
+ pub fn_name: String,
+}
+extern crate alloc;
+use fuel_indexer_utils::prelude::*;
+
+#[indexer(manifest = "indexer.manifest.yaml")]
+mod indexer_mod {
+ fn handle_call_receipt(block_data: BlockData) {
+ let height = block_data.header.height;
+ if !block_data.transactions.is_empty() {
+ let transaction = block_data.transactions[0];
+ for receipt in transaction.receipts {
+ match receipt {
+ fuel::Receipt::Call { contract_id, .. } => {
+ info!("Found call receipt from contract {contract_id:?}");
+ }
+ }
+ }
+ }
+ }
+}
+Log
A Log
receipt is generated when calling log()
on a non-reference types in a Sway contracts - specifically bool
, u8
, u16
, u32
, and u64
. The ra
field includes the value being logged while rb
may include a non-zero value representing a unique ID for the log
instance. Read more about Log
in the Fuel protocol ABI spec.
use fuel_types::ContractId;
+pub struct Log {
+ pub contract_id: ContractId,
+ pub ra: u64,
+ pub rb: u64,
+}
+extern crate alloc;
+use fuel_indexer_utils::prelude::*;
+
+#[indexer(manifest = "indexer.manifest.yaml")]
+mod indexer_mod {
+ fn handle_log_receipt(block_data: BlockData) {
+ let height = block_data.header.height;
+ if !block_data.transactions.is_empty() {
+ let transaction = block_data.transactions[0];
+ for receipt in transaction.receipts {
+ match receipt {
+ fuel::Receipt::Log { contract_id, .. } => {
+ info!("Found log receipt from contract {contract_id:?}");
+ }
+ }
+ }
+ }
+ }
+}
+LogData
A LogData
receipt is generated when calling log()
in a Sway contract on a reference type; this includes all types except non-reference types. The data
field will include the logged value as a hexadecimal. The rb
field will contain a unique ID that can be used to look up the logged data type. Read more about LogData
in the Fuel protocol ABI spec.
+>
use fuel_types::ContractId;
+pub struct LogData {
+ pub contract_id: ContractId,
+ pub data: Vec<u8>,
+ pub rb: u64,
+ pub len: u64,
+ pub ptr: u64,
+}
+++Note: the example below will run both when the type
+MyEvent
is logged as well as whenMyEvent
is returned from a function.
extern crate alloc;
+use fuel_indexer_utils::prelude::*;
+
+#[indexer(manifest = "indexer.manifest.yaml")]
+mod indexer_mod {
+ fn handle_log_data(event: MyEvent) {
+ info!("Event {event:?} was logged in the contract");
+ }
+}
+MessageOut
A MessageOut
receipt is generated as a result of the send_typed_message()
Sway method in which a message is sent to a recipient address along with a certain amount of coins. The data
field supports data of an arbitrary type T
and will be decoded by the indexer upon receipt. Read more about MessageOut
in the Fuel protocol ABI spec.
use fuel_types::{MessageId, Bytes32, Address};
+pub struct MessageOut {
+ pub message_id: MessageId,
+ pub sender: Address,
+ pub recipient: Address,
+ pub amount: u64,
+ pub nonce: Bytes32,
+ pub len: u64,
+ pub digest: Bytes32,
+ pub data: Vec<u8>,
+}
+extern crate alloc;
+use fuel_indexer_utils::prelude::*;
+
+#[indexer(manifest = "indexer.manifest.yaml")]
+mod indexer_mod {
+ fn handle_message_out(event: MyEvent) {
+ info!("Event {event:?} was logged in the contract");
+ }
+}
+Mint
A Mint
receipt is generated whenever an asset is burned in a Sway contract. Read more about Mint
in the Fuel protocol ABI spec.
use fuel_types::{AssetId, ContractId};
+pub struct Mint {
+ pub sub_id: AssetId,
+ pub contract_id: ContractId,
+ pub val: u64,
+ pub pc: u64,
+ pub is: u64,
+}
+extern crate alloc;
+use fuel_indexer_utils::prelude::*;
+
+#[indexer(manifest = "indexer.manifest.yaml")]
+mod indexer_mod {
+ fn handle_mint_receipt(block_data: BlockData) {
+ let height = block_data.header.height;
+ if !block_data.transactions.is_empty() {
+ let transaction = block_data.transactions[0];
+ for receipt in transaction.receipts {
+ match receipt {
+ fuel::Receipt::Mint { contract_id, .. } => {
+ info!("Found mint receipt from contract {contract_id:?}");
+ }
+ }
+ }
+ }
+ }
+}
+Panic
A Panic
receipt is produced when a Sway smart contract call fails for a reason that doesn't produce a revert. The reason field records the reason for the panic, which is represented by a number between 0 and 255. You can find the mapping between the values and their meanings here in the FuelVM source code. Read more about Panic
in the Fuel protocol spec.
use fuel_types::ContractId;
+pub struct Panic {
+ pub contract_id: ContractId,
+ pub reason: u32,
+}
+extern crate alloc;
+use fuel_indexer_utils::prelude::*;
+
+#[indexer(manifest = "indexer.manifest.yaml")]
+mod indexer_mod {
+ fn handle_panic_receipt(block_data: BlockData) {
+ let height = block_data.header.height;
+ if !block_data.transactions.is_empty() {
+ let transaction = block_data.transactions[0];
+ for receipt in transaction.receipts {
+ match receipt {
+ fuel::Receipt::Panic { contract_id, .. } => {
+ info!("Found panic receipt from contract {contract_id:?}");
+ }
+ }
+ }
+ }
+ }
+}
+Return
A Return
receipt is generated when returning a non-reference type in a Sway contract, specifically bool
, u8
, u16
, u32
, and u64
. The val
field includes the value being returned. Read more about Return
in the Fuel protocol spec.
use fuel_types::ContractId;
+pub struct Return {
+ pub contract_id: ContractId,
+ pub val: u64,
+ pub pc: u64,
+ pub is: u64,
+}
+You can handle functions that produce a Return
receipt type by adding a parameter with the type Return
.
extern crate alloc;
+use fuel_indexer_utils::prelude::*;
+
+#[indexer(manifest = "indexer.manifest.yaml")]
+mod indexer_mod {
+ fn handle_return_receipt(block_data: BlockData) {
+ let height = block_data.header.height;
+ if !block_data.transactions.is_empty() {
+ let transaction = block_data.transactions[0];
+ for receipt in transaction.receipts {
+ match receipt {
+ fuel::Receipt::Return { contract_id, .. } => {
+ info!("Found return receipt from contract {contract_id:?}");
+ }
+ }
+ }
+ }
+ }
+}
+ReturnData
A ReturnData
receipt is generated when returning a reference type in a Sway contract; this includes all types except non-reference types. The data
field will include the returned value as a hexadecimal. Read more about ReturnData
in the Fuel protocol ABI spec.
use fuel_types::ContractId;
+pub struct ReturnData {
+ id: ContractId,
+ data: Vec<u8>,
+}
+++Note: the example below will run both when the type
+MyStruct
is logged as well as whenMyStruct
is returned from a function.
extern crate alloc;
+use fuel_indexer_utils::prelude::*;
+
+#[indexer(manifest = "indexer.manifest.yaml")]
+mod indexer_mod {
+ fn handle_return_data(event: MyStruct) {
+ info!("MyStruct is: {event:#}");
+ }
+}
+Revert
A Revert
receipt is produced when a Sway smart contract function call fails. The table below lists possible reasons for the failure and their values. The error_val
field records these values, enabling your indexer to identify the specific cause of the reversion. Read more about Revert
in the Fuel protocol spec.
use fuel_types::ContractId;
+pub struct Revert {
+ pub contract_id: ContractId,
+ pub error_val: u64,
+}
+Reason | Value |
---|---|
FailedRequire | 0 |
FailedTransferToAddress | 1 |
FailedSendMessage | 2 |
FailedAssertEq | 3 |
FailedAssert | 4 |
extern crate alloc;
+use fuel_indexer_utils::prelude::*;
+
+#[indexer(manifest = "indexer.manifest.yaml")]
+mod indexer_mod {
+ fn handle_revert_receipt(block_data: BlockData) {
+ let height = block_data.header.height;
+ if !block_data.transactions.is_empty() {
+ let transaction = block_data.transactions[0];
+ for receipt in transaction.receipts {
+ match receipt {
+ fuel::Receipt::Revert { contract_id, .. } => {
+ info!("Found return receipt from contract {contract_id:?}");
+ }
+ }
+ }
+ }
+ }
+}
+ScriptResult
A ScriptResult
receipt is generated when a contract call resolves; that is, it's generated as a result of the RET
, RETD
, and RVRT
instructions. The result
field will contain a 0
for success, and a non-zero value otherwise. Read more about ScriptResult
in the Fuel protocol spec.
pub struct ScriptResult {
+ pub result: u64,
+ pub gas_used: u64,
+}
+extern crate alloc;
+use fuel_indexer_utils::prelude::*;
+
+#[indexer(manifest = "indexer.manifest.yaml")]
+mod indexer_mod {
+ fn handle_script_result_receipt(block_data: BlockData) {
+ let height = block_data.header.height;
+ if !block_data.transactions.is_empty() {
+ let transaction = block_data.transactions[0];
+ for receipt in transaction.receipts {
+ match receipt {
+ fuel::Receipt::ScriptResult { result, .. } => {
+ info!("Result from script: {result:?}");
+ }
+ }
+ }
+ }
+ }
+}
+Transfer
A Transfer
receipt is generated when coins are transferred to a contract as part of a Sway contract. The asset_id
field contains the asset ID of the transferred coins, as the FuelVM has built-in support for working with multiple assets. The pc
and is
fields aren't currently used for anything, but are included for completeness. Read more about Transfer
in the Fuel protocol spec.
use fuel_types::{ContractId, AssetId};
+pub struct Transfer {
+ pub contract_id: ContractId,
+ pub to: ContractId,
+ pub amount: u64,
+ pub asset_id: AssetId,
+ pub pc: u64,
+ pub is: u64,
+}
+extern crate alloc;
+use fuel_indexer_utils::prelude::*;
+
+#[indexer(manifest = "indexer.manifest.yaml")]
+mod indexer_mod {
+ fn handle_transfer_receipt(block_data: BlockData) {
+ let height = block_data.header.height;
+ if !block_data.transactions.is_empty() {
+ let transaction = block_data.transactions[0];
+ for receipt in transaction.receipts {
+ match receipt {
+ fuel::Receipt::Transfer { contract_id, .. } => {
+ info!("Found transfer receipt from contract {contract_id:?}");
+ }
+ }
+ }
+ }
+ }
+}
+TransferOut
A TransferOut
receipt is generated when coins are transferred to an address rather than a contract. Every other field of the receipt works the same way as it does in the Transfer
receipt. Read more about TransferOut
in the Fuel protocol spec.
use fuel_types::{ContractId, AssetId, Address};
+pub struct TransferOut {
+ pub contract_id: ContractId,
+ pub to: Address,
+ pub amount: u64,
+ pub asset_id: AssetId,
+ pub pc: u64,
+ pub is: u64,
+}
+extern crate alloc;
+use fuel_indexer_utils::prelude::*;
+
+#[indexer(manifest = "indexer.manifest.yaml")]
+mod indexer_mod {
+ fn handle_transfer_out_receipt(block_data: BlockData) {
+ let height = block_data.header.height;
+ if !block_data.transactions.is_empty() {
+ let transaction = block_data.transactions[0];
+ for receipt in transaction.receipts {
+ match receipt {
+ fuel::Receipt::TransferOut { contract_id, .. } => {
+ info!("Found transfer_out receipt from contract {contract_id:?}");
+ }
+ }
+ }
+ }
+ }
+}
+++In addition to Fuel-specific types, you can also index custom types triggered in your Sway smart contract.
+
To index custom types from a Sway smart contract, you'll need that specific contract's ABI in JSON format; the JSON ABI is generated as a result of running forc build
to build your contract. After that, the process is similar to indexing Fuel types.
Let's cover some of these concepts in an example below.
+First, let's create a Sway contract with some simple types.
+contract;
+
+use std::logging::log;
+
+struct Addition {
+ added_value: u64,
+ updated_total: u64,
+}
+
+struct Subtraction {
+ subtracted_value: u64,
+ updated_total: u64,
+}
+
+abi ValueStore {
+ #[storage(read, write)]
+ fn add(value: u64);
+
+ #[storage(read, write)]
+ fn subtract(value: u64) -> Subtraction;
+}
+
+storage {
+ total: u64 = 1000,
+}
+
+impl ValueStore for Contract {
+ #[storage(read, write)]
+ fn add(value: u64) {
+ let updated_total = storage.total.read() + value;
+ storage.total.write(updated_total);
+ log(
+ Addition {
+ added_value: value,
+ updated_total
+ }
+ )
+ }
+
+ #[storage(read, write)]
+ fn subtract(value: u64) -> Subtraction {
+ let updated_total = storage.total.read() - value;
+ storage.total.write(updated_total);
+
+ Subtraction {
+ subtracted_value: value,
+ updated_total
+ }
+ }
+}
+
+Addition
and Subtraction
. As we'll soon see, indexers can process custom types that are logged or returned as part of a function.forc build
generates a JSON ABI similar to the lightly-edited one below:{
+ "types": [
+ {
+ "typeId": 0,
+ "type": "()",
+ "components": [],
+ "typeParameters": null
+ },
+ {
+ "typeId": 1,
+ "type": "struct Addition",
+ "components": [
+ {
+ "name": "added_value",
+ "type": 3,
+ "typeArguments": null
+ },
+ {
+ "name": "updated_total",
+ "type": 3,
+ "typeArguments": null
+ }
+ ],
+ "typeParameters": null
+ },
+ {
+ "typeId": 2,
+ "type": "struct Subtraction",
+ "components": [
+ {
+ "name": "subtracted_value",
+ "type": 3,
+ "typeArguments": null
+ },
+ {
+ "name": "updated_total",
+ "type": 3,
+ "typeArguments": null
+ }
+ ],
+ "typeParameters": null
+ },
+ {
+ "typeId": 3,
+ "type": "u64",
+ "components": null,
+ "typeParameters": null
+ }
+ ],
+ "functions": [...],
+ "loggedTypes": [
+ {
+ "logId": 0,
+ "loggedType": {
+ "name": "",
+ "type": 1,
+ "typeArguments": []
+ }
+ }
+ ],
+ "messagesTypes": [...],
+ "configurables": [...]
+}
+
+
+Now that we've discussed how to generate the JSON ABI for our Sway smart contract, let's now cover how to create an associated GraphQL schema.
+To index the contracts and store information about our Sway types in the database, we should create a schema. Let's design a schema that has an entity for each Sway type:
+type AddEntity @entity {
+ id: ID!
+ value: U64!
+ updated_total: U64!
+}
+
+type SubtractEntity @entity {
+ id: ID!
+ value: U64!
+ updated_total: U64!
+}
+
+++Note how the types used here, match the types used in our Sway smart contract. For a detailed mapping of these types, please see the Storing Records section.
+
So far we've covered how to (1) write your Sway smart contract and generate its JSON ABI, and (2) create types in your GraphQL schema that align with your Sway types.
+Next, we'll cover how to write the manifest file for your indexer.
+Before writing any of the handler code for your indexer, we need to make sure that our indexer manifest contains the necessary information to allow for the compiler to parse our contract types.
+Specifically, we should ensure that the contract_abi
and graphql_schema
fields point to the correct locations, respectively.
# A namespace is a logical grouping of declared names. Think of the namespace
+# as an organization identifier
+namespace: fuellabs
+
+# The identifier field is used to identify the given index.
+identifier: custom_types_example
+
+# The abi option is used to provide a link to the Sway JSON ABI that is generated when you
+# build your project.
+abi: path/to/custom/type/example/contract-abi.json
+
+# The particular start block after which you'd like your indexer to start indexing events.
+start_block: ~
+
+# The particular end block after which you'd like your indexer to stop indexing events.
+end_block: ~
+
+# The `fuel_client` denotes the address (host, port combination) of the running Fuel client
+# that you would like your indexer to index events from. In order to use this per-indexer
+# `fuel_client` option, the indexer service at which your indexer is deployed will have to run
+# with the `--indexer_net_config` option.
+fuel_client: ~
+
+# The contract_id specifies which particular contract you would like your index to subscribe to.
+contract_id: ~
+
+# The graphql_schema field contains the file path that points to the GraphQL schema for the
+# given index.
+graphql_schema: path/to/custom/type/example/indexer.schema.graphql
+
+# The module field contains a file path that points to code that will be run as an executor inside
+# of the indexer.
+# Important: At this time, wasm is the preferred method of execution.
+module:
+ wasm: ~
+
+# The resumable field contains a boolean that specifies whether or not the indexer should, synchronise
+# with the latest block if it has fallen out of sync.
+resumable: true
+
+Finally, we can create handlers to index these particular types and store them in the database. Let's look at the following example:
+use fuel_indexer_utils::prelude::*;
+
+#[indexer(manifest = "indexer.manifest.yaml")]
+mod indexer_mod {
+ fn index_addition(addition_event: Addition) {
+ let addition = AddEntity {
+ id: 123,
+ value: addition_event.added_value,
+ updated_total: addition_event.updated_total
+ };
+ addition.save();
+ }
+
+ fn index_subtraction(subtraction_event: Subtraction) {
+ let subtraction = SubtractEntity {
+ id: 123,
+ value: subtraction_event.subtracted_value,
+ updated_total: subtraction_event.updated_total
+ };
+ subtraction.save();
+ }
+}
+Regardless of whether a custom type was logged (e.g. Addition
) or returned (e.g. Subtraction
), the type will be available for you to use in your functions. Just include the type(s) you want your function to use in the parameters, and the function will be executed whenever each of the parameters have been satisfied by an instance of the type(s).
The Fuel indexer uses PostgreSQL as the primary database.
+++💡 We're open to supporting other storage solutions in the future.
+
Below is a mapping of GraphQL schema types to their Sway and database equivalents. Note that an empty cell denotes that there is no direct equivalent for the type in the corresponding domain.
+GraphQL Scalar | Sway Type | Postgres Type |
---|---|---|
Address | b256 | varchar(64) |
AssetId | u8[32] | varchar(64) |
Boolean | bool | boolean |
Bytes | str[] | varchar(10485760) |
Bytes32 | str[32] | varchar(64) |
Bytes4 | str[4] | varchar(8) |
Bytes64 | str[64] | varchar(128) |
Bytes8 | str[8] | varchar(16) |
ContractId | b256 | varchar(64) |
I128 | numeric(39,0) | |
I32 | u32 | integer |
I64 | u64 | bigint |
I8 | u8 | integer |
ID | varchar(64) primary key | |
Json | str[] | json |
U128 | numeric(39, 0) | |
U32 | u32 | integer |
U64 | u64 | numeric(20, 0) |
U8 | u8 | integer |
UID | varchar(64) | |
String | str[] | varchar(255) |
Let's define an Event
struct in a Sway contract:
struct Event {
+ id: u64,
+ address: Address,
+ block_height: u64,
+}
+
+The corresponding GraphQL schema to mirror this Event
struct would resemble:
type Event @entity {
+ id: ID!
+ account: Address!
+ block_height: U64!
+}
+
+And finally, this GraphQL schema will generate the following Postgres schema:
+ Table "schema.event"
+ Column | Type | Collation | Nullable | Default | Storage | Compression | Stats target | Description
+--------------+-------------+-----------+----------+---------+----------+-------------+--------------+-------------
+ id | bigint | | not null | | plain | | |
+ block_height | bigint | | not null | | plain | | |
+ address | varchar(64) | | not null | | plain | | |
+ object | bytea | | not null | | extended | | |
+Indexes:
+ "event_pkey" PRIMARY KEY, btree (id)
+Access method: heap
+
+Once data has been persisted into your storage backend, you can retrieve it by querying the GraphQL API server. By default, the API server can be reached at http://localhost:29987/api/graph/:namespace/:identifier
, where :namespace
and :identifier
are the values for the respective fields in your indexer's manifest. If you've changed the WEB_API_HOST
or WEB_API_PORT
values of your configuration, then you'll need to adjust the URL accordingly.
A basic query has the following form:
+query {
+ entity {
+ field_1
+ field_2
+ ...
+ }
+ ...
+}
+
+The entity
field corresponds to the name of an entity defined in your schema and the sub-fields are the fields defined on that entity type; entities and fields are stored in the database using the names defined in the schema, so make sure that your query uses those same names as well.
query {
+ block {
+ id
+ height
+ timestamp
+ }
+}
+
+We're requesting the ID, height, and timestamp for each block stored in the backend. If successful, the API server will return a response similar to the following:
+[
+ {
+ "height" : 1,
+ "id" : "f169a30cfcbf1eebd97a07b19de98e4b38a4367b03d1819943be41744339d38a",
+ "timestamp" : 1668710162
+ },
+ {
+ "height" : 2,
+ "id" : "a8c554758f78fe73054405d38099f5ad21a90c05206b5c6137424985c8fd10c7",
+ "timestamp" : 1668710163
+ },
+ {
+ "height" : 3,
+ "id" : "850ab156ddd9ac9502768f779936710fd3d792e9ea79bc0e4082de96450b5174",
+ "timestamp" : 1668710312
+ },
+ {
+ "height" : 4,
+ "id" : "19e19807c6988164b916a6877fe049d403d55a07324fa883cb7fa5cdb33438e2",
+ "timestamp" : 1668710313
+ },
+ {
+ "height" : 5,
+ "id" : "363af43cfd2a6d8af166ee46c15276b24b130fc6a89ce7b3c8737d29d6d0e1bb",
+ "timestamp" : 1668710314
+ }
+]
+
+The Fuel indexer supports foreign keys on entity types; thus, you can also ask for information about a referenced entity inside of your query. A nested query has the following general structure:
+query {
+ entityA {
+ field_A1
+ field_A2
+ referenced_entityB {
+ field_B1
+ field_B2
+ ...
+ }
+ ...
+ }
+ ...
+}
+
+Essentially, it's the same as the basic query example with an added sub-block to request information about the reference entity. The response from the API server will be returned in the same general structure as the query. Let's look at another example to illustrate how it works in practice.
+++Important: There is no limit to how deeply nested your entities and queries can be. However, every nested reference will add computation and latency to your query as the information will have to be retrieved from different tables in your storage backend. Please exercise caution in your entity design and try to minimize nesting as much as possible.
+
We'll start with the following example schema:
+type City @entity {
+ id: ID!
+ name: String!
+}
+
+type Library @entity {
+ id: ID!
+ name: String!
+ city: City!
+}
+
+type Book @entity {
+ id: ID!
+ title: String!
+ library: Library!
+}
+
+type Character @entity {
+ id: ID!
+ name: String!
+ book: Book!
+}
+
+This schema uses implicit foreign keys to reference other entities; for more information on implicit and explicit foreign keys, please refer to the Relationships section of the book. In this contrived example, we're storing information about characters that are found in books which are stored in libraries that can be found in cities. This will be the query that we use to retrieve the aforementioned data:
+query {
+ character {
+ name
+ book {
+ title
+ library {
+ name
+ city {
+ name
+ }
+ }
+ }
+ }
+}
+
+Let's assume that we've created an indexer for this data and the indexed data has been stored in the database. If we send the query, we'll get the following response:
+[
+ {
+ "name": "Lil Ind X",
+ "book": {
+ "title": "Fuel Indexer",
+ "library": {
+ "name": "Fuel Labs Library",
+ "city": {
+ "name": "Fuel City"
+ }
+ }
+ }
+ }
+]
+
+The Fuel indexer's GraphQL Playground is an interactive, in-browser GraphQL IDE that allows developers to easily explore and test the indexer's GraphQL API server. You can read more about the GraphQL playground in general here.
+Every public indexer can access the GraphQL playground of the Fuel indexer node on which the given indexer runs, so users and developers can get to querying their data right away.
+To use the GraphQL playground to explore your indices, start your indexer service, then open the following URL in your browser - where namespace
and identifier
correspond to the namespace and identifier of the index that you'd like to explore.
http://localhost:29987/api/playground/:namespace/:identifier
+
+The Fuel indexer currently supports the following search and filtering operations:
+Additionally, you can combine these operations using the and
or or
keywords, and invert operations by using the not
keyword.
++You should practice sensible database design when filtering records. Apply database indices to the underlying columns in order to make search operations more efficient; however, be advised that an overuse of database indices will lead to degraded performance.
+
You can query for a particular instance of an object by passing an ID value to with the id
key. For example, let's say that you are storing blocks and you want details about a certain block, you would pass the corresponding ID:
query {
+ block(id: 4121419699470229811) {
+ id
+ hash
+ height
+ producer
+ }
+}
+
+{
+ "data": [
+ {
+ "hash": "aff5eb785f2d24ae62858fa673296e957abea518858e2f08bb47df2dbb9c8ca1",
+ "height": 8209,
+ "id": 4121419699470229811,
+ "producer": "f65d6448a273b531ee942c133bb91a6f904c7d7f3104cdaf6b9f7f50d3518871"
+ }
+ ]
+}
+
+++Note: Remember that IDs currently must be of type
+u64
, and as such, the ID selection operation will only allow for au64
value. We're open to changing this in the future.
You can store null values in your records if the corresponding entity fields are configured to allow for it. You can exclude records that contain null values in a particular column or set of columns by using the has
operator inside of a filter
object.
query {
+ block(filter: { has: [producer] } ) {
+ id
+ hash
+ height
+ producer
+ }
+}
+
+{
+ "data": [
+ {
+ "hash": "d0d663e0bf499aca19d3ecb9b0b291234dc3769d2b46512016eca7244ca0ef22",
+ "height": 411,
+ "id": 3775485677453992400,
+ "producer": "f65d6448a273b531ee942c133bb91a6f904c7d7f3104cdaf6b9f7f50d3518871"
+ },
+ {
+ "hash": "7ff79bf3793143c557225f37b7e7d8a2b9d2e544b839d62cc367b8c5b079d478",
+ "height": 412,
+ "id": 3919088689958184000,
+ "producer": "f65d6448a273b531ee942c133bb91a6f904c7d7f3104cdaf6b9f7f50d3518871"
+ }
+ ]
+}
+
+Additionally, you can exclude records in which a particular column's value does not contain any elements in a given set by using the in
operator as part of a filter
object.
query {
+ block(filter: { height: { in: [1, 2, 3] } } ) {
+ id
+ hash
+ height
+ }
+}
+
+{
+ "data": [
+ {
+ "hash": "d77632f85669dd44737abf36b32f479ae518e07a9174c8571377ebb81563bb9a",
+ "height": 1,
+ "id": 3618468784755926500
+ },
+ {
+ "hash": "7cb9542b624d88b3d66c6c9a1835f66fecba8892a87ffab9c17251c456ca5dcd",
+ "height": 2,
+ "id": 4122538829619016000
+ },
+ {
+ "hash": "24f9611115f7ecb4a393751933a9f89329812cf08bdbe483c071b3401d06c8d6",
+ "height": 3,
+ "id": 3762867646901937000
+ }
+ ]
+}
+
+Finally, you can filter records by comparing the values of certain fields to a particular value of your choosing by using one of the comparison operators inside of a filter
object.
You can do simple value comparisons using any of the following operators:
+equals | equals |
greater than | gt |
greater than or equal to | gte |
less than | lt |
less than or equal to | lte |
Here's an example:
+query {
+ block(filter: { height: { lte: 5 } } ) {
+ id
+ hash
+ height
+ producer
+ }
+}
+
+{
+ "data": [
+ {
+ "hash": "d77632f85669dd44737abf36b32f479ae518e07a9174c8571377ebb81563bb9a",
+ "height": 1,
+ "id": 3618468784755926500,
+ "producer": "f65d6448a273b531ee942c133bb91a6f904c7d7f3104cdaf6b9f7f50d3518871"
+ },
+ {
+ "hash": "7cb9542b624d88b3d66c6c9a1835f66fecba8892a87ffab9c17251c456ca5dcd",
+ "height": 2,
+ "id": 4122538829619016000,
+ "producer": "f65d6448a273b531ee942c133bb91a6f904c7d7f3104cdaf6b9f7f50d3518871"
+ },
+ {
+ "hash": "24f9611115f7ecb4a393751933a9f89329812cf08bdbe483c071b3401d06c8d6",
+ "height": 3,
+ "id": 3762867646901937000,
+ "producer": "f65d6448a273b531ee942c133bb91a6f904c7d7f3104cdaf6b9f7f50d3518871"
+ },
+ {
+ "hash": "616566afdc141ecd2b60fdc56aae4f3d04b3f6db9e65a3c21d0105a08cc1b349",
+ "height": 4,
+ "id": 3833467323683451000,
+ "producer": "f65d6448a273b531ee942c133bb91a6f904c7d7f3104cdaf6b9f7f50d3518871"
+ },
+ {
+ "hash": "1dca838d492f29b7a3afa7755ac8741c99db992da47673cd27be86f9b0620118",
+ "height": 5,
+ "id": 3991987200693004000,
+ "producer": "f65d6448a273b531ee942c133bb91a6f904c7d7f3104cdaf6b9f7f50d3518871"
+ }
+ ]
+}
+
+You can also filter for records that are contained in the range of two values by using the between
operator. To do so, you'd set the lower bound using the min
keyword and the upper bound by using max
.
query {
+ block(filter: { height: { between: { min: 101, max: 103 } } } ) {
+ id
+ hash
+ height
+ producer
+ }
+}
+
+{
+ "data": [
+ {
+ "hash": "3b85fbed2d933d0334d54776612a5af72a513e875d06fa9152f6d41d0e50e417",
+ "height": 101,
+ "id": 3763145849079675000,
+ "producer": "f65d6448a273b531ee942c133bb91a6f904c7d7f3104cdaf6b9f7f50d3518871"
+ },
+ {
+ "hash": "deea78034c2f0fcd7ef2d2d2d203d19fcd63f1b1846fac089c51c2aa7b5c8149",
+ "height": 102,
+ "id": 7365137137742930000,
+ "producer": "f65d6448a273b531ee942c133bb91a6f904c7d7f3104cdaf6b9f7f50d3518871"
+ },
+ {
+ "hash": "a405d5688fdf41817868361217a09812349cc6fe0fe2bf9329fcd23e338e9444",
+ "height": 103,
+ "id": 7292000934927820000,
+ "producer": "f65d6448a273b531ee942c133bb91a6f904c7d7f3104cdaf6b9f7f50d3518871"
+ }
+ ]
+}
+
+As previously stated, you can combine or invert operations to filter for your desired results even further.
+Let's look at an example query in which we combine two filters together.
+query {
+ block(filter: {
+ producer: { equals: "f65d6448a273b531ee942c133bb91a6f904c7d7f3104cdaf6b9f7f50d3518871" },
+ and: { height: { lt: 4 } }
+ } ) {
+ id
+ hash
+ height
+ producer
+ }
+}
+
+{
+ "data": [
+ {
+ "hash": "d77632f85669dd44737abf36b32f479ae518e07a9174c8571377ebb81563bb9a",
+ "height": 1,
+ "id": 3618468784755926500,
+ "producer": "f65d6448a273b531ee942c133bb91a6f904c7d7f3104cdaf6b9f7f50d3518871"
+ },
+ {
+ "hash": "7cb9542b624d88b3d66c6c9a1835f66fecba8892a87ffab9c17251c456ca5dcd",
+ "height": 2,
+ "id": 4122538829619016000,
+ "producer": "f65d6448a273b531ee942c133bb91a6f904c7d7f3104cdaf6b9f7f50d3518871"
+ },
+ {
+ "hash": "24f9611115f7ecb4a393751933a9f89329812cf08bdbe483c071b3401d06c8d6",
+ "height": 3,
+ "id": 3762867646901937000,
+ "producer": "f65d6448a273b531ee942c133bb91a6f904c7d7f3104cdaf6b9f7f50d3518871"
+ }
+ ]
+}
+
+You can also use the not
operator in order to invert the operation of a particular filter. For example, the following query returns contracts that we've seen on the network.
query {
+ contract {
+ id
+ }
+}
+
+{
+ "data": [
+ {
+ "id": "1072ca8fcab43048a5b31c1ea204748c2cb5acca6b90f3b1a02ef7a2d92386d9"
+ },
+ {
+ "id": "9b8b258e0d64b9e8a022e3f38a751ad5a1b36e4dfdcc25a6fb8308e044250b8c"
+ },
+ {
+ "id": "0000000000000000000000000000000000000000000000000000000000000000"
+ },
+ {
+ "id": "8fe8ce43603c1a48274aac7532da56707901d9606a2b05de801993f48ea6bfe7"
+ }
+ ]
+}
+
+Let's ignore the base asset contract by inverting the in
operator:
query {
+ contract(filter: {not: { id: { equals: "0000000000000000000000000000000000000000000000000000000000000000"}}}) {
+ id
+ }
+}
+
+{
+ "data": [
+ {
+ "id": "1072ca8fcab43048a5b31c1ea204748c2cb5acca6b90f3b1a02ef7a2d92386d9"
+ },
+ {
+ "id": "9b8b258e0d64b9e8a022e3f38a751ad5a1b36e4dfdcc25a6fb8308e044250b8c"
+ },
+ {
+ "id": "8fe8ce43603c1a48274aac7532da56707901d9606a2b05de801993f48ea6bfe7"
+ }
+ ]
+}
+
+The Fuel indexer currently supports offset-based pagination in order to allow users to selectively request parts of a set of results.
+A paginated query can be made using three keywords:
+order
- sort order (required)first
- limit on number of results (required)offset
- the amount of records to skip before returning results (optional)query {
+ blocks: block(order: { height: asc }, first: 5) {
+ hash
+ height
+ timestamp
+ }
+}
+
+In this query, we're requesting details about the first five blocks in ascending order of block height. You can also see that we've added a blocks
alias; this is completely optional and all it does is change the key for the list of results in the JSON response.
With this query, we receive the following response:
+{
+ "data": {
+ "blocks": [
+ {
+ "hash": "d77632f85669dd44737abf36b32f479ae518e07a9174c8571377ebb81563bb9a",
+ "height": 1,
+ "timestamp": 1678483351
+ },
+ {
+ "hash": "7cb9542b624d88b3d66c6c9a1835f66fecba8892a87ffab9c17251c456ca5dcd",
+ "height": 2,
+ "timestamp": 1678483471
+ },
+ {
+ "hash": "24f9611115f7ecb4a393751933a9f89329812cf08bdbe483c071b3401d06c8d6",
+ "height": 3,
+ "timestamp": 1678483591
+ },
+ {
+ "hash": "616566afdc141ecd2b60fdc56aae4f3d04b3f6db9e65a3c21d0105a08cc1b349",
+ "height": 4,
+ "timestamp": 1678483711
+ },
+ {
+ "hash": "1dca838d492f29b7a3afa7755ac8741c99db992da47673cd27be86f9b0620118",
+ "height": 5,
+ "timestamp": 1678483831
+ }
+ ],
+ "page_info": {
+ "has_next_page": true,
+ "limit": 5,
+ "offset": 0,
+ "pages": 80,
+ "total_count": 400
+ }
+ }
+}
+
+As you can see, we get the requested amount of blocks and the corresponding fields. However, there's also a page_info
object included in the response. This object tells us if there's another page available to request along with information that we can use to construct our next response. To get the next page, we'll add an offset
to our original query:
query {
+ blocks: block(
+ order: { height: asc },
+ first: 5,
+ offset: 5
+ ) {
+ hash
+ height
+ timestamp
+ }
+}
+
+The response contains the next five blocks after our requested offset, and the page_info
object lets us know what offset we used in the query.
{
+ "data": {
+ "blocks": [
+ {
+ "hash": "c93ffc9178d526a836d707137de08b0f743fabce79ecec77c419bfb7e6be8863",
+ "height": 6,
+ "timestamp": 1678483951
+ },
+ {
+ "hash": "4f0c81a42c86c718c0ae90ba838d6f1bdfc9a757cbf07c946fb3280b44257b46",
+ "height": 7,
+ "timestamp": 1678484071
+ },
+ {
+ "hash": "659b486cc2c3bd1133df9245645648b6a09b35e16c7f71bb05449cea0e83611c",
+ "height": 8,
+ "timestamp": 1678484191
+ },
+ {
+ "hash": "4bf61bd8f88b7fb40e842a6497d686bc2f63839ec3ca1eedb4e81a0935adaeb6",
+ "height": 9,
+ "timestamp": 1678484311
+ },
+ {
+ "hash": "b090634788ddd0461cba4d0833a3f15b8e2f51e672fb1527fc8c78cd8f80dc1a",
+ "height": 10,
+ "timestamp": 1678484431
+ }
+ ],
+ "page_info": {
+ "has_next_page": true,
+ "limit": 5,
+ "offset": 5,
+ "pages": 80,
+ "total_count": 400
+ }
+ }
+}
+
+Finally, let's combine nested entities, filtering, and pagination into one complete example.
+Sticking with the same block explorer example, let's say that we are looking for a particular transaction and its containing block, but we don't remember either of the hashes. All we know is that the total value of the transaction is greater than zero, it was sometime after the start of the beta-5
testnet, and it was included as part of the first fifty blocks. Additionally, we don't want to parse through all the results at once, so we only want to look at two records at a time. Finally, we think that it may have been on the more recent side, so we want to check them in reverse chronological order.
Putting all of that together, we get the following query:
+query {
+ transactions: tx(
+ order: { timestamp: desc },
+ filter: { value: { gt: 0 } },
+ first: 2,
+ offset: 0
+ ) {
+ id
+ hash
+ timestamp
+ value
+ block (
+ filter: {
+ height: { between: { min: 0, max: 50 } },
+ and: {
+ timestamp: { gt: 1678410000 }
+ }
+ }
+ ) {
+ id
+ hash
+ height
+ timestamp
+ }
+ }
+}
+
+The Fuel indexer's GraphQL API allows you to add filters on multiple entity fields and even nested entities! In the query above, we're asking for the two most recent transactions with a value greater than zero. Also, we're applying two filters to the nested block
entity by using the and
operator in order to help us narrow down the set of results.
The response returns the results in the expected format and includes additional information that informs us about how many total results satisfy the criteria.
+{
+ "data": {
+ "page_info": {
+ "has_next_page": true,
+ "limit": 2,
+ "offset": 0,
+ "pages": 2,
+ "total_count": 4
+ },
+ "transactions": [
+ {
+ "block": {
+ "hash": "f40297895086e66c0947c213dd29e90f596b860d10316ab806064608dd2580cd",
+ "height": 45,
+ "id": 7306026486395921000,
+ "timestamp": 1678486898
+ },
+ "hash": "85acfa181ebfa3b48c10d3181217918dd377b875d07dabc72d6d1081e4c52713",
+ "id": 3919319574514776000,
+ "timestamp": 1678486898,
+ "value": 10000000000
+ },
+ {
+ "block": {
+ "hash": "e3e0860a358c0d044669748cffff82b4b0073baaca53a128ddc8ce3757ae3988",
+ "height": 41,
+ "id": 7018409465212200000,
+ "timestamp": 1678486633
+ },
+ "hash": "42f3fd7ffa073975a0eca993044a867d8c87a8d39f5a88032a3b9aba213f6102",
+ "id": 7364622549171910000,
+ "timestamp": 1678486633,
+ "value": 10000000000
+ }
+ ]
+ }
+}
+
+The Fuel indexer's authentication functionality offers users a range of options for verifying their identity. The system supports any arbitrary authentication scheme (in theory); however, in practice the service defaults to JWT authentication due to its stateless nature and popularity.
+To authenticate using JWT, users ask an indexer operator for a nonce, sign that nonce with their wallet, then send both the nonce and signature to the indexer operator for verification. Once the signature is verified on the backend, a valid JWT is produced and returned to the user, thus authenticating the user.
+It is important to note that authentication is disabled by default. However, if authentication is enabled, users will need to authenticate before performing operations that involve modifying the state of the service, such as uploading indexers.
+The new authentication functionality offers a flexible and secure way for users to authenticate and perform operations that affect the service's state.
+Below is a demonstration of basic JWT authentication using an indexer operator at "https://beta-5-indexer.fuel.network"
+forc index auth --url https://beta-5-indexer.fuel.network:29987
+
+You will first be prompted for the password for your wallet:
+Please enter your wallet password:
+
+After successfully entering your wallet password you should be presented with your new JWT token.
+✅ Successfully authenticated at https://beta-5-indexer.fuel.network:29987/api/auth/signature.
+
+Token: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiODNlNjhiOTFmNDhjYWM4M....
+
+Use this token in your Authorization
headers when making requests for operations such as uploading indexers, stopping indexers, and other operations that mutate state in this way.
Users can just pass this JWT token value to the --auth
flag, if using forc index
commands that support authentication (e.g., forc index deploy --auth $MY_JWT_TOKEN
).
forc index
is the recommended method for end users to interact with the Fuel indexer. After you have installed fuelup
, you can run the forc index help
command in your terminal to view the available commands.
<!-- markdownlint-disable MD033 -->
+<!-- markdownlint-disable MD025 -->
+<!-- markdownlint-disable MD041 -->
+<p align="center">
+ <picture>
+ <source media="(prefers-color-scheme: dark)" srcset="./img/fuel-indexer-logo-dark.png">
+ <img alt="Fuel Indexer logo" width="400px" src="./img/fuel-indexer-logo-light.png">
+ </picture>
+
+</p>
+<p align="center">
+ <a href="https://github.com/FuelLabs/fuel-indexer/actions/workflows/ci.yml" alt="CI">
+ <img src="https://img.shields.io/github/actions/workflow/status/FuelLabs/fuel-indexer/ci.yml?event=release" />
+ </a>
+ <a href="https://docs.rs/fuel-indexer/" alt="docs.rs">
+ <img src="https://docs.rs/fuel-indexer/badge.svg" />
+ </a>
+ <a href="https://crates.io/crates/fuel-indexer" alt="crates.io">
+ <img src="https://img.shields.io/crates/v/fuel-indexer?label=latest" />
+ </a>
+ <a href="https://crates.io/crates/fuel-indexer" alt="img-shields">
+ <img alt="GitHub commits since latest release (by date including pre-releases)" src="https://img.shields.io/github/commits-since/FuelLabs/fuel-indexer/latest?include_prereleases">
+ </a>
+ <a href="https://discord.gg/xfpK4Pe" alt="Discord">
+ <img src="https://img.shields.io/badge/chat%20on-discord-orange?&logo=discord&logoColor=ffffff&color=7389D8&labelColor=6A7EC2" />
+ </a>
+</p>
+
+### [➡️ Read the Quickstart! ➡️](https://docs.fuel.network/docs/indexer/getting-started/quickstart/)
+
+## What is the Fuel indexer?
+
+The Fuel indexer is a standalone service that can be used to index various components of the blockchain. These indexable components include blocks, transactions, receipts, and state within the Fuel network, allowing for high-performance read-only access to the blockchain for advanced dApp use-cases.
+
+> TLDR: It's Infrastructure as a service (IaaS) for Web3 dApp backends.
+
+## Install
+
+Fuel's indexer supports Linux (x64 & arm64) and macOS (x64 & Apple Silicon).
+
+> If you don't want to deal with dependency issues we recommend just using Fuel's indexer with Docker, [via the included docker-compose file](https://github.com/FuelLabs/fuel-indexer/blob/develop/scripts/docker-compose.yaml).
+
+Install Fuel's toolchain manager - fuelup.
+
+```bash
+curl --proto '=https' --tlsv1.2 -sSf https://install.fuel.network/fuelup-init.sh | sh
+
+++The
+fuel-indexer
andforc-index
binaries should now be available in your$PATH
For development, users will primarily use the forc index
command line utility made available after installing fuelup.
forc index --help
+
+forc index 0.0.0
+Fuel Indexer Orchestrator
+
+USAGE:
+ forc-index <SUBCOMMAND>
+
+OPTIONS:
+ -h, --help Print help information
+ -V, --version Print version information
+
+SUBCOMMANDS:
+ auth Authenticate against an indexer service
+ build Build an indexer
+ check Check for Fuel indexer components
+ deploy Deploy an indexer to an indexer service
+ help Print this message or the help of the given subcommand(s)
+ kill Kill the indexer process. Note that this command will kill any process
+ listening on the default indexer port or the port specified by the `--port`
+ flag
+ new Create a new indexer project in a new directory
+ postgres Fuel Postgres Orchestrator
+ remove Stop and remove a running indexer
+ start Standalone binary for the Fuel indexer service
+ status Check the status of a registered indexer
+
+If you're interested in contributing PRs to make the Fuel indexer a better project, feel free to read our contributors document.
+
+forc index auth
Authenticate against an indexer operator.
+++IMPORTANT: There must be an indexer service running at
+--url
in order for this to work.
forc index auth --account 0
+
+Authenticate against an indexer service
+
+USAGE:
+ forc-index auth [OPTIONS]
+
+OPTIONS:
+ --account <ACCOUNT> Index of account to use for signing. [default: 0]
+ -h, --help Print help information
+ --url <URL> URL at which to deploy indexer assets. [default:
+ http://127.0.0.1:29987]
+ -v, --verbose Verbose output.
+
+forc index build
Build an indexer.
+forc index build
+
+Build an indexer
+
+USAGE:
+ forc-index build [OPTIONS]
+
+OPTIONS:
+ -d, --debug Build artifacts with the debug profile.
+ -h, --help Print help information
+ --locked Ensure that the Cargo.lock file is up-to-date.
+ -m, --manifest <MANIFEST> Manifest file name of indexer being built.
+ -p, --path <PATH> Path to the indexer project.
+ -v, --verbose Enable verbose output.
+
+
+forc index check
Check to see which indexer components you have installed.
+forc index check
+
+Check for Fuel indexer components
+
+USAGE:
+ forc-index check
+
+OPTIONS:
+ -h, --help Print help information
+
+forc index deploy
Deploy an indexer to an indexer service.
+forc index deploy --url https://beta-5-indexer.fuel.network
+
+Deploy an indexer to an indexer service
+
+USAGE:
+ forc-index deploy [OPTIONS]
+
+OPTIONS:
+ --auth <AUTH> Authentication header value.
+ -d, --debug Build optimized artifacts with the debug profile.
+ -h, --help Print help information
+ --locked Ensure that the Cargo.lock file is up-to-date.
+ -m, --manifest <MANIFEST> Path to the manifest of indexer project being deployed.
+ -p, --path <PATH> Path to the indexer project.
+ --remove-data Remove all indexed data when replacing an existing indexer.
+ --replace-indexer If an indexer with the same UID exists, remove it.
+ --skip-build Do not build before deploying.
+ --url <URL> URL at which to deploy indexer assets. [default:
+ http://127.0.0.1:29987]
+ -v, --verbose Enable verbose logging.
+
+
+forc index kill
Kill the indexer process. Note that this command will kill any process listening on the default indexer port or the port specified by the --port
flag.
forc index kill --port 29987
+
+Kill the indexer process. Note that this command will kill any process listening on the default
+indexer port or the port specified by the `--port` flag
+
+USAGE:
+ forc-index kill [OPTIONS]
+
+OPTIONS:
+ -9 Terminate or kill
+ -h, --help Print help information
+ --port <PORT> Port at which to detect indexer service API is running. [default: 29987]
+
+forc index new
Create a new indexer project in a new directory.
+forc index new --namespace fuel /home/fuel/projects/example_indexer
+
+Create a new indexer project in a new directory
+
+USAGE:
+ forc-index new [OPTIONS] <PATH>
+
+ARGS:
+ <PATH> Path at which to create indexer
+
+OPTIONS:
+ --absolute-paths Resolve indexer asset filepaths using absolute paths.
+ -h, --help Print help information
+ --name <NAME> Name of indexer.
+ --namespace <NAMESPACE> Namespace to which indexer belongs.
+ -v, --verbose Enable verbose output.
+
+forc index remove
Stop and remove a running indexer.
+forc index remove --url https://beta-5-indexer.fuel.network
+
+Stop and remove a running indexer
+
+USAGE:
+ forc-index remove [OPTIONS]
+
+OPTIONS:
+ --auth <AUTH> Authentication header value.
+ -h, --help Print help information
+ -m, --manifest <MANIFEST> Path to the manifest of the indexer project being removed.
+ -p, --path <PATH> Path to the indexer project.
+ --url <URL> URL at which indexer is deployed. [default: http://127.0.0.1:29987]
+ -v, --verbose Enable verbose output.
+
+forc index start
Start a local Fuel Indexer service.
+forc index start
+
+Standalone binary for the Fuel indexer service
+
+USAGE:
+ forc-index start [OPTIONS]
+
+OPTIONS:
+ --accept-sql-queries
+ Allow the web server to accept raw SQL queries.
+
+ --allow-non-sequential-blocks
+ Allow missing blocks or non-sequential block processing.
+
+ --auth-enabled
+ Require users to authenticate for some operations.
+
+ --auth-strategy <AUTH_STRATEGY>
+ Authentication scheme used.
+
+ --block-page-size <BLOCK_PAGE_SIZE>
+ Amount of blocks to return in a request to a Fuel node. [default: 20]
+
+ -c, --config <FILE>
+ Indexer service config file.
+
+ --client-request-delay <CLIENT_REQUEST_DELAY>
+ Make the service wait for the given duration between block requests to a Fuel client.
+
+ --database <DATABASE>
+ Database type. [default: postgres] [possible values: postgres]
+
+ --disable-toolchain-version-check
+ By default, Fuel Indexer will only accept WASM indexer modules compiled with the same
+ toolchain version as the version of Fuel Indexer.
+
+ --embedded-database
+ Automatically create and start database using provided options or defaults.
+
+ --fuel-node-host <FUEL_NODE_HOST>
+ Host of the running Fuel node. [default: localhost]
+
+ --fuel-node-port <FUEL_NODE_PORT>
+ Listening port of the running Fuel node. [default: 4000]
+
+ -h, --help
+ Print help information
+
+ --indexer-net-config
+ Allow network configuration via indexer manifests.
+
+ --jwt-expiry <JWT_EXPIRY>
+ Amount of time (seconds) before expiring token (if JWT scheme is specified).
+
+ --jwt-issuer <JWT_ISSUER>
+ Issuer of JWT claims (if JWT scheme is specified).
+
+ --jwt-secret <JWT_SECRET>
+ Secret used for JWT scheme (if JWT scheme is specified).
+
+ --local-fuel-node
+ Start a local Fuel node.
+
+ --log-level <LOG_LEVEL>
+ Log level passed to the Fuel Indexer service. [default: info] [possible values: info,
+ debug, error, warn]
+
+ -m, --manifest <FILE>
+ Indexer config file.
+
+ --max-body-size <MAX_BODY_SIZE>
+ Max body size for web server requests. [default: 5242880]
+
+ --metering-points <METERING_POINTS>
+ The number of WASM opcodes after which the indexer's event handler will stop execution.
+ [default: 30000000000]
+
+ --metrics
+ Use Prometheus metrics reporting.
+
+ --network <NETWORK>
+ Use a network alias when connecting to a Fuel client. [default: beta-3 beta-5 beta-5]
+
+ --postgres-database <POSTGRES_DATABASE>
+ Postgres database.
+
+ --postgres-host <POSTGRES_HOST>
+ Postgres host.
+
+ --postgres-password <POSTGRES_PASSWORD>
+ Postgres password.
+
+ --postgres-port <POSTGRES_PORT>
+ Postgres port.
+
+ --postgres-user <POSTGRES_USER>
+ Postgres username.
+
+ --rate-limit
+ Enable rate limiting.
+
+ --rate-limit-request-count <RATE_LIMIT_REQUEST_COUNT>
+ Maximum number of requests to allow over --rate-limit-window..
+
+ --rate-limit-window-size <RATE_LIMIT_WINDOW_SIZE>
+ Number of seconds over which to allow --rate-limit-rps.
+
+ --remove-data
+ When replacing an indexer, also remove the indexed data.
+
+ --replace-indexer
+ Whether to allow replacing an existing indexer. If not specified, an attempt to deploy
+ over an existing indexer results in an error.
+
+ --run-migrations
+ Run database migrations before starting service.
+
+ --stop-idle-indexers
+ Prevent indexers from running without handling any blocks.
+
+ -v, --verbose
+ Enable verbose logging.
+
+ -V, --version
+ Print version information
+
+ --web-api-host <WEB_API_HOST>
+ Web API host. [default: localhost]
+
+ --web-api-port <WEB_API_PORT>
+ Web API port. [default: 29987]
+
+forc index status
Check the status of a registered indexer.
+forc index status --url https://beta-5-indexer.fuel.network
+
+Check the status of a registered indexer
+
+USAGE:
+ forc-index status [OPTIONS]
+
+OPTIONS:
+ --auth <AUTH> Authentication header value.
+ -h, --help Print help information
+ --url <URL> URL at which to find indexer service. [default: http://127.0.0.1:29987]
+ -v, --verbose Enable verbose logging.
+
+
+forc index postgres
forc index postgres
is provided as a way to simplify the setup and management of an embedded Postgres database. After you have installed fuelup
, you can run the forc index postgres help
command in your terminal to view the available commands.
forc index postgres help
+
+USAGE:
+ forc-index postgres <SUBCOMMAND>
+
+OPTIONS:
+ -h, --help Print help information
+ -V, --version Print version information
+
+SUBCOMMANDS:
+ create Create a new database
+ drop Drop a database
+ help Print this message or the help of the given subcommand(s)
+ start Start PostgreSQL with a database
+ stop Stop PostgreSQL
+
+forc index postgres create
Create a new database.
+forc index postgres create example_database
+
+USAGE:
+ forc-index postgres create [OPTIONS] <NAME>
+
+ARGS:
+ <NAME> Name of database.
+
+OPTIONS:
+ --auth-method <AUTH_METHOD>
+ Authentication method. [default: plain] [possible values: plain, md5, scram-sha-256]
+
+ -c, --config <CONFIG>
+ Fuel indexer configuration file.
+
+ --database-dir <DATABASE_DIR>
+ Where to store the PostgreSQL database.
+
+ -h, --help
+ Print help information
+
+ --migration-dir <MIGRATION_DIR>
+ The directory containing migration scripts.
+
+ -p, --password <PASSWORD>
+ Database password. [default: postgres]
+
+ -p, --port <PORT>
+ Port to use. [default: 5432]
+
+ --persistent
+ Do not clean up files and directories on database drop.
+
+ --postgres-version <POSTGRES_VERSION>
+ PostgreSQL version to use. [default: v14] [possible values: v15, v14, v13, v12, v11,
+ v10, v9]
+
+ --start
+ Start the PostgreSQL instance after creation.
+
+ --timeout <TIMEOUT>
+ Duration to wait before terminating process execution for pg_ctl start/stop and initdb.
+
+ -u, --user <USER>
+ Database user. [default: postgres]
+
+forc index postgres drop
Drop a database.
+forc index postgres drop example_database
+
+USAGE:
+ forc-index postgres drop [OPTIONS] <NAME>
+
+ARGS:
+ <NAME> Name of database.
+
+OPTIONS:
+ -c, --config <CONFIG>
+ Fuel indexer configuration file.
+
+ --database-dir <DATABASE_DIR>
+ Where the PostgreSQL database is stored.
+
+ -h, --help
+ Print help information
+
+ --remove-persisted
+ Remove all database files that might have been persisted to disk.
+
+forc index postgres start
Start PostgreSQL with a database.
+forc index postgres start example_database
+
+USAGE:
+ forc-index postgres start [OPTIONS] <NAME>
+
+ARGS:
+ <NAME> Name of database.
+
+OPTIONS:
+ -c, --config <CONFIG> Fuel indexer configuration file.
+ --database-dir <DATABASE_DIR> Where the PostgreSQL database is stored.
+ -h, --help Print help information
+
+forc index postgres stop
Stop PostgreSQL.
+forc index postgres stop example_database
+
+USAGE:
+ forc-index postgres stop [OPTIONS] <NAME>
+
+ARGS:
+ <NAME> Name of database.
+
+OPTIONS:
+ -c, --config <CONFIG> Fuel indexer configuration file.
+ --database-dir <DATABASE_DIR> Where the PostgreSQL database is stored.
+ -h, --help Print help information
+
+Thanks for your interest in contributing to the Fuel indexer! Below we've compiled a list of sections that you may find useful as you work on a potential contribution:
+fuelup
docker
fuelup
We use fuelup in order to get the binaries produced by services in the Fuel ecosystem. Fuelup will install binaries related to the Fuel node, the Fuel indexer, the Fuel orchestrator (forc), and other components. fuelup
can be downloaded here.
docker
We use Docker to produce reproducible environments for users that may be concerned with installing components with large sets of dependencies (e.g. Postgres). Docker can be downloaded here.
+At this time, the Fuel indexer requires the use of a database. We currently support a single database option: Postgres. PostgreSQL is a database solution with a complex feature set and requires a database server.
+++Note: The following explanation is for demonstration purposes only. A production setup should use secure users, permissions, and passwords.
+
On macOS systems, you can install PostgreSQL through Homebrew. If it isn't present on your system, you can install it according to the instructions. Once installed, you can add PostgreSQL to your system by running brew install postgresql
. You can then start the service through brew services start postgresql
. You'll need to create a database for your indexed data, which you can do by running createdb [DATABASE_NAME]
. You may also need to create the postgres
role; you can do so by running createuser -s postgres
.
For Linux-based systems, the installation process is similar. First, you should install PostgreSQL according to your distribution's instructions. Once installed, there should be a new postgres
user account; you can switch to that account by running sudo -i -u postgres
. After you have switched accounts, you may need to create a postgres
database role by running createuser --interactive
. You will be asked a few questions; the name of the role should be postgres
and you should elect for the new role to be a superuser. Finally, you can create a database by running createdb [DATABASE_NAME]
.
In either case, your PostgreSQL database should now be accessible at postgres://postgres@localhost:5432/[DATABASE_NAME]
.
After setting up your database, you should install sqlx-cli
in order to run migrations for your indexer service. You can do so by running cargo install sqlx-cli --features postgres
. Once installed, you can run the migrations by running the following command after changing DATABASE_URL
to match your setup.
git clone git@github.com:FuelLabs/fuel-indexer.git && cd fuel-indexer/
+
+cd packages/fuel-indexer-database/postgres
+DATABASE_URL=postgres://postgres@localhost sqlx migrate run
+
+cargo run --bin fuel-indexer run
+
+You can also start the service with a fresh local node for development purposes:
+cargo run --features fuel-core-lib --bin fuel-indexer run
+
+++If no configuration file or other options are passed, the service will default to a
+postgres://postgres@localhost
database connection.
Fuel indexer tests are currently broken out by a database feature flag. In order to run tests with a Postgres backend, use --features postgres
.
cargo test --locked --workspace --all-targets
+
+cargo test --locked --workspace --all-targets --features postgres
+
+trybuild
testsFor tests related to the meta-programming used in the Fuel indexer, we use trybuild
.
RUSTFLAGS='-D warnings' cargo test -p fuel-indexer-macros --locked
+
+Thanks for your interest in contributing to Fuel Indexer! This document outlines some the conventions on building, running, and testing Fuel Indexer.
+Fuel Indexer has many dependent repositories. If you need any help or mentoring getting started, understanding the codebase, or anything else, please ask on our Discord.
+rustdoc
comments for the new code so others can better understand the change.++Future instructions assume you are in this repository
+
git clone https://github.com/FuelLabs/fuel-indexer
+cd fuel-indexer
+
+rustup
is the official toolchain manager for Rust.
We use some additional components such as clippy
and rustfmt
, to install those:
rustup component add clippy
+rustup component add rustfmt
+
+Fuel Indexer also uses a few other tools installed via cargo
cargo install sqlx-cli
+cargo install wasm-snip
+
+Fuel Indexer's two primary crates are fuel-indexer
and fuel-indexer-api-server
.
You can build Fuel Indexer:
+cargo build -p fuel-indexer -p fuel-indexer-api-server --release --locked
+
+Linting is done using rustfmt
and clippy
, which are each separate commands:
cargo fmt --all --check
+
+cargo clippy --all-features --all-targets -- -D warnings
+
+The test suite follows the Rust cargo standards.
+Testing is simply done using Cargo:
+RUSTFLAGS='-D warnings' SQLX_OFFLINE=1 cargo test --locked --all-targets --all-features
+
+This is a rough outline of what a contributor's workflow looks like:
+develop
1234/short-description
, where 1234
is the number of the associated issue.[commit type]: [short commit blurb]
+fix: database locking issue
enhancement: i added something super cool
chore: i helped do the chores
bug
: If fixing broken functionalityenhancement
: If adding new functionalitychore
: If finishing valuable work (that's no fun!)testing
: If only updating/writing testsdocs
: If just updating docsfeat
: If adding a non-trivial new featureThanks for your contributions!
+For beginners, we have prepared many suitable tasks for you. Checkout our Good First Issues for a list.
+If you are planning something that relates to multiple components or changes current behaviors, make sure to open an issue to discuss with us before continuing.
+v2.0.0
-> v3.0.0
v0.3.0
-> v0.4.0
v0.1.3
-> v0.1.4
git clone git@github.com:FuelLabs/fuel-indexer.git
+
+cd packages/fuel-indexer-database/postgres
+DATABASE_URL=postgres://postgres@localhost sqlx migrate run
+
+cargo run --bin fuel-indexer
+
+cargo test --locked --workspace --all-features --all-targets
+
+There are a few system requirements related to compilation, tooling, and SQL backends that you'll need to be able to contribute to the Fuel indexer.
+apt update && apt install -y \
+ cmake \
+ pkg-config \
+ git \
+ gcc \
+ build-essential \
+ clang \
+ libclang-dev \
+ llvm \
+ libpq-dev
+
+Dependency | Required For |
---|---|
cmake | Manages the build process in an operating system and in a compiler-independent manner |
pkg-config | Language-agnostic helper tool used when compiling applications and libraries |
git | Version control system |
gcc | Compiler tools required to build various Fuel indexer crates |
clang /libclang-dev | Compiler tools required to build various Fuel indexer crates on Unix-like OSes |
llvm | Required for building Fuel indexer crate dependencies |
libpq-dev | Set of library function helping facilitate interaction with the PostgreSQL backend |
brew update && brew install \
+ cmake \
+ llvm \
+ libpq \
+ postgresql
+
+Dependency | Required For |
---|---|
cmake | Manages the build process in an operating system and in a compiler-independent manner |
llvm | Compiler infrastructure for building Fuel indexer crate dependencies |
libpq | Postgres C API library |
postgresql | Installs the command line console (psql ) as well as a PostgreSQL server locally |
pacman -Syu --needed --noconfirm \
+ cmake \
+ gcc \
+ pkgconf \
+ git \
+ clang \
+ llvm11 \
+ llvm11-libs \
+ postgresql-libs
+
+Dependency | Required For |
---|---|
cmake | Manages the build process in an operating system and in a compiler-independent manner |
git | Version control system |
gcc | Compiler tools required to build various Fuel indexer crates |
llvm11 | Compiler infrastructure for building Fuel indexer crate dependencies |
llvm11-libs | Compiler infrastructure libraries for building Fuel indexer crate dependencies |
pkgconf | System for configuring build dependency information |
postgresql-libs | Provides the essential shared libraries for any PostgreSQL client program or interface |
clang | Compiler required to build various Fuel indexer crates Unix-like OSes |
Here is a list of terms and their definitions in order to help users properly understand certain concepts about the Fuel indexer.
+asset
: a component that is used to create and operate an indexerexecutor
: an async task run by an indexerindex
/indices
: data produced by an indexerindexer service
: a service that runs one or more indexersindexer
: an abstraction that takes data from Fuel virtual machine and produces indicesThe Fuel indexer project can currently be used in a number of different ways:
+We'll describe these three different use cases below.
+The Fuel indexer provides functionality to make it easy to build and compile arbitrary indexers by using the forc index
CLI tool. Using forc index
, users can create, build, deploy, and remove indexers, as well as authenticate against a running indexer service, and check the status of running indexers.
Create, deploy, and check the status of a new indexer.
+forc index new fuel && \
+ cd fuel && forc index deploy --url http://indexer.fuel.network && \
+ forc index status --url http://indexer.fuel.network --auth $MY_TOKEN
+
+You can also start the Fuel indexer as a standalone service that connects to a Fuel node in order to monitor the Fuel blockchain for new blocks and transactions. To do so, run the requisite database migrations, adjust the configuration to connect to a Fuel node, and start the service.
+Create, deploy, and check the status of a new indexer.
+fuel-indexer run \
+ --network beta-5 \
+ --run-migrations \
+ --accept-sql-queries \
+ --replace-indexer
+
+Finally, you can run the Fuel indexer as part of a project that uses other components of the Fuel ecosystem, such as Sway. The convention for a Fuel project layout including an indexer is as follows:
+.
+├── contracts
+│ └── hello-contract
+│ ├── Forc.toml
+│ └── src
+│ └── main.sw
+├── frontend
+│ └── index.html
+└── indexer
+ └── hello-indexer
+ ├── Cargo.toml
+ ├── hello_indexer.manifest.yaml
+ ├── schema
+ │ └── hello_indexer.schema.graphql
+ └── src
+ └── lib.rs
+
+Every Fuel indexer project requires three components:
+ + +A manifest is a YAML configuration file that specifies various aspects of how an indexer should function: Where should the indexer start? Where should the indexer end? What contract should the indexer subscribe to?
+Below is a sample indexer manifest file
+namespace: fuellabs
+identifier: order_book_v1
+fuel_client: beta-5.fuel.network:80
+abi: path/to/my/contract-abi.json
+contract_id: "fuels0x39150017c9e38e5e280432d546fae345d6ce6d8fe4710162c2e3a95a6faff051"
+graphql_schema: path/to/my/schema.graphql
+start_block: 1564
+end_block: 310000
+module:
+ wasm: path/to/my/wasm_module.wasm
+
+namespace
Required.
+The namespace
is the topmost organizational level of an indexer. You can think of different namespaces as separate and distinct collections comprised of indexers. A namespace is unique to a given indexer operator -- i.e., indexer operators will not be able to support more than one namespace of the same name.
identifier
Required.
+The identifier
field is used to (quite literally) identify the given indexer. If a namespace describes a collection of indexers, then an identifier describes a unique indexer inside that collection. As an example, if a provided namespace
is "fuel"
and a provided identifier
is "index1"
, then the full identifier for the given indexer will be fuel.index1
.
fuel_client
Optional.
+The fuel_client
denotes the address (host, port combination) of the running Fuel client that you would like your indexer to index events from. In order to use this per-indexer fuel_client
option, the indexer service at which your indexer is deployed will have to run with the --indexer_net_config
option.
abi
Optional.
+The abi
option is used to provide a link to the Sway JSON application binary interface (ABI) that is generated when you build your Sway project. This generated ABI contains all types, type IDs, logged types, and message types used in your Sway contract.
contract_id
Optional.
+The contract_id
specifies the particular contract to which you would like an indexer to subscribe. Setting this field to an empty string will index events from any contract that is currently executing on the network. This field accepts either a single string, or a list of strings. The indexer will index events from all IDs if a list is passed.
++Important: Contract IDs are unique to the content of a contract. If you are subscribing to a certain contract and then the contract itself is changed or updated, you will need to change the
+contract_id
field of the manifest to the new ID. +Note: This parameter supports bothBech32
contract IDs and non-Bech32
contract IDs
graphql_schema
Required.
+The graphql_schema
field contains the file path pointing to the corresponding GraphQL schema for a given indexer. This schema file holds the structures of the data that will eventually reside in your database. You can read more about the format of the schema file here.
++Important: The objects defined in your GraphQL schema are called 'entities'. These entities are what will be eventually be stored in the database.
+
start_block
Optional.
+The start_block
field indicates the block height after which you'd like your indexer to start indexing events.
end_block
Optional.
+The end_block
field indicates the block height after which the indexer should stop indexing blocks.
++Important: If no
+end_block
is added the indexer will keep listening to new blocks indefinitely.
module
Required.
+The module
field contains a file path that points to code that will be run as an executor inside of the indexer.
resumable
Optional.
+The resumable
field contains a boolean value and specifies whether the indexer should synchronise with the latest block if it has fallen out of sync.
Indexer modules are compiled binaries that process data from the Fuel blockchain into entity types defined in your schema so that the data can be stored in a database. The Fuel indexer supports both WebAssembly (WASM) and native binaries; however, we strongly recommend using WASM binaries.
+This document describes the process of creating an indexer module.
+Prior to creating a module for an indexer, both the manifest and schema should be created. At compile time, information will be extracted from both of those assets and combined it with your defined logic to create handlers that save data to storage. Let's look at the following example of a module that will be compiled to WASM:
+use fuel_indexer_utils::prelude::*;
+
+#[indexer(manifest = "indexer.manifest.yaml")]
+mod indexer_mod {
+
+ // This `log_the_greeting` function will be called, when we find
+ // a `Greeting` in a block.
+ fn log_the_greeting(greeting: Greeting) {
+ info!("The greeting is: {greeting:?}");
+ }
+}
+The first line imports the prelude from fuel_indexer_utils
; this allows you to quickly bootstrap an indexer by using common types and traits. Then, we have a module decorated with the #[indexer]
macro.
Finally, we have an example handler function. You can define which functions handle different events by using the function parameters. If you add a function parameter of a certain type T
, the function will be triggered whenever that type is found as part of a block, transaction, or receipt.
Greeting
struct. When that function executes as part of a transaction, the logged struct will be included in the data that is processed from the Fuel blockchain. Your indexer module will see the struct and execute log_the_greeting
.++You can learn more about what data can be indexed and find example handlers in the Indexing Fuel Types and Indexing Custom Types sections.
+
To compile your indexer code to WASM, you'll first need to install the wasm32-unknown-unknown
target platform through rustup
, if you haven't done so already.
rustup add target wasm32-unknown-unknown
+
+After that, you can conveniently use the forc index
plugin to manager your indexers. Simply use forc index build
to build your indexer or checkout the forc index build
docs for more options.
++ +Notes on Web Assembly modules
+There are a few points that Fuel indexer users should know when using WASM:
++
+- +
+WASM modules are only used if the execution mode specified in your manifest file is
+wasm
.- +
+Developers should be aware of what things may not work off-the-shelf in a module: file I/O, thread spawning, and anything that depends on system libraries or makes system calls. This is due to the technological limitations of WASM as a whole; more information can be found here.
+- +
+As of this writing, there is a small bug in newly built Fuel indexer WASM modules that produces a WASM runtime error due to an errant upstream dependency. For now, a quick workaround requires the use of
+wasm-snip
to remove the errant symbols from the WASM module. More info can be found in the related script here.- +
+Users on Apple Silicon macOS systems may experience trouble when trying to build WASM modules due to its
+clang
binary not supporting WASM targets. If encountered, you can install a binary with better support from Homebrew (brew install llvm
) and instructrustc
to leverage it by setting the following environment variables:+
+- +
AR=/opt/homebrew/opt/llvm/bin/llvm-ar
- +
CC=/opt/homebrew/opt/llvm/bin/clang
The GraphQL schema is a required component of the Fuel indexer. When data is indexed into the database, the actual values that are persisted to the database will be values created using the data structures defined in the GraphQL schema.
+Below is a sample GraphQL schema for a Fuel indexer.
+type Metadata @entity(virtual: true) {
+ imageUrl: String!
+ data: Bytes
+}
+
+type Account @entity {
+ id: ID!
+ address: Address!
+ index: U64!
+ metadata: Metadata
+}
+
+type Wallet @entity {
+ id: ID!
+ name: String!
+ accounts: [Account!]!
+}
+
+For a complete list of all scalars that can be used in a Fuel indexer, please see the GraphQL Scalars section.
+Further, for a complete list of how Sway data types, GraphQL scalar types, and Fuel indexer database types map to each other, please see the Database Types section.
+Finally, for a more in-depth explanation on the schema being used above 👆🏽, please read the GraphQL section.
+ +Once data has been persisted into your storage backend, you can retrieve it by querying the GraphQL API server. By default, the API server can be reached at http://localhost:29987/api/graph/:namespace/:identifier
, where :namespace
and :identifier
are the values for the respective fields in your indexer's manifest. If you've changed the WEB_API_HOST
or WEB_API_PORT
values of your configuration, then you'll need to adjust the URL accordingly.
A basic query has the following form:
+query {
+ entity {
+ field_1
+ field_2
+ ...
+ }
+ ...
+}
+
+The entity
field corresponds to the name of an entity defined in your schema and the sub-fields are the fields defined on that entity type; entities and fields are stored in the database using the names defined in the schema, so make sure that your query uses those same names as well.
query {
+ block {
+ id
+ height
+ timestamp
+ }
+}
+
+We're requesting the ID, height, and timestamp for each block stored in the backend. If successful, the API server will return a response similar to the following:
+[
+ {
+ "height" : 1,
+ "id" : "f169a30cfcbf1eebd97a07b19de98e4b38a4367b03d1819943be41744339d38a",
+ "timestamp" : 1668710162
+ },
+ {
+ "height" : 2,
+ "id" : "a8c554758f78fe73054405d38099f5ad21a90c05206b5c6137424985c8fd10c7",
+ "timestamp" : 1668710163
+ },
+ {
+ "height" : 3,
+ "id" : "850ab156ddd9ac9502768f779936710fd3d792e9ea79bc0e4082de96450b5174",
+ "timestamp" : 1668710312
+ },
+ {
+ "height" : 4,
+ "id" : "19e19807c6988164b916a6877fe049d403d55a07324fa883cb7fa5cdb33438e2",
+ "timestamp" : 1668710313
+ },
+ {
+ "height" : 5,
+ "id" : "363af43cfd2a6d8af166ee46c15276b24b130fc6a89ce7b3c8737d29d6d0e1bb",
+ "timestamp" : 1668710314
+ }
+]
+
+The Fuel indexer supports foreign keys on entity types; thus, you can also ask for information about a referenced entity inside of your query. A nested query has the following general structure:
+query {
+ entityA {
+ field_A1
+ field_A2
+ referenced_entityB {
+ field_B1
+ field_B2
+ ...
+ }
+ ...
+ }
+ ...
+}
+
+Essentially, it's the same as the basic query example with an added sub-block to request information about the reference entity. The response from the API server will be returned in the same general structure as the query. Let's look at another example to illustrate how it works in practice.
+++Important: There is no limit to how deeply nested your entities and queries can be. However, every nested reference will add computation and latency to your query as the information will have to be retrieved from different tables in your storage backend. Please exercise caution in your entity design and try to minimize nesting as much as possible.
+
We'll start with the following example schema:
+type City @entity {
+ id: ID!
+ name: String!
+}
+
+type Library @entity {
+ id: ID!
+ name: String!
+ city: City!
+}
+
+type Book @entity {
+ id: ID!
+ title: String!
+ library: Library!
+}
+
+type Character @entity {
+ id: ID!
+ name: String!
+ book: Book!
+}
+
+This schema uses implicit foreign keys to reference other entities; for more information on implicit and explicit foreign keys, please refer to the Relationships section of the book. In this contrived example, we're storing information about characters that are found in books which are stored in libraries that can be found in cities. This will be the query that we use to retrieve the aforementioned data:
+query {
+ character {
+ name
+ book {
+ title
+ library {
+ name
+ city {
+ name
+ }
+ }
+ }
+ }
+}
+
+Let's assume that we've created an indexer for this data and the indexed data has been stored in the database. If we send the query, we'll get the following response:
+[
+ {
+ "name": "Lil Ind X",
+ "book": {
+ "title": "Fuel Indexer",
+ "library": {
+ "name": "Fuel Labs Library",
+ "city": {
+ "name": "Fuel City"
+ }
+ }
+ }
+ }
+]
+
+
+ Finally, let's combine nested entities, filtering, and pagination into one complete example.
+Sticking with the same block explorer example, let's say that we are looking for a particular transaction and its containing block, but we don't remember either of the hashes. All we know is that the total value of the transaction is greater than zero, it was sometime after the start of the beta-5
testnet, and it was included as part of the first fifty blocks. Additionally, we don't want to parse through all the results at once, so we only want to look at two records at a time. Finally, we think that it may have been on the more recent side, so we want to check them in reverse chronological order.
Putting all of that together, we get the following query:
+query {
+ transactions: tx(
+ order: { timestamp: desc },
+ filter: { value: { gt: 0 } },
+ first: 2,
+ offset: 0
+ ) {
+ id
+ hash
+ timestamp
+ value
+ block (
+ filter: {
+ height: { between: { min: 0, max: 50 } },
+ and: {
+ timestamp: { gt: 1678410000 }
+ }
+ }
+ ) {
+ id
+ hash
+ height
+ timestamp
+ }
+ }
+}
+
+The Fuel indexer's GraphQL API allows you to add filters on multiple entity fields and even nested entities! In the query above, we're asking for the two most recent transactions with a value greater than zero. Also, we're applying two filters to the nested block
entity by using the and
operator in order to help us narrow down the set of results.
The response returns the results in the expected format and includes additional information that informs us about how many total results satisfy the criteria.
+{
+ "data": {
+ "page_info": {
+ "has_next_page": true,
+ "limit": 2,
+ "offset": 0,
+ "pages": 2,
+ "total_count": 4
+ },
+ "transactions": [
+ {
+ "block": {
+ "hash": "f40297895086e66c0947c213dd29e90f596b860d10316ab806064608dd2580cd",
+ "height": 45,
+ "id": 7306026486395921000,
+ "timestamp": 1678486898
+ },
+ "hash": "85acfa181ebfa3b48c10d3181217918dd377b875d07dabc72d6d1081e4c52713",
+ "id": 3919319574514776000,
+ "timestamp": 1678486898,
+ "value": 10000000000
+ },
+ {
+ "block": {
+ "hash": "e3e0860a358c0d044669748cffff82b4b0073baaca53a128ddc8ce3757ae3988",
+ "height": 41,
+ "id": 7018409465212200000,
+ "timestamp": 1678486633
+ },
+ "hash": "42f3fd7ffa073975a0eca993044a867d8c87a8d39f5a88032a3b9aba213f6102",
+ "id": 7364622549171910000,
+ "timestamp": 1678486633,
+ "value": 10000000000
+ }
+ ]
+ }
+}
+
+
+ The Fuel indexer currently supports offset-based pagination in order to allow users to selectively request parts of a set of results.
+A paginated query can be made using three keywords:
+order
- sort order (required)first
- limit on number of results (required)offset
- the amount of records to skip before returning results (optional)query {
+ blocks: block(order: { height: asc }, first: 5) {
+ hash
+ height
+ timestamp
+ }
+}
+
+In this query, we're requesting details about the first five blocks in ascending order of block height. You can also see that we've added a blocks
alias; this is completely optional and all it does is change the key for the list of results in the JSON response.
With this query, we receive the following response:
+{
+ "data": {
+ "blocks": [
+ {
+ "hash": "d77632f85669dd44737abf36b32f479ae518e07a9174c8571377ebb81563bb9a",
+ "height": 1,
+ "timestamp": 1678483351
+ },
+ {
+ "hash": "7cb9542b624d88b3d66c6c9a1835f66fecba8892a87ffab9c17251c456ca5dcd",
+ "height": 2,
+ "timestamp": 1678483471
+ },
+ {
+ "hash": "24f9611115f7ecb4a393751933a9f89329812cf08bdbe483c071b3401d06c8d6",
+ "height": 3,
+ "timestamp": 1678483591
+ },
+ {
+ "hash": "616566afdc141ecd2b60fdc56aae4f3d04b3f6db9e65a3c21d0105a08cc1b349",
+ "height": 4,
+ "timestamp": 1678483711
+ },
+ {
+ "hash": "1dca838d492f29b7a3afa7755ac8741c99db992da47673cd27be86f9b0620118",
+ "height": 5,
+ "timestamp": 1678483831
+ }
+ ],
+ "page_info": {
+ "has_next_page": true,
+ "limit": 5,
+ "offset": 0,
+ "pages": 80,
+ "total_count": 400
+ }
+ }
+}
+
+As you can see, we get the requested amount of blocks and the corresponding fields. However, there's also a page_info
object included in the response. This object tells us if there's another page available to request along with information that we can use to construct our next response. To get the next page, we'll add an offset
to our original query:
query {
+ blocks: block(
+ order: { height: asc },
+ first: 5,
+ offset: 5
+ ) {
+ hash
+ height
+ timestamp
+ }
+}
+
+The response contains the next five blocks after our requested offset, and the page_info
object lets us know what offset we used in the query.
{
+ "data": {
+ "blocks": [
+ {
+ "hash": "c93ffc9178d526a836d707137de08b0f743fabce79ecec77c419bfb7e6be8863",
+ "height": 6,
+ "timestamp": 1678483951
+ },
+ {
+ "hash": "4f0c81a42c86c718c0ae90ba838d6f1bdfc9a757cbf07c946fb3280b44257b46",
+ "height": 7,
+ "timestamp": 1678484071
+ },
+ {
+ "hash": "659b486cc2c3bd1133df9245645648b6a09b35e16c7f71bb05449cea0e83611c",
+ "height": 8,
+ "timestamp": 1678484191
+ },
+ {
+ "hash": "4bf61bd8f88b7fb40e842a6497d686bc2f63839ec3ca1eedb4e81a0935adaeb6",
+ "height": 9,
+ "timestamp": 1678484311
+ },
+ {
+ "hash": "b090634788ddd0461cba4d0833a3f15b8e2f51e672fb1527fc8c78cd8f80dc1a",
+ "height": 10,
+ "timestamp": 1678484431
+ }
+ ],
+ "page_info": {
+ "has_next_page": true,
+ "limit": 5,
+ "offset": 5,
+ "pages": 80,
+ "total_count": 400
+ }
+ }
+}
+
+
+ The Fuel indexer's GraphQL Playground is an interactive, in-browser GraphQL IDE that allows developers to easily explore and test the indexer's GraphQL API server. You can read more about the GraphQL playground in general here.
+Every public indexer can access the GraphQL playground of the Fuel indexer node on which the given indexer runs, so users and developers can get to querying their data right away.
+To use the GraphQL playground to explore your indices, start your indexer service, then open the following URL in your browser - where namespace
and identifier
correspond to the namespace and identifier of the index that you'd like to explore.
http://localhost:29987/api/playground/:namespace/:identifier
+
+
+ The Fuel indexer currently supports the following search and filtering operations:
+Additionally, you can combine these operations using the and
or or
keywords, and invert operations by using the not
keyword.
++You should practice sensible database design when filtering records. Apply database indices to the underlying columns in order to make search operations more efficient; however, be advised that an overuse of database indices will lead to degraded performance.
+
You can query for a particular instance of an object by passing an ID value to with the id
key. For example, let's say that you are storing blocks and you want details about a certain block, you would pass the corresponding ID:
query {
+ block(id: 4121419699470229811) {
+ id
+ hash
+ height
+ producer
+ }
+}
+
+{
+ "data": [
+ {
+ "hash": "aff5eb785f2d24ae62858fa673296e957abea518858e2f08bb47df2dbb9c8ca1",
+ "height": 8209,
+ "id": 4121419699470229811,
+ "producer": "f65d6448a273b531ee942c133bb91a6f904c7d7f3104cdaf6b9f7f50d3518871"
+ }
+ ]
+}
+
+++Note: Remember that IDs currently must be of type
+u64
, and as such, the ID selection operation will only allow for au64
value. We're open to changing this in the future.
You can store null values in your records if the corresponding entity fields are configured to allow for it. You can exclude records that contain null values in a particular column or set of columns by using the has
operator inside of a filter
object.
query {
+ block(filter: { has: [producer] } ) {
+ id
+ hash
+ height
+ producer
+ }
+}
+
+{
+ "data": [
+ {
+ "hash": "d0d663e0bf499aca19d3ecb9b0b291234dc3769d2b46512016eca7244ca0ef22",
+ "height": 411,
+ "id": 3775485677453992400,
+ "producer": "f65d6448a273b531ee942c133bb91a6f904c7d7f3104cdaf6b9f7f50d3518871"
+ },
+ {
+ "hash": "7ff79bf3793143c557225f37b7e7d8a2b9d2e544b839d62cc367b8c5b079d478",
+ "height": 412,
+ "id": 3919088689958184000,
+ "producer": "f65d6448a273b531ee942c133bb91a6f904c7d7f3104cdaf6b9f7f50d3518871"
+ }
+ ]
+}
+
+Additionally, you can exclude records in which a particular column's value does not contain any elements in a given set by using the in
operator as part of a filter
object.
query {
+ block(filter: { height: { in: [1, 2, 3] } } ) {
+ id
+ hash
+ height
+ }
+}
+
+{
+ "data": [
+ {
+ "hash": "d77632f85669dd44737abf36b32f479ae518e07a9174c8571377ebb81563bb9a",
+ "height": 1,
+ "id": 3618468784755926500
+ },
+ {
+ "hash": "7cb9542b624d88b3d66c6c9a1835f66fecba8892a87ffab9c17251c456ca5dcd",
+ "height": 2,
+ "id": 4122538829619016000
+ },
+ {
+ "hash": "24f9611115f7ecb4a393751933a9f89329812cf08bdbe483c071b3401d06c8d6",
+ "height": 3,
+ "id": 3762867646901937000
+ }
+ ]
+}
+
+Finally, you can filter records by comparing the values of certain fields to a particular value of your choosing by using one of the comparison operators inside of a filter
object.
You can do simple value comparisons using any of the following operators:
+equals | equals |
greater than | gt |
greater than or equal to | gte |
less than | lt |
less than or equal to | lte |
Here's an example:
+query {
+ block(filter: { height: { lte: 5 } } ) {
+ id
+ hash
+ height
+ producer
+ }
+}
+
+{
+ "data": [
+ {
+ "hash": "d77632f85669dd44737abf36b32f479ae518e07a9174c8571377ebb81563bb9a",
+ "height": 1,
+ "id": 3618468784755926500,
+ "producer": "f65d6448a273b531ee942c133bb91a6f904c7d7f3104cdaf6b9f7f50d3518871"
+ },
+ {
+ "hash": "7cb9542b624d88b3d66c6c9a1835f66fecba8892a87ffab9c17251c456ca5dcd",
+ "height": 2,
+ "id": 4122538829619016000,
+ "producer": "f65d6448a273b531ee942c133bb91a6f904c7d7f3104cdaf6b9f7f50d3518871"
+ },
+ {
+ "hash": "24f9611115f7ecb4a393751933a9f89329812cf08bdbe483c071b3401d06c8d6",
+ "height": 3,
+ "id": 3762867646901937000,
+ "producer": "f65d6448a273b531ee942c133bb91a6f904c7d7f3104cdaf6b9f7f50d3518871"
+ },
+ {
+ "hash": "616566afdc141ecd2b60fdc56aae4f3d04b3f6db9e65a3c21d0105a08cc1b349",
+ "height": 4,
+ "id": 3833467323683451000,
+ "producer": "f65d6448a273b531ee942c133bb91a6f904c7d7f3104cdaf6b9f7f50d3518871"
+ },
+ {
+ "hash": "1dca838d492f29b7a3afa7755ac8741c99db992da47673cd27be86f9b0620118",
+ "height": 5,
+ "id": 3991987200693004000,
+ "producer": "f65d6448a273b531ee942c133bb91a6f904c7d7f3104cdaf6b9f7f50d3518871"
+ }
+ ]
+}
+
+You can also filter for records that are contained in the range of two values by using the between
operator. To do so, you'd set the lower bound using the min
keyword and the upper bound by using max
.
query {
+ block(filter: { height: { between: { min: 101, max: 103 } } } ) {
+ id
+ hash
+ height
+ producer
+ }
+}
+
+{
+ "data": [
+ {
+ "hash": "3b85fbed2d933d0334d54776612a5af72a513e875d06fa9152f6d41d0e50e417",
+ "height": 101,
+ "id": 3763145849079675000,
+ "producer": "f65d6448a273b531ee942c133bb91a6f904c7d7f3104cdaf6b9f7f50d3518871"
+ },
+ {
+ "hash": "deea78034c2f0fcd7ef2d2d2d203d19fcd63f1b1846fac089c51c2aa7b5c8149",
+ "height": 102,
+ "id": 7365137137742930000,
+ "producer": "f65d6448a273b531ee942c133bb91a6f904c7d7f3104cdaf6b9f7f50d3518871"
+ },
+ {
+ "hash": "a405d5688fdf41817868361217a09812349cc6fe0fe2bf9329fcd23e338e9444",
+ "height": 103,
+ "id": 7292000934927820000,
+ "producer": "f65d6448a273b531ee942c133bb91a6f904c7d7f3104cdaf6b9f7f50d3518871"
+ }
+ ]
+}
+
+As previously stated, you can combine or invert operations to filter for your desired results even further.
+Let's look at an example query in which we combine two filters together.
+query {
+ block(filter: {
+ producer: { equals: "f65d6448a273b531ee942c133bb91a6f904c7d7f3104cdaf6b9f7f50d3518871" },
+ and: { height: { lt: 4 } }
+ } ) {
+ id
+ hash
+ height
+ producer
+ }
+}
+
+{
+ "data": [
+ {
+ "hash": "d77632f85669dd44737abf36b32f479ae518e07a9174c8571377ebb81563bb9a",
+ "height": 1,
+ "id": 3618468784755926500,
+ "producer": "f65d6448a273b531ee942c133bb91a6f904c7d7f3104cdaf6b9f7f50d3518871"
+ },
+ {
+ "hash": "7cb9542b624d88b3d66c6c9a1835f66fecba8892a87ffab9c17251c456ca5dcd",
+ "height": 2,
+ "id": 4122538829619016000,
+ "producer": "f65d6448a273b531ee942c133bb91a6f904c7d7f3104cdaf6b9f7f50d3518871"
+ },
+ {
+ "hash": "24f9611115f7ecb4a393751933a9f89329812cf08bdbe483c071b3401d06c8d6",
+ "height": 3,
+ "id": 3762867646901937000,
+ "producer": "f65d6448a273b531ee942c133bb91a6f904c7d7f3104cdaf6b9f7f50d3518871"
+ }
+ ]
+}
+
+You can also use the not
operator in order to invert the operation of a particular filter. For example, the following query returns contracts that we've seen on the network.
query {
+ contract {
+ id
+ }
+}
+
+{
+ "data": [
+ {
+ "id": "1072ca8fcab43048a5b31c1ea204748c2cb5acca6b90f3b1a02ef7a2d92386d9"
+ },
+ {
+ "id": "9b8b258e0d64b9e8a022e3f38a751ad5a1b36e4dfdcc25a6fb8308e044250b8c"
+ },
+ {
+ "id": "0000000000000000000000000000000000000000000000000000000000000000"
+ },
+ {
+ "id": "8fe8ce43603c1a48274aac7532da56707901d9606a2b05de801993f48ea6bfe7"
+ }
+ ]
+}
+
+Let's ignore the base asset contract by inverting the in
operator:
query {
+ contract(filter: {not: { id: { equals: "0000000000000000000000000000000000000000000000000000000000000000"}}}) {
+ id
+ }
+}
+
+{
+ "data": [
+ {
+ "id": "1072ca8fcab43048a5b31c1ea204748c2cb5acca6b90f3b1a02ef7a2d92386d9"
+ },
+ {
+ "id": "9b8b258e0d64b9e8a022e3f38a751ad5a1b36e4dfdcc25a6fb8308e044250b8c"
+ },
+ {
+ "id": "8fe8ce43603c1a48274aac7532da56707901d9606a2b05de801993f48ea6bfe7"
+ }
+ ]
+}
+
+
+ \n ### [➡️ Read the Quickstart! ➡️](https://docs.fuel.network/docs/indexer/getting-started/quickstart/) ## What is the Fuel indexer? The Fuel indexer is a standalone service that can be used to index various components of the blockchain. These indexable components include blocks, transactions, receipts, and state within the Fuel network, allowing for high-performance read-only access to the blockchain for advanced dApp use-cases. > TLDR: It's Infrastructure as a service (IaaS) for Web3 dApp backends. ## Install Fuel's indexer supports Linux (x64 & arm64) and macOS (x64 & Apple Silicon). > If you don't want to deal with dependency issues we recommend just using Fuel's indexer with Docker, [via the included docker-compose file](https://github.com/FuelLabs/fuel-indexer/blob/develop/scripts/docker-compose.yaml). Install Fuel's toolchain manager - fuelup. ```bash\ncurl --proto '=https' --tlsv1.2 -sSf https://install.fuel.network/fuelup-init.sh | sh The fuel-indexer and forc-index binaries should now be available in your $PATH","breadcrumbs":"forc index » forc index","id":"125","title":"forc index"},"126":{"body":"For development, users will primarily use the forc index command line utility made available after installing fuelup. forc index --help forc index 0.0.0\nFuel Indexer Orchestrator USAGE: forc-index
\n ### [➡️ Read the Quickstart! ➡️](https://docs.fuel.network/docs/indexer/getting-started/quickstart/) ## What is the Fuel indexer? The Fuel indexer is a standalone service that can be used to index various components of the blockchain. These indexable components include blocks, transactions, receipts, and state within the Fuel network, allowing for high-performance read-only access to the blockchain for advanced dApp use-cases. > TLDR: It's Infrastructure as a service (IaaS) for Web3 dApp backends. ## Install Fuel's indexer supports Linux (x64 & arm64) and macOS (x64 & Apple Silicon). > If you don't want to deal with dependency issues we recommend just using Fuel's indexer with Docker, [via the included docker-compose file](https://github.com/FuelLabs/fuel-indexer/blob/develop/scripts/docker-compose.yaml). Install Fuel's toolchain manager - fuelup. ```bash\ncurl --proto '=https' --tlsv1.2 -sSf https://install.fuel.network/fuelup-init.sh | sh The fuel-indexer and forc-index binaries should now be available in your $PATH","breadcrumbs":"forc index » forc index","id":"125","title":"forc index"},"126":{"body":"For development, users will primarily use the forc index command line utility made available after installing fuelup. forc index --help forc index 0.0.0\nFuel Indexer Orchestrator USAGE: forc-index