Gas Killer Docs
Solidity Reference

Integrate the SDK

Make your contract a valid Gas Killer target.

The HTTP API returns a ready-to-sign verifyAndUpdate transaction. That call only exists if your contract inherits the Gas Killer Solidity SDK, so the contract side comes first: the router refuses to accept a task for a target that does not advertise the SDK interface.

A valid target needs three things. Miss any one and the failure shows up at a different stage:

RequirementEnforced bySymptom when missing
Inherits GasKillerSDKRouter, at submit timeTask rejected — the target does not report the interface
avsAddress and blsSignatureChecker wired to the live deploymentRouter, while building the payloadTask fails — the router will not return a payload that cannot settle
Mutating functions marked trackStateNothing — it is your replay protectionSilent: stale payloads stay applicable

The router proves the settlement call before handing it back, so a mis-wired target fails as a task with the underlying error rather than as a reverted transaction you paid for. Get the addresses right up front anyway, see Configuration.

Requirements

  • Solidity ^0.8.27 and Foundry.
  • An EVM at Cancun or later. TransitionGuard uses EIP-1153 transient storage and has no pre-Cancun fallback path, so deploying to a chain without it breaks settlement itself rather than just the guard. The public testnet settles on Sepolia, which qualifies.

Install

forge install gas-killer/solidity-sdk

Add the remapping to remappings.txt (or foundry.toml):

gas-killer-sdk/=lib/solidity-sdk/src/

The SDK pulls eigenlayer-middleware for IBLSSignatureChecker, and pins evm_version = "cancun" in its own foundry.toml. Set the same in yours.

A minimal target

Everything an integration needs, and nothing else. Counter keeps one value and one tracked function that recomputes it; in a real contract that function is whatever is too expensive to run on-chain.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.27;

import {GasKillerSDK} from "gas-killer-sdk/GasKillerSDK.sol";

contract Counter is GasKillerSDK {
    /// Declared first, so `total` occupies storage slot 0 and `runs` slot 1.
    /// The signed diff writes raw slots, see Tracked functions.
    uint256 public total;
    uint256 public runs;

    event Recomputed(uint256 indexed runs, uint256 total);

    constructor(address avs, address blsSigChecker) {
        _setAvsAddress(avs);
        _setBlsSignatureChecker(blsSigChecker);
    }

    /// The expensive path. On-chain this is the reference implementation the
    /// operators reproduce off-chain; you never have to call it on-chain.
    function recompute(uint32 rounds) external trackState {
        uint256 acc = total;
        for (uint32 i = 0; i < rounds; i++) {
            acc = uint256(keccak256(abi.encode(acc, i)));
        }
        total = acc;
        runs += 1;
        emit Recomputed(runs, total);
    }
}

That is the whole contract-side integration. verifyAndUpdate, stateTransitionCount(), supportsInterface, and the transition guard all come from the base.

Deploy and verify

Pass the live addresses from Configuration:

forge create src/Counter.sol:Counter \
  --rpc-url "$RPC_URL" --private-key "$PRIVATE_KEY" \
  --constructor-args "$AVS_ADDRESS" "$SIG_CHECKER_ADDRESS"

Then check all four of these before submitting a task. Each maps to a failure you would otherwise debug from a revert:

The router will accept it. Must return true, or task submission is rejected:

cast call "$TARGET" "supportsInterface(bytes4)(bool)" 0x93de4531 --rpc-url "$RPC_URL"

The wiring took. Both must equal the values you passed:

cast call "$TARGET" "avsAddress()(address)" --rpc-url "$RPC_URL"
cast call "$TARGET" "blsSignatureChecker()(address)" --rpc-url "$RPC_URL"

The checker is bound to the live operator set. This is the check that catches a stale address, see Verify your own wiring.

The counter starts at zero. Confirms StateTracker is reachable:

cast call "$TARGET" "stateTransitionCount()(uint256)" --rpc-url "$RPC_URL"

Then submit a task

With the contract deployed, the API side is unchanged from the Quickstart: POST /tasks with your target address and the ABI-encoded calldata for the tracked function, poll until ready, submit the payload.

call_data: Array.from(
  Buffer.from(
    encodeFunctionData({ abi, functionName: "recompute", args: [1000] }).slice(2),
    "hex",
  ).values(),
)

The operators execute recompute(1000) off-chain, sign the resulting diff, and the payload you get back is a verifyAndUpdate call that writes total, runs, and the Recomputed log directly, without running the loop on-chain.

Where to go next

  • Configuration — the addresses to wire, how to verify them, and how to stay reconfigurable.
  • Tracked functions — what trackState guarantees, storage layout rules, and what keeps a function extractable.
  • Reference — full API surface, state update types, and a revert-selector lookup table.

The SDK also ships an aggregate-Schnorr scheme (SchnorrGasKillerSDK) that verifies a single secp256k1 signature in constant gas. The public testnet runs the BLS scheme, which is what this section documents. The service is moving to Schnorr, see Migration.

On this page