Shovel Indexer

repository·main·Indexed 42 days ago

https://github.com/indexsupply/shovel

A 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.

Tokens
4.7K
Snippets
19
Records
22
Agent score
54%

What's inside Shovel

  1. Install the latest Shovel from main branch

    main

    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 shovel
  2. Script Shovel JSON configuration with @indexsupply/shovel-config

    main

    Use 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));
  3. Install Shovel v1.6 on macOS (ARM64)

    main

    To 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 -version
  4. Build the shovel Docker image

    main

    To 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 .
  5. Define an Integration and its Table schema

    main

    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' }
        ]
      }
    };
  6. Configure BlockData and Event filtering

    main

    Shovel allows you to filter which BlockData or EventInput fields are indexed using two primary methods:

    1. Argument-based filtering: Use filter_op (e.g., eq, ne, gt, lt) and filter_arg (an array of strings).
    2. Reference-based filtering: Use 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.

  7. Initialize a JSON-RPC 2.0 Client

    main

    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")
  8. Profile the shovel indexer

    main

    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 cpu
  9. Configure Shovel using a JSON configuration file

    main

    Shovel 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"}
          ]
        }
      }]
    }
  10. Configure Client options

    main

    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")
  11. Handle JSON-RPC errors

    main

    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)
        }
    }