Skip to content

blocknative/gas-agent

Repository files navigation

Gas Agent

A Rust-based agent system that generates and submits gas price predictions for EVM networks to the Gas Network for evaluation.

Prerequisites

Install Rust

You'll need Rust installed on your system. The recommended way is using rustup:

macOS and Linux

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
source ~/.cargo/env

Windows

Download and run the installer from rustup.rs

Verify your installation:

rustc --version
cargo --version

Installation

Pre-built Binaries (Recommended)

Download the latest release for your platform:

# Install using our shell installer (macOS/Linux)
curl --proto '=https' --tlsv1.2 -LsSf https://github.com/blocknative/gas-agent/releases/latest/download/gas-agent-installer.sh | sh

# Or install via Homebrew (macOS/Linux)
brew install blocknative/tap/gas-agent

# Or download manually from GitHub releases
# Visit: https://github.com/blocknative/gas-agent/releases

From Source

If you prefer to build from source or need the latest unreleased changes:

# Install via cargo (requires Rust toolchain)
cargo install --git https://github.com/blocknative/gas-agent

# Or clone and build
git clone https://github.com/blocknative/gas-agent
cd gas-agent
cargo build --release

Quick Start

  1. Generate signing keys

    All agent payloads are signed before submission to the Gas Network so that they are verifiable and attributable. Use the private key for the signer_key field for each agent.

    gas-agent generate-keys
  2. Configure chains and agents

    A list of chains and the agents to run for each chain can be configured and will run in parallel with each chain running on it's own thread. Set the CHAINS env variable with a JSON string:

    [
      {
        "system": "ethereum",
        "network": "mainnet",
        "json_rpc_url": "https://ethereum-rpc.publicnode.com",
        "agents": [
          {
            "kind": "percentile",
            "signer_key": "YOUR-GENERATED-PRIVATE-KEY",
            "prediction_trigger": "block"
          }
        ]
      }
    ]
  3. Run the agent

    gas-agent start

Agent Registration

Before your agent can submit predictions to the Gas Network, you need to register and get your signing addresses whitelisted.

How Agent Authentication Works

  1. Signed Predictions: All agent payloads are cryptographically signed using your private key before submission
  2. Address Extraction: The Gas Network collector validates the signature and extracts the corresponding Ethereum address
  3. Whitelist Validation: Only predictions from whitelisted addresses are accepted and processed by the network

Getting Whitelisted

To register your agent and get your signing addresses whitelisted:

  1. Generate Your Keys: Use the built-in key generation tool to create your signing keys:

    gas-agent generate-keys
  2. Save Your Keys: Securely store the private key for your agent configuration and note the corresponding public address

  3. Submit Whitelist Request: Contact the Blocknative team with your public address(es):

  4. Include in Your Request:

    • Your Ethereum address(es) that will be signing predictions
    • Brief description of your prediction model/strategy
    • Expected prediction frequency and settlement types
    • Your intended use case or research goals

Multiple Agents and Addresses

  • Unique Keys Recommended: Use different signing keys for different agent types or models
  • Separate Evaluation: Each unique combination of address, system, network, and settlement is evaluated independently
  • All Addresses Need Whitelisting: Each signing address you plan to use must be individually whitelisted

Testing Before Whitelisting

While waiting for whitelist approval, you can:

  • Test your agent locally with mock endpoints
  • Verify your prediction logic and model performance
  • Ensure your signing and payload generation works correctly

Once whitelisted, your agent can begin submitting predictions that will be evaluated and potentially published to the Gas Network for end users.

Development

Prerequisites for Development

Before starting development, ensure you have:

  1. Rust toolchain (see Installation section above)
  2. Git for version control
  3. A code editor with Rust support (VS Code with rust-analyzer recommended)

Setting Up the Development Environment

# Clone the repository
git clone https://github.com/blocknative/gas-agent
cd gas-agent

# Verify everything builds and tests pass
cargo check
cargo test
cargo clippy --workspace --all-targets --all-features
cargo fmt --check

Running in Development Mode

# Run with debug logging
RUST_LOG=debug cargo run -- start --chains 'YOUR-CONFIG-JSON'

# Run tests
cargo test

# Run specific tests
cargo test test_name

# Run with release optimizations (for performance testing)
cargo run --release -- start --chains 'YOUR-CONFIG-JSON'

Development Workflow

  1. Create a feature branch:

    git checkout -b feature/your-feature-name
  2. Make your changes and ensure code quality:

    # Check for compilation errors
    cargo check
    
    # Run tests
    cargo test
    
    # Fix linting issues
    cargo clippy --workspace --all-targets --all-features --fix
    
    # Format code
    cargo fmt
  3. Commit your changes:

    git add .
    git commit -m "Add your feature description"
  4. Push and create a pull request:

    git push -u origin feature/your-feature-name
    # Then create a PR on GitHub

