Reference
GasKillerSDK API surface, state update types, and revert selectors.
API surface of GasKillerSDK. Inheriting it gives your contract everything on
this page.
abstract contract GasKillerSDK is
StateTracker, TransitionGuard, ERC165, IGasKillerSDK, IGasKillerSDKBatchConstants
| Constant | Value | Meaning |
|---|---|---|
| — | 300 | Default blockStaleMeasure, in blocks, when unset |
| ERC-165 id | 0x82b35a01 | type(IGasKillerSDK).interfaceId; the router requires it |
| ERC-165 id | 0x2ea5ee1d | type(IGasKillerSDKBatch).interfaceId; batching and the in-transition latch |
The quorum threshold is not an SDK constant. It belongs to the
SchnorrStakeRegistry the target is wired to, as thresholdNum / thresholdDen
of the registry's total weight. The public testnet registry requires 2/3.
verifyAndUpdate
The settlement entrypoint. The router builds this call for you (you never encode it by hand), but its parameters explain every failure mode.
function verifyAndUpdate(
bytes32 msgHash,
uint32 referenceBlockNumber,
bytes calldata storageUpdates,
uint256 transitionIndex,
bytes4 targetFunction,
uint256 s,
address Raddr,
address[] calldata nonSigners
) external payable;| Parameter | Purpose |
|---|---|
msgHash | The signed digest; recomputed on-chain and compared |
referenceBlockNumber | Block the operator set is evaluated at |
storageUpdates | ABI-encoded (StateUpdateType[], bytes[]) — the diff to apply |
transitionIndex | Counter value this payload was computed against |
targetFunction | Selector of the tracked function that was simulated |
s | Aggregate Schnorr response scalar |
Raddr | Aggregate nonce, as address(R) |
nonSigners | Operators that did not sign, in strictly ascending order |
It executes in this order, so an earlier check masks any later one:
referenceBlockNumber < block.number→ elseFutureBlockNumberreferenceBlockNumber + blockStaleMeasure >= block.number→ elseStaleBlockNumbertransitionIndex + 1 == stateTransitionCount()→ elseInvalidTransitionIndex- Digest matches
msgHash→ elseInvalidSignature isValidSignatureon the configured registry: subtract each non-signer's key and weight from the aggregate, check the threshold, verify the signature → elseInvalidQuorumSignature, or a registry revert- Apply the updates
Verification gas doesn't grow with the size of the operator set: one ecrecover
against the registry's cached aggregate key, plus one point subtraction per
non-signer.
The signed digest
sha256(abi.encode(transitionIndex, address(this), targetFunction, storageUpdates))Available as a view for off-chain cross-checking:
function getMessageHash(
uint256 transitionIndex,
bytes4 targetFunction,
bytes calldata storageUpdates
) external view returns (bytes32);Binding address(this) means a payload cannot be replayed against a different
contract, and binding transitionIndex means it cannot be replayed against a
different state of the same one.
verifyAndUpdateBatch
Settles several independently signed transitions in one transaction. Every transition after the first verifies at warm-access prices.
struct TaskSubmission {
bytes32 msgHash;
uint32 referenceBlockNumber;
bytes storageUpdates;
uint256 transitionIndex;
bytes4 targetFunction;
uint256 s;
address Raddr;
address[] nonSigners;
}
function verifyAndUpdateBatch(TaskSubmission[] calldata submissions) external payable;Each submission is checked exactly as a standalone verifyAndUpdate would check
it, so the signed digest does not change when a transition is batched.
Submissions apply in order of ascending transitionIndex, and the batch is
atomic.
A submission whose index has already settled is skipped rather than reverting the
batch. Settlement is permissionless, so without the skip anyone could lift one
submission from the mempool, settle it on its own first, and void the rest. Only
a gap, meaning an index above the next expected one, reverts with
InvalidTransitionIndex.
State update types
storageUpdates decodes to a list of operations applied in order.
| Type | Effect |
|---|---|
STORE | Write a 32-byte value to a storage slot |
CALL | External call, optionally with ETH value |
LOG0–LOG4 | Emit a log with 0–4 indexed topics |
CREATE | Deploy a contract, nonce-derived address |
CREATE2 | Deploy a contract, salt-derived address |
Most integrations only ever produce STORE and LOG*. See
Tracked functions
for the funding rules that apply to the value-bearing types.
Inherited members
From StateTracker:
| Member | Description |
|---|---|
trackState | Modifier; increments the transition counter before the body |
stateTransitionCount() | Transitions applied so far |
From TransitionGuard:
| Member | Description |
|---|---|
guardTransition | Modifier; reverts re-entry, holds the in-transition latch |
inTransition() | True while a transition is applying |
Both use fixed hashed slots, and TransitionGuard uses EIP-1153 transient
storage, so a Cancun-or-later EVM is required.
Revert selectors
What a failed settlement means. Reverts from the SDK:
| Selector | Error | Cause | Fix |
|---|---|---|---|
0x252f8a0e | FutureBlockNumber | Reference block is not yet mined | Retry; usually a node lagging behind head |
0x305c3e93 | StaleBlockNumber | Payload older than blockStaleMeasure | Submit a new task — the payload expired |
0x7376e0a2 | InvalidTransitionIndex | Contract state advanced since signing | Submit a new task; another transition landed first |
0x8baa579f | InvalidSignature | Recomputed digest ≠ msgHash | Payload was altered, or the target's ABI differs from the router's |
0x68477238 | InvalidQuorumSignature | The registry rejected the signature | See below |
0xc2e5347d | EmptyBatch | verifyAndUpdateBatch called with no submissions | Report it — malformed batch |
0x83a33c51 | BlockStaleMeasureOverflow | _setBlockStaleMeasure given a value above uint96 | Fix the value your contract passes |
0x287cdced | ReentrantTransition | verifyAndUpdate re-entered mid-transition | A CALL update re-entered the contract; see inTransition() |
0x493f09c4 | RevertingContext | A CALL update reverted | Often under-funded msg.value; carries the inner revert data |
0x30116425 | DeploymentFailed | A CREATE/CREATE2 update failed | Usually under-funded msg.value |
0x5f6f132c | InvalidArguments | Update arguments malformed | Report it |
0x25773e13 | MalformedLogPayload | Log update malformed | Report it |
Reverts that come from the SchnorrStakeRegistry, not the SDK:
| Selector | Error | What it usually means |
|---|---|---|
0xdc897c0c | StaleSnapshot | The operator set changed after the reference block. Submit a new task |
0x11ea130d | FutureReferenceBlock | Reference block is not yet mined. Retry |
0xbfc6c337 | NotRegistered(address) | A listed non-signer is not in this registry. Your target may be wired to a different registry than the router signs for, see Configuration |
0xedb3eb6c | NonSignersNotSorted | Malformed non-signer list. Report it |
InvalidQuorumSignature
The registry returned false. There are two causes:
- Too few signers. The operators who signed hold less than the threshold share of the registry's weight. This is transient: too many operators missed the round.
- The aggregate doesn't match. The key the registry holds is not the one the operators signed with. If it happens on every task, your target is wired to the wrong registry. If it happens once, the operator set changed mid-round and a new task will settle.
Decode an unknown selector with cast 4byte <selector>, or recover a revert
locally by replaying the payload at a block inside its validity window:
cast call "$TO" "$DATA" --from "$FROM" --block <n> --rpc-url "$RPC_URL"A payload that reverts at every block in its window is not a timing problem: it is a configuration problem.
Source
gas-killer/solidity-sdk— the SDKgas-killer/example-contracts— worked examples of progressively richer targets
