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/feeestimatepackage and two read-only endpoints,GET /bridge/v1/claim-fee-estimateandGET /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:503when no estimate is computable (no estimator for the network, an RPC failure, no samples),400for 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 ofdocs/bridge_service.md). Go client methods, swagger anddocs/bridge_service.mdupdated. - Bridge service:
block_finalizedon 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_revisionin/and/health(#1921, closes #1920): informational counter starting at1. The bump rule and the changes of each revision are inbridgeservice/docs/API_REVISIONS.md. - New tool:
config_render(#1912): composes and resolves an aggkit config file with the same pipeline as the aggkit binary (--cfgrepeatable and merged in order,--default-values,--default-vars,--mandatory-vars,--use-default-mandatory-vars,--env-prefix, optional deprecated-field checks). It exits1listing every unresolved variable, or reporting cycles. Built bymake build-toolsand 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 inIgnoreNetworkIDs, both at enumeration and during live discovery. It still combines withAutoRegisterNewNetworks. Network0(L1) is a regular entry, so it must be listed to keep serving the L1 bridge service.- L2 syncers default to
finalizedblock finality (#1902):[BridgeL2Sync]and[L2GERSync]no longer index L2 blocks that may still be reorged. - Dependency bump (#1904):
golang.org/x/netv0.60.0 (govulncheck findings), plus the CI/lint tooling updated alongside it.
⚠️ Breaking Changes
Config
[BridgeL2Sync]and[L2GERSync]:BlockFinalitydefault changes fromLatestBlocktoFinalizedBlock(#1902).[ClaimL2Sync]needs no edit — it inherits the new value through its existing{{BridgeL2Sync.BlockFinality}}reference. Deployments whose L2 does not advance thefinalizedblock tag must set the old value explicitly (the kurtosiscdk-erigone2e environment does exactly that).[BridgeServiceFinder]:AllowNetworkIDsandIgnoreNetworkIDsare mutually exclusive — setting both makesaggkit-proxyfail at startup withErrConflictingNetworkFilters(#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)
L1InfoTreeQueriergainsIsUpToDate(#1922).bridgetracker/domain.SupervisedStoreandSupervisedRegistrygainUpdateTracking(#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 ofclaimAsseton the destination network, with separate first-claim (wrapper deployment) and repeat-claim estimates (exactly one applies per request, chosen bywrapper_exists; the other isnullwith its reason inunavailable)./bridge-fee-estimate?network_id=&token_address=— estimated cost ofbridgeAssetout of the origin network, one estimate per bridging mechanism (native gas token viamsg.value, lock of an origin-native ERC20, burn of a wrapped token), no per-token breakdown.- Both answer
503when no estimate is computable and400for 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,/settlementsand/injected-l1-info-leaf. For an L2 lookup on/injected-l1-info-leafboth the L1 leaf block and the L2 injection block must be finalized (falseas soon as one is known not to be).api_revisionadded to/and/health, always present whatever thesync_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) andGET /tracker/v1/activity/from/{address}: no shape change.claim_status: "claimed"now always requires theClaimedstep to bedoneand, on the activity endpoint, on-chainisClaimed()confirmation in the same refresh (#1914).step_indexpoints 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 inIgnoreNetworkIDs.
🔌 Others API
- 🔌 Others API Update: no changes. No
.proto/gRPC interface changes in this version.
🐞 Bug Fixes
aggkit-proxytracker servedclaim_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 atomicUpdateTrackingwrite, step derivation reads aspending/readyToClaiminstead of falling back to the last step, and the activity paths downgrade a trackerclaimedthat the same refresh's on-chain check reported as unclaimed.l2gersynclogged a false error when the L1 info tree was not synced yet (#1922, closes #1814): a missingUpdateHashChainValueGER is now loggedWARN("l1 info tree not synced yet, will retry") whilel1infotreesyncis not up to date, and stillERRORonce it is (possible fake GER injected on L2).docs/remove_ger_runbook.mddocuments 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:
TestClaimFeeEstimateViaProxyandTestBridgeFeeEstimateViaProxythrough the proxy onanvil-2chains(L2 repeat-claim estimate served; fail-closed and400paths asserted), with a new CI matrix entry intest-go-e2e.yml. - Unit:
reorgdetector.IsBlockFinalizedagainst the real reorg detector (including the call order that guarantees an orphaned block can never be reported as finalized),IsBlockFinalizedforL1InfoTreeSync,BridgeSync,ClaimSyncandL2GERSync, block-hash filling in the queries,api_revisionin everysync_statuscase, the tracker atomic-persist and torn-write regression tests,AllowNetworkIDsfiltering/conflict cases, andtools/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 ongo 1.26.0/ toolchaingo1.26.9(image built fromgolang:1.26.9). The Go 1.26.10 bump of v0.11.1 (#1917) lives onrelease/0.11and is therefore not part of this candidate.
📦 What's Changed
- chore: bump golang.org/x/net to v0.60.0 by @arnaubennassar in #1904
- feat: bridgeservice claim and bridge fee estimation endpoints by @arnaubennassar in #1903
- feat: add
config_rendertool to compose and resolve aggkit config files by @joanestebanr in #1912 - feat: use finalized block finality for L2 syncers by @arnaubennassar in #1902
- fix(bridgetracker): never report claimed for an unclaimed bridge read mid-persist by @arnaubennassar in #1914
- fix: remove false-error log when l1 info tree is not synced yet in l2gersync by @joanestebanr in #1922
- feat: bridge service add block_finalized field to several endpoints by @joanestebanr in #1921
- feat: aggkit-proxy bridgeServiceFinder AllowNetworkIDs whitelist by @joanestebanr in #1923
Full Changelog: v0.11.0...v0.12.0-rc1