Code Quality Standards

The project enforces strict code quality standards:

  • No warnings: All code must compile without warnings
  • Formatted code: Use cargo fmt to format code consistently
  • Linted code: Use cargo clippy to catch common mistakes and improve code quality
  • Tested code: Add tests for new functionality
  • Documentation: Document public APIs and complex logic

Testing

# Run all tests
cargo test

# Run tests with output
cargo test -- --nocapture

# Run tests for a specific module
cargo test models::

# Run integration tests only
cargo test --test '*'

# Generate test coverage report (requires cargo-tarpaulin)
cargo install cargo-tarpaulin
cargo tarpaulin --out Html

Configuration Options

The agent supports the following command-line arguments and environment variables:

  • --server-address / SERVER_ADDRESS: HTTP server bind address (currently used only for Kubernetes probes) (default: 0.0.0.0:8080)
  • --chains / CHAINS: JSON configuration for EVM networks and agents
  • --collector-endpoint / COLLECTOR_ENDPOINT: The Gas Network endpoint for payload evaluation (default: https://collector.gas.network)

Chain Configuration

The chain configuration is specified as a JSON array where each object represents an EVM network and its associated agents. Each chain configuration supports the following fields:

ChainConfig Fields

  • system (required): The blockchain system to connect to

    • Available options: "ethereum", "base", "polygon"
  • network (required): The network within the system

    • Available options: "mainnet"
  • json_rpc_url (required): The JSON-RPC endpoint URL to poll for new blocks

    • Example: "https://ethereum-rpc.publicnode.com"
  • pending_block_data_source (optional): Configuration for fetching pending-block (mempool) data

  • agents (required): Array of agent configurations to run on this chain

Pending Block Data Source

When specified, this configures how to fetch pending block (mempool) data which can be passed to models that can be more reactive to changes in the mempool and to make use of private data to create more accurate predictions:

{
  "pending_block_data_source": {
    "json_rpc": {
      "url": "https://api.example.com/pending",
      "method": "eth_getPendingBlock",
      "params": ["pending"],
      "poll_rate_ms": 1000
    }
  }
}

A full CHAINS configuration for Ethereum with a JSON RPC pending block data source:

CHAINS='[{"system": "ethereum", "network": "mainnet", "json_rpc_url": "https://ethereum-rpc.publicnode.com", "pending_block_data_source": {"json_rpc": {"url": "http://localhost:8545", "method": "eth_getBlockByNumber", "params": ["pending", true], "poll_rate_ms": 1000}}, "agents": [{"kind": "pending_floor", "prediction_trigger": {"poll": {"rate_ms": 1000}}, "signer_key": "0xede33830a316e1f3018a50709c54d1f01d6ab3053f4f29ec552ce10bc9f5ef72"}]}]'

Fields:

  • url (required): The JSON-RPC endpoint URL
  • method (required): The RPC method to call
  • params (optional): Parameters to pass to the RPC method
  • poll_rate_ms (required): Polling interval in milliseconds

Currently, JSON RPC is the only source available, but other sources are coming soon. Please create an issue if there is a specific source that you would like to see supported.

Expected RPC Response Structure

The pending block RPC endpoint must return a JSON-RPC 2.0 response containing a required transactions field with an array of transaction objects. Each transaction object has the following requirements:

Required fields:

  • hash: Transaction hash identifier

Gas price fields (one of the following combinations):

  • gasPrice: Legacy gas price (for pre-EIP-1559 transactions)
  • OR maxFeePerGas AND maxPriorityFeePerGas: EIP-1559 gas price fields (both required together)
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "transactions": [
      {
        "hash": "0x1234567890abcdef...",
        "maxFeePerGas": "0x174876e800",
        "maxPriorityFeePerGas": "0x59682f00"
      },
      {
        "hash": "0xabcdef1234567890...",
        "gasPrice": "0x165a0bc00"
      }
    ]
  }
}

Agent Configuration

Each agent in the agents array supports the following configuration:

{
  "kind": "percentile",
  "signer_key": "0x1234567890abcdef...",
  "prediction_trigger": "block"
}

