Install the latest Shovel from main branch
mainTo install the bleeding-edge version from the main branch for macOS on ARM64, use the following commands:
curl -LO https://indexsupply.net/bin/main/darwin/arm64/shovel --silent
chmod +x shovelrepository·main·Indexed 42 days ago
https://github.com/indexsupply/shovelA blockchain indexer that extracts data from EVM sources into PostgreSQL. It includes a CLI for running indexing tasks, a JSON-RPC 2.0 client for block and log retrieval, and the @indexsupply/shovel-config TypeScript utility for type-safe generation of configuration files.
To install the bleeding-edge version from the main branch for macOS on ARM64, use the following commands:
curl -LO https://indexsupply.net/bin/main/darwin/arm64/shovel --silent
chmod +x shovelUse the @indexsupply/shovel-config package to programmatically define and generate Shovel JSON configuration files using TypeScript. This allows you to use strongly-typed objects for Source, Table, and Integration definitions instead of writing raw JSON.
To generate the final JSON string, use makeConfig to assemble your sources and integrations, and toJSON to serialize the resulting configuration object.
import { makeConfig, toJSON } from "@indexsupply/shovel-config";
import type { Source, Table, Integration } from "@indexsupply/shovel-config";
const table: Table = {
name: "transfers",
columns: [
{ name: "log_addr", type: "bytea" },
{ name: "from", type: "bytea" },
{ name: "to", type: "bytea" },
{ name: "amount", type: "numeric" },
],
};
const mainnet: Source = {
name: "mainnet",
chain_id: 1,
url: "https://ethereum.publicnode.com",
};
let integrations: Integration[] = [
{
enabled: true,
name: "transfers",
sources: [{ name: mainnet.name, start: 0n }],
table: table,
block: [
{
name: "log_addr",
column: "log_addr",
filter_op: "contains",
filter_arg: ["0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"],
},
],
event: {
type: "event",
name: "Transfer",
inputs: [
{ indexed: true, name: "from", type: "address", column: "from" },
{ indexed: true, name: "to", type: "address", column: "to" },
{ indexed: false, name: "amount", type: "uint256", column: "amount" },
],
},
},
];
const config = makeConfig({
pg_url: "postgres:///shovel",
sources: [mainnet],
integrations: integrations,
});
console.log(toJSON(config));You can install the Shovel Config TS package via NPM:
npm install @indexsupply/shovel-config
npm install @indexsupply/shovel-configTo install the specific v1.6 release for macOS on ARM64 architecture, download the binary using curl, grant execution permissions with chmod, and verify the installation with the -version flag.
curl -LO https://indexsupply.net/bin/1.6/darwin/arm64/shovel --silent
chmod +x shovel
./shovel -versionTo build the shovel Docker image, run the docker build command from the root directory of the repository. You must specify the tag shovel:latest and point to the Dockerfile located at ./cmd/shovel/Dockerfile using the -f flag.
docker build -t shovel:latest -f ./cmd/shovel/Dockerfile .An Integration defines how data from specific sources is mapped to a PostgreSQL table.
Key components include:
table: A Table object defining the name, columns (with PGColumnType), and optional index (an array of IndexStatment strings like ['column_name', 'DESC']).event: An Event object describing the ABI-decoded event data to index.block: An array of BlockData objects for indexing non-event data (like tx_hash or block_num).notification: An optional Notification object specifying which columns to notify on.const integration = {
name: 'my-event-integration',
enabled: true,
sources: [{ name: 'main-rpc', start: 12345n }],
table: {
name: 'my_events',
columns: [
{ name: 'id', type: 'bigint' },
{ name: 'user_address', type: 'text' }
],
index: [['id', 'ASC']]
},
event: {
name: 'Transfer',
type: 'event',
inputs: [
{ name: 'from', type: 'address', column: 'from_addr' },
{ name: 'value', type: 'uint256', column: 'amount' }
]
}
};Shovel allows you to filter which BlockData or EventInput fields are indexed using two primary methods:
filter_op (e.g., eq, ne, gt, lt) and filter_arg (an array of strings).filter_op (specifically contains or !contains) and filter_ref which points to a specific integration and column.For EventInput, providing a column name instructs Shovel to map that specific event field to the specified PostgreSQL column. If column is omitted, the field is not saved.
Use New to create a new Client instance. You can provide multiple URLs to enable round-robin load balancing via NextURL().
Special URL suffixes:
debug: Enables debug mode, which pipes request/response bodies to os.Stdout using a TeeReader.nocache: Disables the internal caching mechanism for requests.import "github.com/indexsupply/shovel/jrpc2"
// Create a client with multiple provider URLs
client := jrpc2.New("https://eth-mainnet.g.alchemy.com/v2/key", "https://eth-mainnet.infura.io/v3/key")You can run the indexer with profiling enabled to analyze performance. The profiler runs after the indexing process completes.
Use the -profile flag with one of the following values:
cpu: Starts a CPU profile at startup and stops it after indexing.heap: Captures a heap profile after indexing.Profiling data is captured into an internal buffer and can be accessed via the /debug/pprof/capture endpoint on the dashboard server.
# Run with CPU profiling enabled
./shovel -config config.json -profile cpuShovel uses a JSON configuration file to define database connections (pg_url), blockchain sources (eth_sources), and data extraction pipelines (integrations).
Key configuration sections include:
pg_url: The connection string for the target PostgreSQL database.eth_sources: An array of blockchain sources, each requiring a name, chain_id, and one or more urls (or a single url).integrations: A list of extraction tasks. Each integration defines:sources: Which eth_sources to pull data from (referenced by name).table: The destination table name and its schema (columns).block: Filters applied to block data (e.g., filtering by log_addr using filter_op: "contains").event: The specific EVM event to watch, including its name, type, and mapping of event inputs to database columns.{
"pg_url": "postgres:///shovel",
"eth_sources": [
{
"name": "mainnet",
"chain_id": 1,
"urls": ["https://ethereum-rpc.publicnode.com"]
}
],
"integrations": [{
"name": "usdc-transfer",
"enabled": true,
"sources": [{"name": "mainnet"}],
"table": {
"name": "usdc",
"columns": [
{"name": "log_addr", "type": "bytea"},
{"name": "value", "type": "numeric"}
]
},
"block": [
{
"name": "log_addr",
"column": "log_addr",
"filter_op": "contains",
"filter_arg": ["0x...", "0x..."]
}
],
"event": {
"name": "Transfer",
"type": "event",
"inputs": [
{"indexed": true, "name": "from", "type": "address", "column": "from"}
]
}
}]
}The Client provides several builder-style methods to tune performance and behavior:
WithMaxReads(n int): Sets the maximum number of reads allowed for the internal caches (lcache, bcache, hcache) before they expire/prune.WithPollDuration(d time.Duration): Sets the interval for HTTP polling used to update the latest block cache.WithWSURL(url string): Sets a WebSocket URL. If provided, the client will use WebSocket subscriptions (eth_subscribe) for real-time updates instead of HTTP polling.client := jrpc2.New("https://example.com/rpc").
WithMaxReads(50).
WithPollDuration(2 * time.Second).
WithWSURL("wss://example.com/ws")The client returns an Error type when the RPC provider returns a JSON-RPC error object. You can check if an error is a valid RPC error using the Exists() method.
An Error contains:
Code: The integer error code.Message: The error message string.// Example of checking for an RPC-level error
if err != nil {
var rpcErr jrpc2.Error
if errors.As(err, &rpcErr) && rpcErr.Exists() {
fmt.Printf("RPC Error: %d - %s\n", rpcErr.Code, rpcErr.Message)
}
}