Fuel
Fuel is a high-performance modular execution layer with its own virtual machine (FuelVM) and Sway programming language.
Core Concepts
Processors
Fuel integration primarily uses one processor type:
FuelProcessor<TContract extends Contract>: Binds to a specific Fuel contract ID (address) and requires its JSON ABI. It processes transactions involving this contract.- Typical Use Case: Monitoring a specific Sway contract (dApp, token, etc.).
- Binding:
FuelProcessor.bind({ address: 'fuel1...', abi: MyContractAbi, chainId: FuelNetwork.TESTNET, startBlock: 10000n }) - The ABI (
MyContractAbi) is typically imported from the JSON file generated by theforc buildcommand. - The
TContracttype parameter can be inferred or explicitly set using the contract type generated byfuels typegenfor enhanced type safety.
Handlers
Handlers are defined within the FuelProcessor to react to specific activities:
onTransaction(handler(transaction, ctx), fetchConfig?): Triggered for every transaction that interacts with the bound contract ID. Thetransactionobject contains decoded information about the transaction, including operations, inputs, outputs, and receipts.onLog<T>(handler(log, ctx), logIdFilter): Triggered when a specific log (identified by its ID derived from the ABI) is emitted within a transaction processed by the contract. Thelogobject contains the decoded log data (log.data).onTransfer(handler(transfer, ctx), filter): Triggered when aTransferorTransferOutreceipt matches the provided filter criteria (contract ID, asset ID, sender (from), recipient (to)). Useful for tracking asset movements involving the bound contract.onBlockInterval(handler(block, ctx), interval?, backfillInterval?): Processes blocks based on block height intervals.onTimeInterval(handler(block, ctx), intervalMinutes?, backfillIntervalMinutes?): Processes blocks based on time intervals.onCall(handler(call, ctx), nameFilter)(Currently Internal/Experimental): Aims to trigger on specific function calls within a transaction, similar to EVMonTrace. Requires the contract ABI.
Context (ctx)
ctx)Handler functions receive a FuelContractContext<TContract> object providing:
- Chain information:
chainId. - Contract details:
contractAddress,contractName,contract(afuels-tsContract instance for on-chain reads). - Block/Transaction details:
block,transaction,timestamp. - Helper methods: Access to the
fuels-tsprovider(ctx.provider). - Standard SDK outputs:
ctx.meter.Counter('...'),ctx.eventLogger.emit('...').
Fetch Configuration (fetchConfig)
fetchConfig)The onTransaction handler accepts an optional fetchConfig parameter. The configuration is defined by the FuelFetchConfig interface:
interface FuelFetchConfig {
includeFailed?: boolean; // Optionally include failed transactions
}This allows for more granular control over which transactions are processed. For example, to include failed transactions:
processor.onTransaction(..., { fetchConfig: { includeFailed: true } });Getting Started Example (Hypothetical Counter Contract)
First, create a Fuel processor project: yarn sentio create -c fuel <project-name>. You can refer to the CLI Reference to learn details.
import { FuelProcessor, FuelContractContext, FuelNetwork } from "@sentio/sdk/fuel";
import { CounterContractAbi } from "./abis/counter-contract-abi"; // ABI from forc build
import { CounterContract } from "./types/CounterContract"; // Types from fuels typegen
import { FuelLog } from "@sentio/sdk/fuel";
// Define the structure of the CountIncremented log based on the ABI
interface CountIncrementedLog {
counter: bigint;
amount: bigint;
}
const CONTRACT_ID = "fuel1qvhrq08r886ua2u6z0j3a5j0z3z5f7k6f7z3q5"; // Replace with your contract ID
FuelProcessor.bind({
address: CONTRACT_ID,
abi: CounterContractAbi,
chainId: FuelNetwork.TESTNET, // Or FuelNetwork.BETA_5
startBlock: 150000n,
})
.onTransaction(async (tx, ctx: FuelContractContext<CounterContract>) => {
// Process every transaction interacting with the contract
ctx.meter.Counter("transaction_cnt").add(1);
// Example: Check transaction status
if (tx.status === "success") {
ctx.meter.Counter("successful_txns").add(1);
} else {
ctx.meter.Counter("failed_txns").add(1);
}
})
.onLog<CountIncrementedLog>(async (log, ctx: FuelContractContext<CounterContract>) => {
// Process CountIncremented logs
ctx.eventLogger.emit("CountIncremented", {
distinctId: log.sender?.address, // Use sender if available in log context
newCounterValue: log.data.counter.toString(),
incrementAmount: log.data.amount.toString(),
});
ctx.meter.Gauge("latest_cnt").record(log.data.counter);
ctx.meter.Counter("total_incremented").add(log.data.amount);
}, "0x0000000000000001") // Replace with the actual Log ID for CountIncremented
.onBlockInterval(async (block, ctx: FuelContractContext<CounterContract>) => {
// Example: Reading contract state periodically
try {
const countResult = await ctx.contract.functions.count().get();
const currentCount = countResult.value.toBigInt();
ctx.meter.Gauge("onchain_count_snapshot").record(currentCount);
} catch (error) {
console.error("Failed to read count from contract:", error);
}
}, 100); // Check every 100 blocks
Updated 4 days ago