Fields:

  • kind (required): The type of agent to run

    • "node": Publishes the standard estimate from the node
    • "target": Publishes the actual minimum price for new blocks
    • Model-based agents:
      • "adaptive_threshold": Uses adaptive threshold analysis
      • "distribution_analysis": Analyzes gas price distributions
      • "moving_average": Uses moving average calculations
      • "percentile": Uses percentile-based predictions
      • "time_series": Uses time series analysis
      • "last_min": Takes the minimum from the previous block and uses that as the prediction for the next block.
      • "pending_floor": Takes the minimum from the pending-block, adds 1 wei and uses that as the prediction for the next block.
  • signer_key (required): Private key for signing predictions (use cargo run -- generate-keys to create)

  • prediction_trigger (required): When to generate predictions

    • "block": Generate prediction when a new block is detected
    • {"poll": {"rate_ms": 5000}}: Generate predictions at regular intervals (rate in milliseconds)

Models

The gas agent includes several built-in prediction models that analyze block data to estimate optimal gas prices. Each model uses different strategies and data sources to predict gas prices. All models now return errors when they lack sufficient data instead of fallback values, providing clear feedback about what's needed for successful predictions.

Historical Block Models

These models use historical onchain data to create a prediction for the next block. Since these models can only generate a single prediction per block, the "prediction_trigger": "block" is typically used with these models. All historical models require at least one block with transaction data and will return descriptive errors if insufficient data is provided.

percentile

Analyzes the distribution of gas prices across the 5 most recent blocks and selects the 75th percentile to ensure high inclusion probability. This model is particularly effective during periods of high volatility, as it targets a price that would have included 75% of recent transactions. Requires at least one block distribution with transactions.

last_min

Simply takes the minimum gas price from the most recent block and uses it as the prediction for the next block. This is the most aggressive pricing strategy and works well when gas prices are stable. Requires at least one non-empty block distribution.

moving_average

Calculates a Simple Weighted Moving Average (SWMA) of recent gas prices, giving more weight to more recent blocks (up to 10 blocks). This approach works well when gas prices are relatively stable and provides smooth price transitions. Requires at least one block with transactions.

adaptive_threshold

Identifies the minimum gas price that would have been included in each recent block (up to 50 blocks) and applies an adaptive premium based on price volatility. When prices are stable, it applies a small premium; when volatile, it applies a larger premium (up to 50%). This provides a balance between cost and inclusion probability. Requires at least one block distribution with transactions.

time_series

Uses simple linear regression to identify trends in gas prices and predict the next value based on the median gas price of the last 20 blocks. This model is particularly useful when gas prices show a consistent trend over time (either increasing or decreasing). Requires at least one block with transactions for analysis.

distribution_analysis

Analyzes the cumulative distribution function (CDF) of gas prices in the most recent block to find "sweet spots" where many transactions are being included. It identifies points where the rate of change in the CDF decreases significantly, representing efficient gas price levels, then applies a 10% premium for higher inclusion probability. Requires at least one block distribution with a non-empty latest block.

Pending Block Models

These models use pending (mempool or private) transaction data to make predictions. They are specifically designed for users who have access to pending block information, such as block builders with proprietary transaction flows.

pending_floor

Specifically designed for block builders with proprietary private transaction flow who can see what the likely next block will contain. This model analyzes the pending block distribution to find the minimum gas price and adds exactly 1 wei (0.000000001 gwei) to ensure transaction inclusion while paying the absolute minimum. Unlike historical models, this one requires access to pending block data and will return an error if no pending block distribution is provided. Most effective when used with a polling prediction trigger to provide up to date predictions ("prediction_trigger": {"poll": {"rate_ms: <desired_rate>"}}).

Model Error Handling

All models will return descriptive errors instead of fallback values when they lack sufficient data. This provides clear feedback about what's needed for successful predictions.

Common Error Scenarios:

  • Empty block distributions: All historical models require at least one block distribution
  • No transactions: Models need blocks that contain actual transaction data to analyze
  • Missing pending data: The pending_floor model specifically requires pending block distribution data

Error Message Examples:

  • "LastMin model requires at least one block distribution"
  • "Percentile model requires blocks with transactions"
  • "PendingFloor model requires pending block distribution data"

When a model returns an error, check that:

  1. You're providing the correct type of data for the model
  2. Your block distributions contain actual transaction data
  3. For pending_floor, you're providing the pending_block_distribution parameter

Creating Custom Models

To create a custom prediction model, you'll need to fork the repository and implement your own model logic. Here's how:

Step 1: Fork and Clone

git fork https://github.com/blocknative/gas-agent
git clone https://github.com/YOUR-USERNAME/gas-agent
cd gas-agent

Step 2: Add Your Model Type

Add your model to the ModelKind enum in src/types.rs:

