github agglayer/aggkit v0.12.0-rc1

pre-release6 hours ago

The notes below are written relative to v0.11.0, the last tag cut from develop. This release candidate is cut from the head of develop; the reason for the cut is the bridge service fee-estimation work (#1903).

🚀 What's New

  • Bridge service: claim and bridge fee estimation (#1903): new bridgeservice/feeestimate package and two read-only endpoints, GET /bridge/v1/claim-fee-estimate and GET /bridge/v1/bridge-fee-estimate, one estimator per network the instance serves. Estimates are gas units derived from recently observed claims/bridges times the live destination (claim) or origin (bridge) gas price, and they fail closed: 503 when no estimate is computable (no estimator for the network, an RPC failure, no samples), 400 for invalid input, never a default or partial value. First-claim (wrapper deployment) and repeat-claim estimates are reported separately. Message bridges/claims are not estimated, and no L1 data-fee component is included (the endpoint reports user-paid cost only — see the Assumptions section of docs/bridge_service.md). Go client methods, swagger and docs/bridge_service.md updated.
  • Bridge service: block_finalized on block-bound responses (#1921, closes #1915): clients can now tell whether a response can still be affected by a reorg. The value comes from the syncer that served the data (IsBlockFinalized(blockNum, blockHash), hash-verified), not from comparing block numbers against the finalized block, and is omitted when the syncer cannot tell.
  • Bridge service: api_revision in / and /health (#1921, closes #1920): informational counter starting at 1. The bump rule and the changes of each revision are in bridgeservice/docs/API_REVISIONS.md.
  • New tool: config_render (#1912): composes and resolves an aggkit config file with the same pipeline as the aggkit binary (--cfg repeatable and merged in order, --default-values, --default-vars, --mandatory-vars, --use-default-mandatory-vars, --env-prefix, optional deprecated-field checks). It exits 1 listing every unresolved variable, or reporting cycles. Built by make build-tools and shipped in the image as /usr/local/bin/config_render.
  • aggkit-proxy: BridgeServiceFinder.AllowNetworkIDs (#1923, closes #1909): an allowlist of network IDs to serve. When non-empty, every other network is excluded exactly as if it were in IgnoreNetworkIDs, both at enumeration and during live discovery. It still combines with AutoRegisterNewNetworks. Network 0 (L1) is a regular entry, so it must be listed to keep serving the L1 bridge service.
  • L2 syncers default to finalized block finality (#1902): [BridgeL2Sync] and [L2GERSync] no longer index L2 blocks that may still be reorged.
  • Dependency bump (#1904): golang.org/x/net v0.60.0 (govulncheck findings), plus the CI/lint tooling updated alongside it.

⚠️ Breaking Changes

Config

  • [BridgeL2Sync] and [L2GERSync]: BlockFinality default changes from LatestBlock to FinalizedBlock (#1902). [ClaimL2Sync] needs no edit — it inherits the new value through its existing {{BridgeL2Sync.BlockFinality}} reference. Deployments whose L2 does not advance the finalized block tag must set the old value explicitly (the kurtosis cdk-erigon e2e environment does exactly that).
  • [BridgeServiceFinder]: AllowNetworkIDs and IgnoreNetworkIDs are mutually exclusive — setting both makes aggkit-proxy fail at startup with ErrConflictingNetworkFilters (#1923).

API
There is no breaking interface change: no endpoint, query parameter or response field is removed, and every API change in this version is additive.

Go interfaces (relevant to out-of-tree implementations and hand-written mocks only)

  • L1InfoTreeQuerier gains IsUpToDate (#1922).
  • bridgetracker/domain.SupervisedStore and SupervisedRegistry gain UpdateTracking (#1914).

📋 Configuration Updates

Config diff: v0.11.0...v0.12.0-rc1 — see config/default.go and proxy/config/default.go.

Changed defaults (aggkit) — set the old value explicitly to keep the previous behaviour:

[BridgeL2Sync]
BlockFinality = "FinalizedBlock"   # was "LatestBlock"

[L2GERSync]
BlockFinality = "FinalizedBlock"   # was "LatestBlock"

New optional field (aggkit-proxy):

[BridgeServiceFinder]
# Only these networks are served. Mutually exclusive with IgnoreNetworkIDs.
# Include 0 to keep serving the L1 bridge service.
AllowNetworkIDs = []

No config change required for the fee-estimation endpoints: the estimators are wired in cmd (one per network this instance can serve) and there are no new TOML keys. config_render is a standalone tool and does not touch the aggkit config.

DB migrations: none in this version.

🔌 API Updates

🔌 Bridge service API

  • 🔌 Bridge service API Update: no breaking interface change — nothing removed, no parameter added or removed on existing endpoints, all changes to existing response bodies are additive.

    Added endpoints (both GET, read-only, fail closed):

    • /claim-fee-estimate?network_id=&origin_network=&origin_token_address= — estimated cost of claimAsset on the destination network, with separate first-claim (wrapper deployment) and repeat-claim estimates (exactly one applies per request, chosen by wrapper_exists; the other is null with its reason in unavailable).
    • /bridge-fee-estimate?network_id=&token_address= — estimated cost of bridgeAsset out of the origin network, one estimate per bridging mechanism (native gas token via msg.value, lock of an origin-native ERC20, burn of a wrapped token), no per-token breakdown.
    • Both answer 503 when no estimate is computable and 400 for invalid input. No server-side margin is added. Message claims/bridges are not estimated.

    Modified endpoints (existing response fields unchanged):

    • block_finalized (true | false | omitted) added to /bridges, /bridge-by-deposit-count, /bridges-by-content, /claims, /claims-by-ger, /root-by-ler, /settlements and /injected-l1-info-leaf. For an L2 lookup on /injected-l1-info-leaf both the L1 leaf block and the L2 injection block must be finalized (false as soon as one is known not to be).
    • api_revision added to / and /health, always present whatever the sync_status.

    Removed endpoints: none.

🔌 Proxy API

  • 🔌 AProxy API Update: no endpoint added, removed or modified — not a breaking interface change.
    • GET /tracker/v1/network/{network_id}/tx/{tx_hash} (and its WS twin) and GET /tracker/v1/activity/from/{address}: no shape change. claim_status: "claimed" now always requires the Claimed step to be done and, on the activity endpoint, on-chain isClaimed() confirmation in the same refresh (#1914). step_index points at the first pending step when no step is in progress.
    • [BridgeServiceFinder] AllowNetworkIDs (#1923) is config only: a non-listed network answers as if it were in IgnoreNetworkIDs.

🔌 Others API

  • 🔌 Others API Update: no changes. No .proto/gRPC interface changes in this version.

🐞 Bug Fixes

  • aggkit-proxy tracker served claim_status: "claimed" for bridges that were not claimed (#1914): a bridge read in the middle of the per-step writes of a single engine tick could be reported as claimed while unclaimed on chain (seen in a Bali load test on v0.11.0). Fixed at three levels — steps and tx are persisted in one atomic UpdateTracking write, step derivation reads as pending/readyToClaim instead of falling back to the last step, and the activity paths downgrade a tracker claimed that the same refresh's on-chain check reported as unclaimed.
  • l2gersync logged a false error when the L1 info tree was not synced yet (#1922, closes #1814): a missing UpdateHashChainValue GER is now logged WARN ("l1 info tree not synced yet, will retry") while l1infotreesync is not up to date, and still ERROR once it is (possible fake GER injected on L2). docs/remove_ger_runbook.md documents the new pattern.

🏷️ Image tags

Pushing this tag runs the Release workflow, which publishes multi-arch (linux/amd64, linux/arm64) images to ghcr.io/agglayer/aggkit: 0.12.0-rc1 (production, no shell) and 0.12.0-rc1-dev (development, with shell), plus 0.12.0-rc1-no_shell. As with previous release candidates, no floating 0.12 tag is published.

✅ Testing & Validation

  • New e2e coverage: TestClaimFeeEstimateViaProxy and TestBridgeFeeEstimateViaProxy through the proxy on anvil-2chains (L2 repeat-claim estimate served; fail-closed and 400 paths asserted), with a new CI matrix entry in test-go-e2e.yml.
  • Unit: reorgdetector.IsBlockFinalized against the real reorg detector (including the call order that guarantees an orphaned block can never be reported as finalized), IsBlockFinalized for L1InfoTreeSync, BridgeSync, ClaimSync and L2GERSync, block-hash filling in the queries, api_revision in every sync_status case, the tracker atomic-persist and torn-write regression tests, AllowNetworkIDs filtering/conflict cases, and tools/config_render.
  • The bridge service has not been exercised against a real reorging chain with block_finalized, and the fee estimates have not been validated against real L1/L2 gas prices beyond the e2e environments.

📌 Notes

  • This candidate is cut from develop, which is still on go 1.26.0 / toolchain go1.26.9 (image built from golang:1.26.9). The Go 1.26.10 bump of v0.11.1 (#1917) lives on release/0.11 and is therefore not part of this candidate.

📦 What's Changed

Full Changelog: v0.11.0...v0.12.0-rc1

Don't miss a new aggkit release

NewReleases is sending notifications on new releases.