The GraphQL API is the primary interface through which external clients interact with fuel-core nodes. It provides a comprehensive query language for reading blockchain data, submitting transactions, and subscribing to real-time updates. This document covers the API service architecture, schema design, resolver implementation, off-chain indexing, and the client SDK.
For information about the underlying storage system that the GraphQL API queries, see Storage System. For details about transaction lifecycle and management, see Transaction Management.
The GraphQL API serves as the main external interface for fuel-core, exposing blockchain data and operations through HTTP and WebSocket endpoints. The API is built on the async-graphql framework (v7.x) and consists of three main components:
fuel_core_services::ServiceRunner<GraphqlService>): Axum-based HTTP/WebSocket server at :4000/v1/graphql handling GraphQL requests via CoreSchemafuel_core_graphql_api::worker_service::Task): Background indexer processing SharedImportResult events to maintain off-chain indexes in Database<OffChain>FuelClient in crates/client): Type-safe Rust client with FailoverTransport for high availability
Sources: crates/fuel-core/src/graphql_api/api_service.rs99-129 crates/fuel-core/src/graphql_api/worker_service.rs115-157 crates/client/src/client.rs282-291
Sources: crates/fuel-core/src/graphql_api/api_service.rs99-374 crates/fuel-core/src/graphql_api/ports.rs87-328 crates/fuel-core/src/schema/tx.rs119-196 crates/fuel-core/src/graphql_api/worker_service.rs115-157 crates/fuel-core/src/service/adapters/graphql_api.rs1-312
The GraphQL service is implemented in crates/fuel-core/src/graphql_api/api_service.rs and exposes a single endpoint at /v1/graphql that handles both HTTP and WebSocket connections. The service is built on top of axum for HTTP routing and async-graphql for GraphQL execution.
The ServiceConfig struct in crates/fuel-core/src/graphql_api.rs37-62 defines the API behavior. It is passed to new_service() in crates/fuel-core/src/graphql_api/api_service.rs236-252 during service initialization:
| Configuration Field | Type | Purpose |
|---|---|---|
addr | std::net::SocketAddr | Binding address for the API server (default: 127.0.0.1:4000) |
number_of_threads | usize | Threads for AsyncProcessor query execution pool |
database_batch_size | usize | Batch size for ReadDatabase iterator operations |
block_subscriptions_queue | usize | Queue size for block subscriptions |
max_queries_depth | usize | Maximum depth of nested queries (DoS protection) |
max_queries_complexity | usize | Maximum query complexity score (DoS protection) |
max_queries_recursive_depth | usize | Maximum recursion depth (DoS protection) |
max_queries_resolver_recursive_depth | usize | Maximum resolver recursion depth |
max_queries_directives | usize | Maximum number of directives per query |
max_concurrent_queries | usize | Maximum concurrent queries allowed |
request_body_bytes_limit | usize | Maximum request body size |
required_fuel_block_height_tolerance | u32 | Blocks behind before considered out of sync |
required_fuel_block_height_timeout | Duration | Timeout for block height requirements |
query_log_threshold_time | Duration | Threshold for logging slow queries |
api_request_timeout | Duration | Request timeout duration |
assemble_tx_dry_run_limit | usize | Max dry runs during transaction assembly |
assemble_tx_estimate_predicates_limit | usize | Max predicate estimations during assembly |
costs | Costs | Configurable complexity cost parameters |
The broader Config struct in crates/fuel-core/src/graphql_api.rs22-34 wraps ServiceConfig and adds:
utxo_validation: bool - Enforces UTXO validation in queriesdebug: bool - Enables produceBlocks mutation and debugger endpointshistorical_execution: bool - Enables ReadViewAt for historical queriesexpensive_subscriptions: bool - Enables newBlocks subscriptionmax_tx, max_gas, max_size: TxPool limits exposed in schemachain_name: String - Chain identifier in ChainInfo queriesSources: crates/fuel-core/src/graphql_api.rs22-34 crates/fuel-core/src/graphql_api/api_service.rs236-374
The client SDK in crates/client/src/client.rs327-340 automatically normalizes URLs to ensure consistent endpoint paths:
Sources: crates/client/src/client.rs327-340
The GraphQL schema is defined in SDL format at crates/client/assets/schema.sdl The schema follows a clear organization:
The CoreSchema type is defined as Schema<QueryRoot, MutationRoot, SubscriptionRoot> in crates/fuel-core/src/schema/schema.rs Each root type aggregates field resolvers:
The #[graphql(flatten)] attribute merges fields from each query struct into the root type. Resolvers are implemented as methods on the query structs (e.g., TxQuery::transaction(), BlockQuery::block()).
Sources: crates/fuel-core/src/schema/schema.rs crates/fuel-core/src/schema/tx.rs119-196 crates/fuel-core/src/schema/block.rs crates/client/assets/schema.sdl945-1172
The schema uses unions and interfaces extensively:
| Union/Interface | Variants | Purpose |
|---|---|---|
TransactionStatus | SubmittedStatus, SuccessStatus, PreconfirmationSuccessStatus, FailureStatus, PreconfirmationFailureStatus, SqueezedOutStatus | Transaction lifecycle states |
Input | InputCoin, InputContract, InputMessage | Transaction input types |
Output | CoinOutput, ContractOutput, ChangeOutput, VariableOutput, ContractCreated | Transaction output types |
CoinType | Coin, MessageCoin | UTXO types |
Consensus | Genesis, PoAConsensus | Block consensus types |
Sources: crates/client/assets/schema.sdl145-227 crates/client/assets/schema.sdl636-828
Queries are implemented as GraphQL resolver methods in the TxQuery, BlockQuery, ChainQuery, and other query structures.
The TxQuery struct in crates/fuel-core/src/schema/tx.rs119-196 provides transaction-related queries:
Sources: crates/fuel-core/src/schema/tx.rs119-615 crates/fuel-core/src/graphql_api/ports.rs250-320
The API uses async-graphql's built-in complexity scoring configured via SchemaBuilder methods in crates/fuel-core/src/graphql_api/api_service.rs289-315 Complexity is calculated using the #[graphql(complexity = "...")] attribute on each field:
Each field's complexity is defined by accessing the Costs struct via query_costs() static function in crates/fuel-core/src/graphql_api.rs123-125:
| Operation | Cost Expression | Defined In |
|---|---|---|
transaction | query_costs().tx_get | schema/tx.rs200-202 |
transactions | query_costs().storage_iterator + (first * (query_costs().storage_read + child_complexity)) | schema/tx.rs226-235 |
dryRun | query_costs().dry_run * txs.len() | schema/tx.rs294-296 |
assembleTx | query_costs().assemble_tx | schema/tx.rs371-373 |
coinsToSpend | query_costs().coins_to_spend | schema/coins.rs |
balance | query_costs().balance_query | schema/balance.rs |
The Costs struct in crates/fuel-core/src/graphql_api.rs64-87 stores the base cost values. QUERY_COSTS is a static OnceLock<Costs> initialized by initialize_query_costs() in crates/fuel-core/src/graphql_api.rs140-155
Sources: crates/fuel-core/src/graphql_api/api_service.rs289-315 crates/fuel-core/src/graphql_api.rs64-155 crates/fuel-core/src/schema/tx.rs200-235
Mutations handle state-changing operations, primarily transaction submission and block production.
The transaction submission mutation in crates/fuel-core/src/schema/tx.rs647-682 handles transaction insertion into the TxPool:
Sources: crates/fuel-core/src/schema/tx.rs647-682 crates/fuel-core/src/service/adapters/graphql_api.rs104-124
The assembleTx query in crates/fuel-core/src/schema/tx.rs371-515 constructs complete transactions by adding required inputs and outputs:
Sources: crates/fuel-core/src/schema/tx.rs371-515 crates/fuel-core/src/schema/tx/assemble_tx.rs
Subscriptions enable real-time streaming of blockchain events over WebSocket connections.
The subscription resolvers in crates/fuel-core/src/schema/tx.rs678-852 provide real-time transaction status updates via WebSocket:
The subscription resolvers call ctx.data_unchecked::<Box<dyn TxStatusManager>>().tx_update_subscribe(id) to get a BoxStream<TxStatusMessage>. The TxStatusManagerAdapter in crates/fuel-core/src/service/adapters/graphql_api.rs84-101 implements the port by delegating to TxStatusManagerSharedData::subscribe().
Sources: crates/fuel-core/src/schema/tx.rs678-852 crates/fuel-core/src/graphql_api/ports.rs258-270 crates/fuel-core/src/service/adapters/graphql_api.rs84-101
The alpha__new_blocks subscription in crates/fuel-core/src/schema/block.rs streams serialized ImportResult data:
The worker service subscribes to block imports via BlockImporterAdapter::events_shared_result() and broadcasts serialized results to all new_blocks subscription clients.
Sources: crates/fuel-core/src/graphql_api/worker_service.rs209-219 crates/client/src/client.rs1044-1066
The GraphQL API uses a port/adapter pattern defined in crates/fuel-core/src/graphql_api/ports.rs to decouple resolvers from concrete service implementations:
Port traits are defined in crates/fuel-core/src/graphql_api/ports.rs They use #[async_trait::async_trait] for async methods:
| Port Trait | Key Method Signatures | Purpose |
|---|---|---|
OnChainDatabase | Compound trait: DatabaseBlocks + DatabaseMessages + StorageInspect<Coins> + StorageInspect<StateTransitionBytecodeVersions> + DatabaseContracts + DatabaseChain + DatabaseMessageProof | Read consensus-critical on-chain data |
DatabaseBlocks | fn transaction(&self, tx_id: &TxId) -> StorageResult<Transaction>fn block(&self, height: &BlockHeight) -> StorageResult<CompressedBlock>fn blocks(&self, height, direction) -> BoxedIter<StorageResult<CompressedBlock>> | Block and transaction queries |
OffChainDatabase | fn tx_status(&self, tx_id: &TxId) -> StorageResult<TransactionExecutionStatus>fn balance(&self, owner, asset_id, base_asset_id) -> StorageResult<TotalBalanceAmount>fn owned_coins_ids(&self, owner, start_coin, direction) -> BoxedIter<StorageResult<UtxoId>> | Read indexed off-chain data |
TxPoolPort | async fn transaction(&self, id: TxId) -> anyhow::Result<Option<Transaction>>async fn insert(&self, txs: Transaction) -> anyhow::Result<()>fn latest_pool_stats(&self) -> TxPoolStats | Access pending transactions in mempool |
BlockProducerPort | async fn dry_run_txs(&self, transactions, height, time, utxo_validation, gas_price, record_storage_reads) -> anyhow::Result<DryRunResult>async fn storage_read_replay(&self, height: BlockHeight) -> anyhow::Result<Vec<StorageReadReplayEvent>> | Execute transactions without commitment |
TxStatusManager | async fn status(&self, tx_id: TxId) -> anyhow::Result<Option<TransactionStatus>>async fn tx_update_subscribe(&self, tx_id: TxId) -> anyhow::Result<BoxStream<TxStatusMessage>> | Track transaction lifecycle and provide status subscriptions |
DatabaseMessageProof | fn block_history_proof(&self, message_block_height, commit_block_height) -> StorageResult<MerkleProof> | Generate Merkle proofs for cross-chain messages |
Sources: crates/fuel-core/src/graphql_api/ports.rs162-328
Adapters bridge the port traits to concrete service implementations:
Sources: crates/fuel-core/src/graphql_api/ports.rs1-428 crates/fuel-core/src/service/adapters/graphql_api.rs1-312
The worker service in crates/fuel-core/src/graphql_api/worker_service.rs is a background task that maintains off-chain indexes by processing imported blocks.
The worker implements RunnableService and RunnableTask from fuel_core_services. The InitializeTask handles genesis/recovery by processing historical blocks from block_importer.block_event_at_height(BlockAt::Genesis) up to the latest height.
Sources: crates/fuel-core/src/graphql_api/worker_service.rs115-228 crates/fuel-core/src/graphql_api/worker_service.rs469-592
The worker processes ExecutorEvent variants in crates/fuel-core/src/graphql_api/worker_service.rs230-351 to maintain indexes:
The worker checks self.balances_indexation_enabled, self.coins_to_spend_indexation_enabled, and self.asset_metadata_indexation_enabled flags (set from Database<OffChain>::balances_indexation_enabled() etc.) to conditionally update optional indexes. Each indexation module in crates/fuel-core/src/graphql_api/indexation/ contains the logic for updating its respective tables.
Sources: crates/fuel-core/src/graphql_api/worker_service.rs230-351 crates/fuel-core/src/graphql_api/indexation/balances.rs crates/fuel-core/src/graphql_api/indexation/coins_to_spend.rs crates/fuel-core/src/graphql_api/indexation/assets.rs
The worker in crates/fuel-core/src/graphql_api/worker_service.rs164-228 coordinates with the transaction status manager to provide real-time updates:
The worker implements the worker::TxStatusCompletion trait, which calls TxStatusManagerAdapter::send_complete() for each transaction in the imported block.
Sources: crates/fuel-core/src/graphql_api/worker_service.rs164-228 crates/fuel-core/src/service/adapters/graphql_api.rs208-218
The FuelClient struct in crates/client/src/client.rs282-290 provides a type-safe Rust client for interacting with the GraphQL API.
The normalize_url() function in crates/client/src/client.rs327-340 ensures all URLs point to /v1/graphql and have a scheme. The FailoverTransport struct in crates/client/src/client/transport.rs implements GraphqlTransport trait and automatically switches to backup URLs on errors.
Sources: crates/client/src/client.rs282-433 crates/client/src/client/transport.rs
The client supports automatic consistency via the required_fuel_block_height extension mechanism. Each response from the server includes extensions.current_fuel_block_height, which the client caches and sends in subsequent requests.
Code entities:
ConsistencyPolicy enum in crates/client/src/client.rs228-257:
Auto { height: Option<BlockHeight> }: Automatically tracks latest seen height via update_from_response()Manual { height: Option<BlockHeight> }: User sets height via FuelClient::with_required_fuel_block_height()RequiredFuelBlockHeightExtension in crates/fuel-core/src/graphql_api/extensions/required_fuel_block_height.rs: Validates required_fuel_block_height extension value and waits for node to reach that heightFuelClient::process_response() in crates/client/src/client.rs493-532: Extracts current_fuel_block_height from response extensions and updates ConsistencyPolicySources: crates/client/src/client.rs228-257 crates/client/src/client.rs493-532 crates/fuel-core/src/graphql_api/extensions/required_fuel_block_height.rs
The FuelClient provides type-safe methods generated from the GraphQL schema using cynic crate. All methods are defined in crates/client/src/client.rs601-1387:
| Method Category | Method Signatures | GraphQL Query | Return Type |
|---|---|---|---|
| Health & Info | async fn health(&self) -> io::Result<bool>async fn node_info(&self) -> io::Result<NodeInfo>async fn chain_info(&self) -> io::Result<ChainInfo> | health, nodeInfo, chain | Health status, node version, chain metadata |
| Gas Price | async fn latest_gas_price(&self) -> io::Result<LatestGasPrice>async fn estimate_gas_price(&self, block_horizon: u32) -> io::Result<EstimateGasPrice> | latestGasPrice, estimateGasPrice | Current and estimated gas prices |
| Blocks | async fn block(&self, id: &Bytes32) -> io::Result<Option<Block>>async fn block_by_height(&self, height: BlockHeight) -> io::Result<Option<Block>>fn blocks(&self, request: PaginationRequest<String>) -> impl Stream<Item = io::Result<Block>> | block(id), block(height), blocks(first, after) | Block data with transactions and consensus |
| Transactions | async fn transaction(&self, id: &TxId) -> io::Result<Option<TransactionResponse>>fn transactions(&self, request: PaginationRequest<String>) -> impl Stream<Item = io::Result<TransactionResponse>>fn transactions_by_owner(&self, owner: &Address, request: PaginationRequest<String>) -> impl Stream<Item = io::Result<TransactionResponse>> | transaction(id), transactions(...), transactionsByOwner(...) | Transaction data with status and receipts |
| Transaction Submission | async fn submit(&self, tx: &Transaction) -> io::Result<TxId>async fn submit_and_await_commit(&self, tx: &Transaction) -> io::Result<TransactionStatus> | submit(tx), submitAndAwait(tx) subscription | Transaction ID or finalized status |
| Dry Run | async fn dry_run(&self, txs: &[Transaction]) -> io::Result<Vec<TxExecutionStatus>>async fn dry_run_opt(&self, txs: &[Transaction], utxo_validation: Option<bool>, gas_price: Option<u64>) -> io::Result<Vec<TxExecutionStatus>> | dryRun(txs, utxoValidation) | Execution results without state changes |
| Transaction Assembly | async fn assemble_tx(&self, tx: &Transaction, block_horizon: u32, required_balances: &[AssetRequirement]) -> io::Result<AssembleTransactionResult> | assembleTx(tx, blockHorizon, requiredBalances, ...) | Complete transaction with selected UTXOs |
| Coins & Balances | async fn coins(&self, filter: CoinFilterInput, pagination: PaginationRequest<String>) -> io::Result<PaginatedResult<Coin, String>>async fn balances(&self, filter: CoinFilterInput, pagination: PaginationRequest<String>) -> io::Result<PaginatedResult<Balance, String>>async fn coins_to_spend(&self, query_per_asset: Vec<SpendQueryElementInput>) -> io::Result<Vec<Vec<CoinType>>> | coins(filter), balances(filter), coinsToSpend(queryPerAsset) | UTXO and balance data by owner/asset |
| Messages | async fn messages(&self, owner: Option<&Address>, pagination: PaginationRequest<String>) -> io::Result<PaginatedResult<Message, String>>async fn message_proof(&self, tx_id: &TxId, nonce: &Nonce, commit_block_id: Option<&Bytes32>, commit_block_height: Option<BlockHeight>) -> io::Result<Option<MessageProof>> | messages(owner), messageProof(...) | L1->L2 messages and Merkle proofs |
| Contracts | async fn contract(&self, id: &ContractId) -> io::Result<Option<Contract>>async fn contract_balance(&self, id: &ContractId, asset: &AssetId) -> io::Result<u64> | contract(id), contractBalance(contract, asset) | Contract bytecode and asset balances |
All methods use cynic::QueryBuilder to generate type-safe GraphQL queries from schema definitions in crates/client/src/client/schema/
Sources: crates/client/src/client.rs601-1387 crates/client/src/client/schema/
WebSocket-based subscriptions require the subscriptions feature:
Sources: crates/client/src/client.rs886-996
The GraphQL API in crates/fuel-core/src/graphql_api/api_service.rs implements multiple layers of protection against denial-of-service attacks:
The limits are configured via ServiceConfig and enforced by async-graphql's built-in validation and the TimeoutLayer middleware.
Sources: crates/fuel-core/src/graphql_api/api_service.rs196-275 crates/fuel-core/src/graphql_api.rs37-62
| Limit Type | Configuration Field | Implementation | Purpose |
|---|---|---|---|
| Request Body Size | request_body_bytes_limit | DefaultBodyLimit layer | Prevents large payload attacks |
| Pagination Limits | first/last parameters | GraphQL connection args | Limits result set size per page |
| Database Batch Size | database_batch_size in ServiceConfig | ReadView::batch_size | Controls database fetch size |
| Concurrent Queries | max_concurrent_queries | ConcurrencyLimitLayer | Limits simultaneous query execution |
Sources: crates/fuel-core/src/graphql_api/api_service.rs196-275 crates/fuel-core/src/graphql_api.rs37-62
Slow queries are automatically logged in crates/fuel-core/src/graphql_api/extensions/validation.rs for analysis:
The ValidationExtension middleware tracks query execution time and logs queries exceeding the configured query_log_threshold_time threshold.
Sources: crates/fuel-core/src/graphql_api/extensions/validation.rs
All list queries use cursor-based pagination to prevent unbounded result sets:
Sources: crates/client/src/client/pagination.rs
The GraphQL API uses a layered view system in crates/fuel-core/src/graphql_api/database.rs to access both on-chain and off-chain data:
This architecture enables:
--historical-execution flag)Arc cloningSources: crates/fuel-core/src/graphql_api/database.rs11-165
The GraphQL API includes metadata in response extensions:
| Extension Field | Type | Purpose |
|---|---|---|
current_fuel_block_height | BlockHeight | Latest block height at time of query |
current_stf_version | StateTransitionBytecodeVersion | Active STF version |
current_consensus_parameters_version | ConsensusParametersVersion | Active consensus params version |
fuel_block_height_precondition_failed | bool | Whether required height wasn't met |
The client automatically extracts and caches these values:
Sources: crates/client/src/client.rs493-532
Sources: crates/fuel-core/src/schema/tx.rs200-217
The coinsToSpend query optimizes UTXO selection for transaction construction:
The indexed path is significantly faster for accounts with many UTXOs.
Sources: crates/fuel-core/src/coins_query.rs
The client SDK includes snapshot tests for query generation:
Sources: crates/client/src/client/schema/snapshots/
The codebase provides mock implementations of port traits for testing:
Sources: crates/fuel-core/src/graphql_api/worker_service/tests.rs1-98
The GraphQL API provides:
Key implementation files:
Refresh this wiki