#[derive(Debug, Clone, EnumString, Display, Deserialize, Serialize)]
#[strum(serialize_all = "snake_case")]
#[serde(rename_all = "snake_case")]
pub enum ModelKind {
    AdaptiveThreshold,
    DistributionAnalysis,
    MovingAverage,
    Percentile,
    TimeSeries,
    LastMin,
    PendingFloor,
    YourCustomModel,  // Add this line
}

Step 3: Create Your Model Implementation

Create a new file src/models/your_custom_model.rs:

/*
Your Custom Model Description
Explain what your model does and how it works.
*/

use crate::models::{FromBlock, ModelError, Prediction};
use crate::types::Settlement;
use crate::{distribution::BlockDistribution, utils::round_to_9_places};

pub fn get_prediction_your_custom_model(
    block_distributions: &[BlockDistribution],
    latest_block: u64,
) -> Result<(Prediction, Settlement, FromBlock), ModelError> {
    // Your model logic here
    //
    // block_distributions is a Vec<BlockDistribution> where:
    // - BlockDistribution = Vec<Bucket>
    // - Bucket { gwei: f64, count: u32 }
    //
    // Each BlockDistribution represents gas price buckets from a block
    // sorted from oldest to newest blocks

    let Some(latest_block_distribution) = block_distributions.last() else {
        return Err(ModelError::insufficient_data(
            "YourCustomModel requires at least one block distribution",
        ));
    };

    let mut total_gas_price = 0.0;
    let mut total_transactions = 0u32;

    for bucket in latest_block_distribution {
        total_gas_price += bucket.gwei * bucket.count as f64;
        total_transactions += bucket.count;
    }

    if total_transactions == 0 {
        return Err(ModelError::insufficient_data(
            "YourCustomModel requires blocks with transactions",
        ));
    }

    let predicted_price = total_gas_price / total_transactions as f64;

    Ok((
        round_to_9_places(predicted_price),
        Settlement::Fast,
        latest_block + 1,
    ))
}

Step 4: Register Your Model

Add your model to the module system in src/models/mod.rs:

use your_custom_model::get_prediction_your_custom_model;

mod your_custom_model;

// In the apply_model function, add your case:
pub async fn apply_model(
    model: &ModelKind,
    block_distributions: &[BlockDistribution],
    pending_block_distribution: Option<BlockDistribution>,
    latest_block: u64,
) -> Result<(Prediction, Settlement, FromBlock), ModelError> {
    // ... existing code ...

    match model {
        // ... existing cases ...
        ModelKind::YourCustomModel => {
            get_prediction_your_custom_model(block_distributions, latest_block)
        }
    }
}

Step 5: Build and Test

cargo build
cargo test

# Test your model
gas-agent start --chains '[{
  "system": "ethereum",
  "network": "mainnet",
  "json_rpc_url": "https://ethereum-rpc.publicnode.com",
  "agents": [{
    "kind": "your_custom_model",
    "signer_key": "YOUR-PRIVATE-KEY",
    "prediction_trigger": "block"
  }]
}]'

Model Development Tips

  1. Understand the Data Structure: Each BlockDistribution contains buckets of gas prices with transaction counts, representing the gas price distribution for that block.

  2. Handle Edge Cases: Validate inputs and return descriptive ModelErrors when data is insufficient.

  3. Consider Settlement Times: Choose appropriate Settlement values:

    • Immediate: Next block
    • Fast: ~15 seconds
    • Medium: ~15 minutes
    • Slow: ~1 hour
  4. Use Utility Functions: The round_to_9_places() function ensures consistent precision across predictions.

  5. Test with Different Market Conditions: Test your model during periods of high volatility, network congestion, and normal conditions.

  6. Leverage Pending Block Data: If available, you can access pending_block_distribution parameter in the apply_model function for more reactive predictions.

Settlement Times and Block Windows

As a prediction provider, you can generate gas price predictions for different settlement times that end users will consume to price their transactions. Each settlement time represents a different urgency level and block window that your predictions target. A settlement BlockWindow is derived from the from_block parameter and Settlement enum. The from_block must be a future block number and is typically set to the next block.

Settlement Options for Prediction Models

Your models can return one of four settlement times, each targeting different end-user needs:

immediate

  • Target Time: Next block (0ms)
  • Block Window: 0 blocks (next block only)
  • End User Profile: Arbitrage bots, MEV strategies, time-critical DeFi operations
  • Prediction Strategy: Should predict the minimum gas price needed for immediate inclusion

fast

  • Target Time: ~15 seconds (15,000ms)
  • End User Profile: Standard DeFi interactions, swaps, NFT minting
  • Prediction Strategy: Balance between inclusion probability and cost efficiency

medium

  • Target Time: ~15 minutes (900,000ms)
  • End User Profile: Regular transfers, non-urgent contract interactions
  • Prediction Strategy: Focus on cost optimization while maintaining reasonable inclusion probability

slow

  • Target Time: ~1 hour (3,600,000ms)
  • End User Profile: Batch operations, low-priority transactions, cost-sensitive users
  • Prediction Strategy: Minimize gas costs, accepting longer wait times

Block Window Calculation

Settlement times are converted to block windows based on each network's block time:

Network Block Times

  • Ethereum: 12 seconds per block (12,000ms)
  • Base: 2 seconds per block (2,000ms)
  • Polygon: 2 seconds per block (2,000ms)

Settlement to Block Window Translation

The number of blocks for each settlement is calculated as: floor(settlement_time_ms / network_block_time_ms)

Settlement Ethereum (12s blocks) Base (2s blocks) Polygon (2s blocks)
immediate 0 blocks (next block) 0 blocks (next block) 0 blocks (next block)
fast ~1 block ~8 blocks ~8 blocks
medium ~75 blocks ~450 blocks ~450 blocks
slow ~300 blocks ~1,800 blocks ~1,800 blocks

How Your Predictions Are Evaluated

When you submit a gas price prediction, the Gas Network evaluates its accuracy within the specified block window:

  1. Prediction Submitted: Your model predicts 20 gwei with fast settlement for Ethereum
  2. Block Window Calculated: fast on Ethereum = ~1 block window starting from from_block
  3. Evaluation Period: The system monitors min price for blocks within that window
  4. Scoring: Your prediction is scored on:
    • Inclusion Rate: Did your prediction price get onchain within the block window
    • Cost Efficiency: Percentage overpayment

For details on the Evaluation Function used to score your predictions, see the Evaluation Function.

Building for Production

# Optimized release build
cargo build --release

# The binary will be available at
./target/release/gas-agent

Release Process

Creating a New Release

Releases are automated using cargo-dist and triggered by pushing git tags. Follow these steps:

1. Update Version

Update the version in Cargo.toml:

[package]
version = "0.1.0"  # Update this

2. Update Changelog

Update CHANGELOG.md following Keep a Changelog format:

## [0.1.0] - 2025-01-22

### Added

- New feature descriptions
- New model implementations

### Changed

- Breaking changes or significant modifications
- Performance improvements

### Fixed

- Bug fixes and error handling improvements

### Removed

- Deprecated features that were removed

Important: The GitHub release title and body are automatically generated from the changelog, so ensure your changelog entries are clear and well-formatted.

3. Create and Push Tag

# Commit version and changelog changes first
git add Cargo.toml CHANGELOG.md
git commit -m "Prepare release v0.1.0"

# Create and push the tag
git tag v0.1.0
git push origin v0.1.0

4. Automated Release Process

When you push the tag, GitHub Actions will automatically:

  1. Build artifacts for multiple platforms (Linux, macOS, Windows)
  2. Run tests to ensure code quality
  3. Generate release notes from your changelog
  4. Create GitHub release with:
    • Auto-generated title from changelog
    • Auto-generated body from changelog
    • Platform-specific binaries and installers
    • Checksums for verification

5. Verify Release

After the workflow completes:

  1. Check the GitHub releases page
  2. Verify all platform binaries are present
  3. Test the installer scripts work correctly
  4. Update documentation if needed

Release Checklist

Before creating a release:

  • All tests pass: cargo test
  • Code is properly formatted: cargo fmt --check
  • No clippy warnings: cargo clippy --workspace --all-targets --all-features
  • Version updated in Cargo.toml
  • CHANGELOG.md updated with new version and changes
  • All changes committed to main branch
  • Git tag created and pushed

Hotfix Releases

For urgent fixes:

  1. Create a hotfix branch from the release tag
  2. Apply minimal fix
  3. Update patch version (e.g., 0.1.00.1.1)
  4. Update changelog with hotfix entry
  5. Create new tag and push

Pre-releases

For beta or release candidate versions:

  1. Use pre-release version format: v0.1.0-beta.1
  2. GitHub release will automatically be marked as pre-release
  3. Follow same process as regular releases

Docker Support

A Dockerfile is included for containerized deployments:

docker build -t gas-agent .
docker run -e CHAINS='YOUR-CONFIG' gas-agent start

Monitoring

The agent exposes kubernetes probe endpoints:

  • Liveness: GET /internal/probe/liveness
  • Readiness: GET /internal/probe/readiness

License

See LICENSE file for details.

About

Create gas predictions and publish them for evaluation and submission to the Gas Network

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Contributors 3

  •  
  •  
  •