Skip to content

feat(aggkit): aggkit bridge-service client + multi-network aggregator (incl. bridge tracker, rc5) - #28

Merged
joanestebanr merged 54 commits into
mainfrom
feat/aggkit-bridge-client
Oct 1, 2026
Merged

joanestebanr merged 54 commits into
mainfrom
feat/aggkit-bridge-client

Conversation

@arnaubennassar

@arnaubennassar arnaubennassar commented Aug 10, 2026 •

Copy link
Copy Markdown
Contributor

What & why

Adds a self-contained src/aggkit/ module: a typed client and multi-network aggregator for the aggkit bridge service REST API (/bridge/v1). This is the API surface behind the dev-ui's migration off the legacy Bridge Hub API (companion PR agglayer/agglayer-dev-ui#24): everything the UI needs — activity, status derivation, claim proofs, ready-to-claim counts, token metadata, and (later in this PR) bridge tracking — is served from aggkit through this module. The NATIVE module is untouched; the legacy bridge-hub proof path is retained (still used by the NATIVE claim flow) and marked @deprecated.

14 commits, oldest layer first:

1. Bridge-service client + multi-network aggregator (0fa7c77)

  • AggkitBridgeClient — typed single-network client (bridges, claims, claim-proof, l1-info-tree-index, token-mappings, sync-status, health) with snake_case query serialization, page_size <= 200 handling, BigInt-safe global_index parsing (raw-text parse), and a typed AggkitApiError.
  • AggkitBridgeAggregator — multi-network fan-out with partial-failure reporting (failedNetworks), tiered status derivation (BRIDGED → LEAF_INCLUDED → READY_TO_CLAIM → CLAIMED, with a targeted global_index claims probe to avoid page-1-only false positives), ready-to-claim counting, claim-proof orchestration, and token-metadata composition.
  • ERC20.getMetadata() on-chain read helper (aggkit /token-mappings carries no name/symbol/decimals).
  • Fixture-driven unit tests (fixtures captured from a live enclave) + a live-enclave smoke script.

2. Claim correctness + proxy fault tolerance (c472bd0)

  • New destination-side GER-injection probe (getInjectedL1InfoLeaf) gates READY_TO_CLAIM for L2-destination rows, closing a measured ≥8s premature-READY window whose claim proof would revert GlobalExitRootInvalid on-chain.
  • getClaimInputs rebuilt on the destination-injected leaf index rather than the deposit's own L1-info-tree index (previously wrong for every L2-destination claim, incl. L1→L2 manual claims); additive sourceL1InfoTreeIndex field.
  • Per-row Tier-2 probe failures degrade to failedNetworks instead of rejecting the whole call; getSyncStatus sends network_id explicitly (unqualified requests 400 through aggkit-proxy).

3. Live smoke script + docs (fef61f2, b706110, 2ec0d5e)

scripts/aggkit-smoke.ts extended to run against aggkit-proxy across networks {0,1,2} (full fan-out activity, real L2→L2 and L2→L1 claim-input checks, opt-in proxy-502 partial-failure section); multi-network proxy configuration and the status lifecycle documented.

4. Bridge tracker API (a8a44a0, c254ec2, 3c6d475, 5ad31b0)

getBridgeTracking — tracker types + client method + aggregator wiring, reconciled against the live-captured wire format (not the docs, which were wrong pre-rc5 — see #1781); terminal-state, step-error, and transient-resolve semantics plus polling guidance documented. This is what the dev-ui tracker UI (companion PR) polls.

5. rc5 phase (fea252e, 76fe6a4, 8094f92, 9a5a5b9, 5bbda2b)

  • Tracker JSDoc reframed for rc5's corrected API.md (once mid-phase, once after rc5's fix landed, to keep the barrel doc in sync).
  • fix(native): consumer-registered chains now win networkId collisions in the chain registry (see breaking-change note below) — previously the SDK's pre-seeded Ethereum mainnet (networkId 0) shadowed a consumer-registered devnet L1.
  • README bridge-tracking section + dangling-ref cleanup; CodeQL ReDoS flag on the baseUrl trailing-slash trim eliminated (regex → linear scan).

Upstream issue context

Testing evidence

  • npm run typecheck, npm run lint: clean.
  • npm run test:run: 113/113 unit tests passing across 5 files, including new registry-precedence tests (registry.test.ts, 6 tests) and the full tracker/aggregator/client suites (33 + 13 + 31 tests). The aggregator suite includes fixture-backed regressions for the L2→L2 premature-window/post-injection transition, the L2→L1 skip path, the 404 message-matching hazard, and the proxy-502 partial-failure path.
  • npm run build: green (ESM + CJS + .d.ts all build successfully).
  • Live-enclave smoke script (scripts/aggkit-smoke.ts) exercises the client + aggregator against a real kurtosis devnet, both direct and through aggkit-proxy across networks {0,1,2} (migration-phase baseline run: 117 checks PASS / 0 FAIL).
  • Registry-precedence tests include explicit proof-of-no-regression cases: no request is made to the default RPC when a consumer override wins the collision, confirming the fix doesn't change behavior for non-colliding consumers.
  • Adversarial review (10-item attack list, run across all three repos' diffs) flagged one fixable issue here: stale barrel JSDoc referencing pre-fix rc5 API.md wording, fixed in 76fe6a4 and re-verified (typecheck/lint clean, 113/113 tests). Full attack-list writeup lives in a local plans workspace, not linkable — summarized here.

Breaking-change notes

None for existing consumers — the entire aggkit module is additive (new type/value exports only), and the NATIVE surface is untouched. The chain-registry fix (8094f92) only changes behavior when a consumer explicitly registers a chain whose networkId collides with one of the SDK's built-in defaults — previously the built-in default silently won that collision; now the consumer's registration wins, which is the documented/expected precedence. No default-registry behavior changes for consumers who don't register overrides. Legacy fetchMerkleProof is retained (the NATIVE claim flow still uses it) and marked @deprecated.

Reviewer notes

Citation sweep (agglayer/aggkit#1791)

Contributes to agglayer/aggkit#1791.

Local design-doc citations in this branch were replaced with self-contained comments in commits 3879690 (sweep) + 5680d83 (review fix), with no behavior change: dist proven byte-identical after comment-stripping, 115/115 tests passing.

Merging this PR does not complete agglayer/aggkit#1793 — the sdk release is a separate manual workflow_dispatch step (.github/workflows/release.yml), and the dev-ui dependency swap to the published version must follow it. agglayer/aggkit#1793 is tracked independently and gets closed manually once both of those steps have happened.

Other issues to close

Closes agglayer/aggkit#1800.

arnaubennassar and others added 13 commits July 14, 2026 08:08
…egator

Add a self-contained `src/aggkit/` module for the aggkit bridge service
REST API (`/bridge/v1`), leaving the NATIVE module untouched:

- `AggkitBridgeClient`: typed single-network client (bridges, claims,
  claim-proof, l1-info-tree-index, token-mappings, sync-status, health)
  with snake_case query serialization, page_size<=200 handling,
  BigInt-safe global_index parsing, and typed `AggkitApiError`.
- `AggkitBridgeAggregator`: multi-network fan-out with partial-failure
  reporting, tiered status derivation (BRIDGED/LEAF_INCLUDED/
  READY_TO_CLAIM/CLAIMED), ready-to-claim counting, claim-proof
  orchestration, and token metadata composition.
- `ERC20.getMetadata()` on-chain read helper.
- Fixture-driven unit tests + a live-enclave smoke script.

Status derivation probes l1-info-tree-index by recording network (not
token origin) and confirms READY_TO_CLAIM candidates via a targeted
global_index claims query to avoid page-1-only false positives.

Legacy bridge-hub `fetchMerkleProof` marked @deprecated (unused by dev-ui).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E4tXyxxSecCKHA4uqsiN81
…lt tolerance

design.md (aggkit-proxy-l2l2 round) §2/§3 delta:
- getInjectedL1InfoLeaf (client.ts): new destination-side GER-injection
  probe, message-matched 404 "not injected" vs the proxy's unrelated 404
  "bridge service url not found" (§3.2).
- getSyncStatus now sends network_id explicitly — unqualified requests 400
  through aggkit-proxy (§2.3 gap G2).
- resolveInjectedLeafIndex + Tier-2b gate in toTransaction: an L2-destination
  row only reaches READY_TO_CLAIM once the destination's GER is actually
  injected, closing the F1/F2 premature-READY window (measured >=8.02s live)
  that would otherwise build a claim proof that reverts GlobalExitRootInvalid
  on-chain (§3.1-§3.4).
- getClaimInputs rewritten to build /claim-proof on the destination-injected
  index M (>= source index N), not the deposit's own L1-info-tree index —
  this was wrong for every L2-destination claim, including mainnet-relevant
  L1->L2 manual claims (§3.5). Adds an additive `sourceL1InfoTreeIndex` field.
- getActivity/getReadyToClaimCount: guard every per-row Tier-2 probe so one
  network's failure degrades to `failedNetworks` instead of rejecting the
  whole call (§2.2 gap G1).
- errors.ts: AggkitApiError.httpStatus JSDoc adds 502 (§2.5).

Additive only: one new client method, one added return field, two
behavioural fixes, one JSDoc line. No exports removed or renamed.

Fixture-backed regression tests added per §3.7 (8 required cases): the
L2->L2 premature-window/post-injection transition, L2->L1 skip-Tier-2b,
synthetic M>N (S2 shape), the 404 message-matching hazard, the proxy-502
partial-failure path, and getSyncStatus's network_id.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PCzWpoQMLj1HTXksvLbmpG
…ross {0,1,2}

design.md §8 S8 item 7: point AGGKIT_URL at aggkit-proxy (direct port or
haproxy /aggkitapi), configure the aggregator with networks {1,2}, and skip
the getHealth check in proxy mode (§2.4 gap G3 — no root health route behind
the proxy).

Adds: full fan-out activity across the configured L2s, an L2->L2 row's
derived status (asserts CLAIMED for the known-autoclaimed L2-1->L2-2
deposit), getClaimInputs for a real L2->L2 AND a real L2->L1 deposit
(printing both sourceL1InfoTreeIndex and leafIndex), and an opt-in
(RUN_PARTIAL_FAILURE_TEST=true) proxy-502 partial-failure section that stops
aggkit-002-bridge, asserts getActivity resolves with failedNetworks naming
only network 2 while network 1's rows still render, then restarts it.

Verified live against the cdk enclave (direct proxy port + haproxy
/aggkitapi, both byte-identical) — all checks pass, enclave healthy
afterward. See handoff-sdk.md for the recorded output.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PCzWpoQMLj1HTXksvLbmpG
Document AggKit proxy usage and transaction status state machine:

src/aggkit/index.ts:
- Add multi-network proxy configuration example showing same base URL
  under multiple networkId keys (enables devnet proxy multiplexing)
- Clarify that routing happens via ?network_id= query parameter

src/aggkit/types.ts:
- Document AggkitTransactionStatus lifecycle (BRIDGED → LEAF_INCLUDED →
  READY_TO_CLAIM → CLAIMED)
- Explain LEAF_INCLUDED state: source settled, destination GER not yet injected
  (common on fresh enclaves where L2 block height exceeds L1)
- Clarify that LEAF_INCLUDED only occurs for L2 destinations (L1 has no injection)

These docs describe the correct configuration pattern for 2-L2 devnets with
aggkit-proxy frontends, and explain the timing windows developers may observe
during claim-readiness polling (design.md §0.1–§3.3).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PCzWpoQMLj1HTXksvLbmpG
…M_ADDRESS

Re-running the S8 smoke script against the current enclave (recreated for the
S12 E2E round) produced "4 CHECK(S) FAILED" with no indication why: the default
FROM_ADDRESS is the EOA from one earlier round's lifecycle deposits, and bridge
history does not survive `kurtosis enclave rm`. Sections 3/5/6/7 all failed for
want of data, which reads identically to a genuine SDK regression.

The script now prints an explicit diagnostic when the fan-out returns an empty
activity page, naming the address and telling the operator to pass FROM_ADDRESS,
and the header documents the default as enclave-specific. The assertions still
fail (an empty page IS a smoke failure when you expected data) -- only the
diagnosis is added.

Confirmed with FROM_ADDRESS set to the dev-ui E2E wallet against the live
2-L2 enclave: every section passes, including the destination-injected
getClaimInputs checks (L2->L2 sourceL1InfoTreeIndex=16/leafIndex=16, L2->L1
18/18). typecheck, lint and 93 unit tests green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PCzWpoQMLj1HTXksvLbmpG
Types for aggkit v0.11.0-rc4's tracker/v1 REST API (docs/bridgetracker/API.md),
carrying raw numeric enums alongside their _string companions verbatim. Adds
AggkitBridgeClient.getBridgeTracking(txHash, networkId?) over the same
fetchRawText/AggkitApiError plumbing as the bridge/v1 endpoints, and
AggkitBridgeAggregator.getBridgeTracking(networkId, txHash), which routes L1
through a configured L2 instance via clientForNetworkOrL1 and passes
networkId through explicitly to the URL path (that routed-through instance's
own networkId is not 0).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014GNMDLHYqUSAJ3qb2GKfK3
…ormat

S4's tracker types were derived verbatim from docs/bridgetracker/API.md;
a live capture off a real rc4 enclave (plans/bridge-tracker/fixtures/)
proved the wire deviates further than that doc's own "numeric + _string
companion" convention suggests. tracking_status, bridge_type, step_name
(not `step`), and per-step status all ship as bare strings with no
numeric/companion fields; BridgeStatus nests leaf_type/origin/destination/
amount under an undocumented `event` object; per-step start_date/end_date/
result/error are omitted keys (not null) until applicable; and
CertificateData.settlement_tx_hash is omitted (not null) pre-Candidate.
error_type and CertificateData.status keep their documented numeric +
_string pairs, confirmed unchanged by the same fixtures.

Copies a representative fixture subset into src/aggkit/__fixtures__ and
adds src/aggkit/__tests__/tracker.test.ts covering URL construction
(including network-0 L1 routing through the aggregator), the
registered-only and giving-up-error shapes, and the resolved route
(step count/order/per-step results) for all three typologies.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014GNMDLHYqUSAJ3qb2GKfK3
Document getBridgeTracking's operational contract on both entry points
(AggkitBridgeClient and AggkitBridgeAggregator) and in the module's
top-level doc comment, matching the style of the earlier
multi-network-proxy/status-lifecycle docs (2ec0d5e):

- Terminal semantics: tracking_status === 'finished', or 'error' with
  bridge_status still null (the tracker giving up entirely). Per-step
  errors inside all_steps[i].error are explicitly non-terminal -- the
  tracker retries those on its own.
- Polling guidance: no push/subscription channel exists, so callers
  must poll; ~5s is a good default (matches the dev-ui consumer),
  stopping once the terminal condition above is met.
- Server-side registration/eviction: the first call for a
  (networkId, txHash) pair registers it (tracking_status:
  'registered', all_steps: null); a RetentionPeriod eviction of a
  still-pending bridge causes the same 'registered'/null-steps
  response on the next poll, not an error -- treat it like the
  original registration, not a regression.
- Cross-reference the aggregator's getBridgeTracking to
  AggkitBridgeClient's for the wire-format deviations already
  documented in types.ts (bare string enums, step_name vs step, etc.)
  rather than duplicating them.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014GNMDLHYqUSAJ3qb2GKfK3
… semantics

Two JSDoc claims contradicted aggkit v0.11.0-rc4's actual behavior:

- A per-step error does NOT leave tracking_status at 'running' -- aggkit
  derives tracking_status from the step at step_index
  (bridgetracker/domain/tracking_data.go), so a step in 'error' reports
  tracking_status 'error' with bridge_status populated. This is exactly
  why the documented terminal check needs the bridge_status === null
  conjunct rather than tracking_status alone.

- TrackingData.error is not "set only if the tracker gave up": the
  tracker_registered.json fixture carries a transient error at
  retry_count 1 while tracking_status is still 'registered' (retries in
  progress). Only the exhausted (2) case is the giving-up terminal.

Doc-only; no runtime change.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014GNMDLHYqUSAJ3qb2GKfK3
v0.11.0-rc5 (agglayer/aggkit#1784) rewrote docs/bridgetracker/API.md to
match the tracker's wire format exactly, closing #1781; the serializer
itself never changed between rc4 and rc5, and live rc5 fixtures confirm
identical shapes (plans/bridge-tracker-rc5/verification.md §4). Update the
tracker JSDoc in types.ts/client.ts/aggregator.ts so it no longer frames
the bare-string enums, event nesting, and omitted-optional-keys behavior
as an open API.md drift/bug — upstream docs now agree with what the SDK
already modeled from fixtures. No type-shape changes. Add an rc5
provenance note to the tracker fixture-loading test file confirming the
rc4 captures remain valid, live-verified unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014GNMDLHYqUSAJ3qb2GKfK3
`ChainRegistry.getChainByNetworkId()` previously returned the first
insertion-order match, so the SDK's pre-seeded default chains (e.g.
Ethereum mainnet at networkId 0) shadowed a consumer-registered chain
sharing the same networkId (e.g. a devnet L1, also at networkId 0).
Dormant in practice (console-triage.md) but real: an ERC-20 bridged
from devnet L1 whose token isn't in a UI's static list would resolve
mainnet's rpcUrl and hit https://eth.llamarpc.com instead of the
devnet RPC.

Track which chainIds are built-in defaults at construction time and
prefer a non-default (consumer-registered) match on networkId lookup
collisions, independent of registration order. No override still
resolves defaults exactly as before. Public API is unchanged.

Reconciles the TODO(aggkit-migration) at aggregator.ts:653-666.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014GNMDLHYqUSAJ3qb2GKfK3
- Add a "Bridge Transaction Tracking" README subsection: getBridgeTracking
  usage sample, polling/terminal-state guidance, the bare-string enum shape
  note, and the #1786 claim-readiness caveat.
- Replace local-only plans/ path citations in types.ts and tracker.test.ts
  with durable content: fixture provenance stated inline (no path), and
  agglayer/aggkit#1781/#1784 issue/PR references for the rc4->rc5 API.md
  correction, instead of a /home/brolygon/... verification doc path.
- Opportunistically strip the /home/brolygon/... absolute-path prefix from
  the pre-existing design.md/handoff-sdk.md citations in types.ts and
  scripts/aggkit-smoke.ts (username leak fix; the broader design.md sweep
  remains deferred).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014GNMDLHYqUSAJ3qb2GKfK3
fea252e reframed the per-type JSDoc in types.ts after aggkit v0.11.0-rc5
(PR #1784) corrected docs/bridgetracker/API.md to match the wire format,
but missed the module-level comment in this barrel, which still framed
the SDK's types as "deviating" from upstream docs. Post-rc5 the docs
agree with the types; only rc4's API.md described them differently.
Comment-only change.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014GNMDLHYqUSAJ3qb2GKfK3
Comment thread src/aggkit/client.ts Fixed
arnaubennassar added a commit to agglayer/agglayer-dev-ui that referenced this pull request Aug 10, 2026
…ailure cause

The old comment said "revert to a semver range once the aggkit SDK surface
ships to npm (tracked for S15)" but didn't mention that the file:../sdk
override is the direct reason `pnpm install --frozen-lockfile` fails on
GitHub CI (ENOENT scandir on the missing sibling ../sdk checkout) for both
the lint/typecheck/test and e2e workflows on PR #24. Rewrite the comment
to state that plainly and spell out the dependency ordering to clear it:
agglayer/sdk#28 merges -> a tracker-API sdk version is published via
workflow_dispatch -> swap this override to that published semver. Local
dev and local CI-equivalent checks are unaffected since a sibling ../sdk
checkout is present here; only GitHub's runners lack one. Comment-only
change, no functional effect: lint/typecheck/test (54 tests) stay green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014GNMDLHYqUSAJ3qb2GKfK3
CodeQL flagged `config.baseUrl.replace(/\/+$/, '')` (client.ts:70) as a
polynomial regex over library-supplied input (bridge check on PR #28):
a `+` quantifier anchored at `$` against an attacker/consumer-controlled
`baseUrl` is a recognized ReDoS shape, even though this specific pattern
has no exploitable nested-quantifier or overlapping-alternation blowup.

Replace the regex with `stripTrailingSlashes()`, a manual backward scan
that strips trailing '/' characters in a single linear pass -- same
semantics (trim any number of trailing slashes before appending
/bridge/v1 or /tracker/v1), no regex engine involved. The tracker
not-ready pattern matching (L1_INFO_TREE_INDEX_NOT_READY_PATTERNS /
INJECTED_L1_INFO_LEAF_NOT_READY_PATTERNS) was already plain
`.includes()` on a lowercased string -- not implicated by this alert --
and is unchanged.

Add regression tests: multiple trailing slashes, and a 50k-slash input
to cover the ReDoS-adjacent long-input case the original alert worried
about.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014GNMDLHYqUSAJ3qb2GKfK3
arnaubennassar and others added 2 commits August 11, 2026 14:03
…comments

Removes citations to machine-local planning documents (design.md, handoff-sdk.md,
manual-validation.md, RELEASE.md, console-triage.md, enclave-notes.md and their
§-numbered sections) from comments, test titles, and diagnostic strings across
the aggkit module. Every citation is replaced with the underlying fact inlined
directly, or dropped where the surrounding text already carried it — no
behavior change. Comment-only sweep; dist/ rebuild confirms every line that
differs in emitted output is itself a comment or JSDoc line.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014GNMDLHYqUSAJ3qb2GKfK3
Missed by the design-doc citation sweep (3879690): a bare `§` symbol with
no section number, left over from before that PR. Names the actual
regression coverage instead of a dangling symbol.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014GNMDLHYqUSAJ3qb2GKfK3
@arnaubennassar
arnaubennassar marked this pull request as ready for review August 14, 2026 14:17

@MaximusHaximus MaximusHaximus left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added some things I noticed in-line -- also posted briefly in Slack about the global_index issue 💖

Fundamentally, I think it would be ideal if the client was defined and published from the aggkit repository that owns the actual REST API interface, and this SDK used a versioned copy of that client rather than the client existing here, built entirely into the SDK - it solves a lot of issues at once. I'm going to open a PR against Aggkit repo showing what that would look like as an example 👍

Comment thread src/aggkit/aggregator.ts Outdated
Comment thread src/aggkit/aggregator.ts Outdated
Comment thread src/aggkit/aggregator.ts Outdated
Comment thread src/aggkit/aggregator.ts Outdated
Comment thread src/aggkit/__fixtures__/bridges_from_address.json
Comment thread src/aggkit/aggregator.ts Outdated
Comment thread src/aggkit/aggregator.ts
Comment thread src/aggkit/index.ts
Comment thread src/native/chains/registry.ts Outdated
Comment thread src/aggkit/httpRaw.ts Outdated
joanestebanr and others added 2 commits August 31, 2026 09:05
8094f92 made getChainByNetworkId() prefer a consumer-registered chain over
a still-default one on a networkId collision, but only covered a consumer
picking a brand-new chainId (e.g. a devnet L1). registerChain() never
removed a chainId from defaultChainIds, so a consumer *reusing* one of the
SDK's own default chainIds never won a collision -- both matches stayed
flagged as default and getChainByNetworkId() fell back to the first one
registered in initializeDefaultChains().

This bites agglayer-dev-ui in testnet mode: it registers its L1 with
Sepolia's real chainId (11155111), which the SDK also pre-seeds as a
default at networkId 0. Both Ethereum mainnet (chainId 1) and that
Sepolia registration stayed default-flagged, so getChainByNetworkId(0)
kept resolving to mainnet -- routing testnet's L1 token-metadata reads
(AggkitBridgeAggregator.getTokenMetadata) to eth.llamarpc.com instead of
Sepolia. Flagged in review on the companion dev-ui PR
(agglayer/agglayer-dev-ui#24, discussion_r3862947494).

registerChain() now deletes the chainId from defaultChainIds before
registering, so any re-registration -- new chainId or reused default one
-- graduates it to consumer-registered. registerDefaultChain() re-adds the
flag right after calling registerChain(), so construction-time seeding is
unaffected.

Adds 3 tests reproducing the reused-chainId (testnet/Sepolia) scenario,
the gap left by 8094f92's tests (which only exercised new chainIds 1337/
9999). 118/118 tests pass, typecheck and lint clean.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Addresses three zero-risk review comments on the aggkit bridge module:

- Mark src/aggkit/__fixtures__/** as linguist-generated in .gitattributes
  so GitHub collapses fixture diffs out of review. (3847551495)

- Fix the non-compiling `networks:` example in the src/aggkit/index.ts
  barrel JSDoc: AggkitAggregatorConfig.networks is Record<number, string>,
  not a map of {baseUrl, networkId} objects -- the latter would hand
  stripTrailingSlashes an object at runtime ("url.charAt is not a
  function"). (3862897795)

- Wrap the retry-exhaustion throw in src/aggkit/httpRaw.ts with
  { cause: lastError } so the original fetch error (stack, code like
  ECONNRESET) survives instead of being flattened into a message string.
  (3862898418)

  This required also adding "ES2022.Error" to tsconfig.json's `lib`
  array: under the prior "lib": ["ES2020"], TypeScript's ErrorConstructor
  only has a 0-1 arg overload, so `new Error(msg, { cause })` is a hard
  arity error (TS2554) regardless of runtime support. ES2022.Error only
  adds the Error.cause/ErrorOptions type declarations -- no change to
  `target` or emitted JS, and no other diagnostics appear anywhere else
  in the tree with it added. Confirmed the fix works at runtime today
  (Node's Error.cause has been supported for years, independent of the
  TS lib setting).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
joanestebanr and others added 8 commits September 3, 2026 16:34
fix: back getActivity with aggkit bridgetracker, drop client-side fan-out 30
PR CI only ran typecheck/lint/test/build, so Prettier drift went
undetected until the manual release workflow's Quality Checks step
caught it. Add format:check so it's gated on every PR/push to main.
src/aggkit/__fixtures__/*.json and types.ts had fallen out of sync
with Prettier config (unnoticed because PR CI didn't run format:check
until the previous commit). No behavioral change.
aggkit's bridgetracker (agglayer/aggkit#1823, PR #1829) adds two
protocol changes to `TrackingData`, on both the REST response and the
WebSocket `status` message:

- A new top-level `claim_status` field
  (`'pending' | 'readyToClaim' | 'claimed' | 'error'`) that replaces
  having to derive "ready to claim" by hand from `step_index`/
  `all_steps` — this SDK's own docs previously told consumers to do
  exactly that manual inspection.
- A new `WaitingL1InfoLeafAvailable` step, inserted immediately before
  `WaitingClaim` on all three routes (L1->L2, L2->L1, L2->L2), with its
  own `{ l1_info_tree_index }` result shape.

Adds `AggkitClaimStatus` and `AggkitWaitingL1InfoLeafAvailableResult`,
extends `AggkitBridgeStep`/`AggkitBridgeStepResult`/
`AggkitTrackingData` accordingly, exports the new types, and updates
the JSDoc/README guidance that referenced the old manual derivation.

No existing fixture under `__fixtures__/tracker_*.json` covers these
fields yet (all predate PR #1829) — left them untouched rather than
fabricating fixture data.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…lable_state-aggkit1823

feat(aggkit): add claim_status and WaitingL1InfoLeafAvailable step
aggkit's bridgetracker (agglayer/aggkit#1830, PR #1831) renames
`GET /tracker/v1/activity/from/{from_address}`'s per-bridge `claimed`
field to `claim_status`, revaluing it from a plain tri-state
(`'true'`/`'false'`/`'error'`) to the same `'pending'`/`'readyToClaim'`/
`'claimed'`/`'error'` vocabulary `TrackingData.claim_status` already used
(agglayer/aggkit#1823, PR #1829) — a consumer no longer needs
`includeTracking: true` plus a `tracking` inspection just to tell "still
pending" apart from "ready to claim": the tracker now resolves
`readyToClaim` directly against the bridge-service
`/l1-info-tree-index` + `/injected-l1-info-leaf` endpoints server-side
either way.

- `AggkitActivityItem.claimed` -> `claim_status: AggkitClaimStatus`,
  reusing the type PR #1829 introduced for `AggkitTrackingData`.
- `errors` may now carry a `readiness` key (in addition to the existing
  `claim` key) when the direct readiness probe itself fails while the
  bridge is still unclaimed — `claim_status` conservatively stays
  `'pending'` rather than surfacing as `'error'`.
- Updates the module docs in `types.ts`, `aggregator.ts` and `index.ts`
  that told consumers to derive readiness by filtering
  `claimed !== 'true'`, and adds a breaking-change writeup + inline note
  to README.md.
- Extends `activity.test.ts` with `readyToClaim` and `errors.readiness`
  coverage; renames existing tests/fixtures off the old field name.

No fixture yet captures this field (all `__fixtures__/tracker_*.json`
predate PR #1831).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…aggkit1830

feat(aggkit): activity endpoint reports claim_status like the tracker
aggkit-smoke.ts still referenced the old tri-state claimed field, which
was renamed to claim_status in 9b52c78. This broke the Quality Checks
type-check step in CI (run 34128060002).

@MaximusHaximus MaximusHaximus left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Spotted another occurrence of the global_index where it is not being manually fixed in the SDK; checked the source code and verified it would be silently corrupted by the JSON.parse(). Weirdly, the comment in types.ts even insists that it's already a string even though it isn't as far as I can tell in RC9. My only theory is that the swagger doc was trusted instead of the code being double-checked, or I'm looking at stale code references -- so I included links back to the code where I found the issue in aggkit. 🤔

Added a few other things in-line also

Comment thread src/aggkit/client.ts Outdated
Comment thread src/aggkit/types.ts Outdated
Comment thread README.md Outdated
Comment thread src/aggkit/index.ts Outdated
Comment thread src/aggkit/aggregator.ts Outdated
Comment thread src/aggkit/__tests__/activity.test.ts Outdated
Comment thread src/aggkit/types.ts Outdated
Comment thread src/aggkit/client.ts Outdated
Comment thread src/aggkit/aggregator.ts Outdated
Comment thread src/aggkit/client.ts Outdated
arnaubennassar and others added 7 commits September 9, 2026 07:46
…or to rc9

- getActivity now pre-quotes global_index via quoteGlobalIndex before
  JSON.parse: the tracker's activity endpoint embeds
  bridgeservicetypes.BridgeResponse unmodified, so it has the identical
  *big.Int bare-number defect as /bridges and /claims.
- AggkitActivityBridge.global_index's doc corrected: it is parsed from a
  bare JSON number (like AggkitBridge.global_index), not already a JSON
  string as previously documented.
- Raise the documented minimum supported aggkit version to v0.11.0-rc9 in
  README.md, src/aggkit/index.ts, and AggkitClaimStatus's doc: the
  tracker's activity endpoint doesn't exist before rc8, and claim_status
  on activity rows / AggkitTrackingData is undefined at runtime on rc8
  despite being declared required, until agglayer/aggkit#1829/#1831 (rc9).
- parsing.ts's quoteGlobalIndex doc extended to cover the activity
  endpoint's use of it, plus a full scope/safety writeup of the regex.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Gzd3Uy5KMYZGm4q7y4hpvc
…racker failover loop

The bridge tracker (/tracker/v1) is a separate aggkit service from the
per-network bridge services (/bridge/v1) — its own binary on its own
port unless an aggkit-proxy fronts both. Address it explicitly instead
of deriving it from a bridge-service URL:

- AggkitBridgeClientConfig gains optional trackerBaseUrl (falls back to
  baseUrl, correct only behind a fronting proxy).
- AggkitAggregatorConfig gains required aggkitProxyUrl, the tracker's
  root. AggkitBridgeAggregator now builds a dedicated trackerClient from
  it and routes getActivity/getBridgeTracking through that client.
- getActivity no longer loops over every configured network's client
  trying each in turn: that was never real failover, only N guesses at
  where the single tracker lives (each per-network client derived its
  own <baseUrl>/tracker/v1). With the tracker addressed explicitly there
  is one endpoint and nothing to fail over to, so getActivity now issues
  exactly one request and an AggkitApiError from the tracker propagates
  unchanged instead of being replayed per network and rewrapped as a
  plain Error.
- getBridgeTracking now prefers trackerClient over the per-network
  fallback (clientForNetworkOrL1): it previously always used the
  per-network client and could throw "no client configured for network
  0" with an empty networks map even while the tracker was reachable via
  aggkitProxyUrl, despite its JSDoc claiming unconditional proxy routing.

BREAKING CHANGE: AggkitAggregatorConfig.aggkitProxyUrl is new and
required. Every AggkitBridgeAggregator construction site must supply it
(the tracker's root URL — the same origin as `networks` values when an
aggkit-proxy fronts everything). getActivity's per-network failover loop
is removed; a tracker failure now propagates as a single, unwrapped
error instead of an aggregated "all configured networks failed" Error.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Gzd3Uy5KMYZGm4q7y4hpvc
includeTracking defaulted to true, so every getActivity call registered
every still-unclaimed bridge in the result with the tracker's supervised
list by default — a server-side write triggered by what looked like a
read. Default it to false, matching the tracker's own server-side
default (bridgetracker/api/activity_command.go:88-90): a plain read no
longer has this side effect.

claim_status already resolves the 'pending' vs. 'readyToClaim'
distinction server-side even with includeTracking: false, so most
callers don't need the per-row AggkitTrackingData under `tracking` at
all; it remains available by passing includeTracking: true explicitly.

BREAKING CHANGE: AggkitBridgeClient.getActivity / AggkitBridgeAggregator
.getActivity's includeTracking now defaults to false instead of true.
Callers that relied on item.tracking being present by default must pass
includeTracking: true explicitly.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Gzd3Uy5KMYZGm4q7y4hpvc
…ations

- AggkitActivityClaim.proof_local_exit_root / proof_rollup_exit_root are
  now optional: verified absent in a live rc9 capture, matching /claims'
  AggkitClaim, whose ClaimResponse source declares these
  *Proof `json:",omitempty"`.
- Corrected the getActivity from_address comment: it is a hex string
  (common.Address via MarshalText), not a byte array — swagger's `type:
  array, items: integer` is swag's rendering of the underlying [20]byte,
  not the actual wire JSON. Notes the encoding is not uniform across the
  response (bridge.from_address keeps mixed case; claim.from_address is
  always "").
- Dropped the stale internal "audit finding C1-C4" / "S7" / "S16" /
  "S19 scratch-note" review-round references throughout client.ts,
  aggregator.ts and types.ts, replacing each with a self-contained
  pointer to the actual doc/rationale it referred to (including one
  citation that was line-wrapped across a JSDoc break, so it would not
  have been caught by a line-based grep for the old phrasing).
- Fixed the stale `{ not-injected, detail }` shorthand in
  resolveInjectedLeafState's doc to match the real
  `{ kind: 'not-ready', reason, detail }` shape.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Gzd3Uy5KMYZGm4q7y4hpvc
…ity fixtures

Update the activity/aggregator/client/tracker test suites for this
round's changes: the ~34 AggkitAggregatorConfig/AggkitBridgeClientConfig
construction sites now supply aggkitProxyUrl/trackerBaseUrl, getActivity
assertions cover the includeTracking:false default and the
quoteGlobalIndex pre-quoting, and the tracker-failover-loop tests are
replaced with single-request-through-trackerClient coverage.

Add two live wire-format fixtures captured from a running aggkit
devnet (docs/provenance in __fixtures__/README.md):
activity_rc9_live_prefix.json (rc9, the global_index bare-number defect
on the activity endpoint's embedded BridgeResponse) and
activity_rc9_live_postfix.json (an aggkit build with agglayer/aggkit#1835
applied, proving quoteGlobalIndex is idempotent over the fixed wire
format). Both pin the same logical deposit across two deterministic
devnet runs.

Add .prettierignore for src/aggkit/__fixtures__/*.json: these are
byte-identical captures that activity.test.ts asserts against as raw
substrings, so reformatting them would break byte-fidelity with the
capture.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Gzd3Uy5KMYZGm4q7y4hpvc
…ertions

Add AGGKIT_TRACKER_URL (defaults to AGGKIT_URL under PROXY_MODE=true;
required standalone) to supply AggkitAggregatorConfig.aggkitProxyUrl,
and log it alongside the existing base/proxyMode/networks line.

Add live assertions against the running devnet for the two review
items this round's activity work exercises: the L1-origin global_index
precision workaround (agglayer/aggkit#1835) and the default
includeTracking: false path (the smoke run's activity call passes only
{ fromAddress }, so it exercises the new default rather than the
opt-in tracking-registration path).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Gzd3Uy5KMYZGm4q7y4hpvc
Add a "Bridge Tracking & Activity" README section explaining the two
distinct aggkit services AggkitBridgeAggregator now talks to (the
per-network bridge service in `networks` vs. the single bridge tracker
at `aggkitProxyUrl`), a config example showing both, and the
`includeTracking` default-false / server-side-write-on-true semantics
for getActivity, with a `claim_status`-filter example for ready-to-claim
bridges.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Gzd3Uy5KMYZGm4q7y4hpvc
arnaubennassar and others added 2 commits September 9, 2026 12:06
fix(aggkit)!: resolve PR #28 review round 2 — activity global_index precision, required aggkitProxyUrl, rc9 floor
CI resolves prettier@3.6.2 from bun.lock, but a prior husky pre-commit
hook ran with a locally-drifted prettier@3.9.5, which reformatted
src/aggkit/types.ts into a style that 3.6.2 flags as non-compliant
(unbroken single-line union types). Reformat the file with the
bun.lock-pinned 3.6.2 binary so `format:check` agrees locally and in
CI; no semantic changes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Gzd3Uy5KMYZGm4q7y4hpvc
arnaubennassar added a commit to agglayer/aggkit that referenced this pull request Sep 9, 2026
…, not bare JSON number (#1835)

## 🔄 Changes Summary
`BridgeResponse.GlobalIndex` (`bridgeservice/types/types.go`) was
declared as `*big.Int`. `math/big`'s own `MarshalJSON` writes unquoted
decimal digits, so `global_index` went out on the wire as a **bare JSON
number**, even though the struct tag said `swaggertype:"string"` and the
generated swagger declared `type: string`. Every sibling `GlobalIndex`
field in the same file (`ClaimResponse.GlobalIndex`,
`UnsetClaimResponse.GlobalIndex`, `SetClaimResponse.GlobalIndex`)
already used the repo's own `BigIntString` wrapper — `BridgeResponse`
was the lone holdout.

For an L1-origin deposit, `global_index = 2^64 + deposit_count`
(`bridgesync/processor.go`), which exceeds JavaScript's
`Number.MAX_SAFE_INTEGER`. A plain `JSON.parse` on the bare-number form
silently rounds it to a **different** value, which can resolve to a
different bridge/claim entirely.

This PR switches `BridgeResponse.GlobalIndex` to `BigIntString`,
matching the other three fields, and updates the two call sites:
- `NewBridgeResponse` (`bridgeservice/utils.go`) now writes
`globalIndex.String()` into the wrapper.
- `bridgetracker/sources/activity.go`'s `ActivitySource.ClaimInfo`
recovers a `*big.Int` via `.ToBigInt()` where the downstream client API
still expects one.
- `bridgetracker/activity.go`'s cache uses the string form directly as
its map key (previously `.String()` on the `*big.Int`, now a plain
string cast) — this is an equality-lookup key, never sorted, so no
ordering behavior changes.
- `test/e2e/bridge_utils.go` and `test/e2e/proxy_tracker_test.go` call
`.ToBigInt()` where `GlobalIndex` is passed into contract-binding calls
that expect `*big.Int`.

## ⚠️ Breaking Changes
- 🛠️ **Config**: N/A
- 🔌 **API/CLI**: **Yes.** `GET /bridge/v1/bridges` and `GET
/tracker/v1/activity/from/{from_address}` now emit `global_index` as a
JSON **string** instead of a bare JSON **number**. Any consumer that
parses `global_index` as a numeric type (e.g. `JSON.parse` mapping it
straight to a JS `number`) must switch to reading/parsing it as a
string. Note that the generated swagger schema for these fields is
**unchanged** — it already declared `global_index` as `type: string`
(via the `swaggertype:"string"` override on the old `*big.Int` field);
only the runtime wire encoding changes to finally match the documented
contract.
- 🗑️ **Deprecated Features**: N/A

## 📋 Config Updates
- N/A

## ✅ Testing
- 🤖 **Automatic**:
- New: `TestNewBridgeResponse_GlobalIndexMarshalsAsQuotedString`
(asserts the quoted form is present and the unquoted form absent),
`TestBigIntString_ToBigIntRoundTrip`,
`TestActivityItemMarshalJSON_EmbeddedBridgeGlobalIndexIsQuotedString`
(covers the tracker's embedded copy of the bridge in activity items).
  - `make lint` → `0 issues.`, exit 0.
- Full non-e2e unit suite: 94 packages `ok`, 0 FAIL, including
`bridgeservice`, `bridgeservice/types`, `bridgetracker`,
`bridgetracker/api`, `bridgetracker/sources`.
- `make generate-swagger-docs` produced a byte-identical result — zero
generated-file diff, since the swagger already said `type: string`.
- 🖱️ **Manual**: Live before/after capture from a 2-L2 anvil devnet
(aggkit-proxy serving `/bridge/v1` and `/tracker/v1`), same deposit
position (L1-origin, `deposit_count 2`, `global_index 2^64+2 =
18446744073709551618`), `claim_status: claimed`. The two captures come
from two runs of a deterministic environment at the same deposit
position, not one continuous chain.

Before (unmodified `v0.11.0-rc9`), `GET
/tracker/v1/activity/from/0x8943...`:
  ```
  "global_index":18446744073709551618
  ```
Unquoted — a plain `JSON.parse` on this yields `18446744073709552000`
(`typeof number`), a different number than the one on the wire.

Interestingly, that same pre-fix payload also contained the sibling
`ClaimResponse` field, already on `BigIntString`, right next to it:
  ```
  "global_index":"18446744073709551618"
  ```
quoted — one response demonstrating both the bug and the sibling field
that proves the fix is right.

  After (this branch), same endpoint:
  ```
  "global_index":"18446744073709551618"
  ```
Quoted, zero unquoted occurrences anywhere in the payload. `JSON.parse`
now yields the exact string `"18446744073709551618"` (`typeof string`).

Same result on `GET /bridge/v1/bridges?network_id=0`: all three
L1-origin rows go from bare numbers to quoted strings.

## 🐞 Issues
- Closes #1820

## 🔗 Related PRs
- agglayer/sdk#28 — the consumer that surfaced this defect.
- #1816 — a related contract-first POC covering the same
defect class (reference only; this PR does not supersede it).

## 📝 Notes
- Chose `BigIntString` over a custom `MarshalJSON` on `*big.Int` because
it matches all three sibling `GlobalIndex` fields already in this file,
and keeps the Go *type* itself honest about the wire encoding, rather
than fixing the bytes on the wire while leaving the type inconsistent
with its siblings and hiding the encoding behind a method.
- The generated swagger schema does not change: `global_index` was
already documented as `type: string` (via `swaggertype:"string"` on the
old `*big.Int`), and `BigIntString` is natively a Go string, so the docs
and the runtime now finally agree — no schema diff, but a genuine
wire-format change for consumers parsing the bare number.
- `agglayer/sdk`'s client-side `quoteGlobalIndex` workaround (added to
cope with the bare-number encoding) should stay in place until its
minimum supported aggkit version contains this fix — it was verified
idempotent over the new quoted form, so it keeps working unchanged after
this lands.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

https://claude.ai/code/session_01Gzd3Uy5KMYZGm4q7y4hpvc

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
@joanestebanr

Copy link
Copy Markdown
Contributor

@MaximusHaximus

Spotted another occurrence of the global_index where it is not being manually fixed in the SDK; checked the source code and verified it would be silently corrupted by the JSON.parse(). Weirdly, the comment in types.ts even insists that it's already a string even though it isn't as far as I can tell in RC9. My only theory is that the swagger doc was trusted instead of the code being double-checked, or I'm looking at stale code references -- so I included links back to the code where I found the issue in aggkit. 🤔

The issue related to global_index has been fixed in bridge service: agglayer/aggkit#1835

It has also been released in https://github.com/agglayer/aggkit/releases/tag/v0.11.0-rc10

Let us known if there is anything else left to address

joanestebanr and others added 3 commits September 16, 2026 11:35
The bridge tracker's proxy backend now reports a step status of
"skipped" (with an accompanying error_type 3 / "skipped") when a step
no longer needs verifying, e.g. because the bridge was already claimed
on the destination network. Add the missing status/error-type values
to AggkitStepStatus/AggkitTrackerErrorType(String) and correct the
JSDoc claiming `error` only ever appears when status is 'error'.

Fixes #38

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
A live capture against a real tracker (network 81) shows that a
'skipped' step does not reliably carry start_date/end_date/error: only
the step that had already started retrying before being superseded
keeps those fields (with a transient, not error_type-3, error), while
every step downstream of it is entirely bare -
{step_index, step_name, status: 'skipped'} and nothing else. Correct
the JSDoc that overclaimed 'error' always accompanies 'skipped', and
add the live fixture plus a test covering the bare-step shape.

Fixes #38

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
fix: support new tracker step status "skipped" 38

@MaximusHaximus MaximusHaximus left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❇️ all my prior feedback is addressed --- approving, I don't see anything blocking here.

The inline comments are all nits / non-blocking bookkeeping — take them or leave them 🤷 :)

One thing I think should be fixed before merge: the PR description is pretty out of date now. It still describes getReadyToClaimCount(), the BRIDGED/LEAF_INCLUDED/READY_TO_CLAIM/CLAIMED derivation, failedNetworks, "113/113 tests" (it's 188 now), and "NATIVE surface is untouched".

It also doesn't mention anything that landed from #32/#33/#35/#36/#37/#39 — the tracker-backed getActivity (no pagination, warnings, claim_status), the required aggkitProxyUrl, the rc9 floor, the two ! breaking commits, or the skipped step status.

Would be great to rewrite it to describe what's actually merging, so anyone (or any agent 🤣) reading the history later gets the right picture 👍

Comment thread README.md
is resolved server-side even without `includeTracking: true` — no `tracking`
snapshot required on the item to tell them apart.

**Caveat ([agglayer/aggkit#1786](https://github.com/agglayer/aggkit/issues/1786), OPEN)**:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Non-blocking: aggkit#1786 closed as completed on 09-15, so the "OPEN" and "until #1786 itself closes" wording here is stale now. Worth finding which aggkit release carries the fix, then either bumping the floor to it or rewording the caveat. It matters a bit more now that readyToClaim comes from the tracker instead of the SDK's own GER-injection check. getClaimInputs still guards against acting early, so the worst case is a claimable: false, not a revert 👍

Comment thread src/aggkit/parsing.ts
* of aggkit's own `BigIntString` wrapper, so it serializes as a bare number
* even though aggkit's swagger declares it a string. Tracked at
* https://github.com/agglayer/aggkit/issues/1820, fixed by
* https://github.com/agglayer/aggkit/pull/1835 — delete this workaround once

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Non-blocking, optional: #1835 is in rc10 and rc11 now. If the floor moves for #1786 anyway, this workaround's own removal condition is met and it can go.

Comment thread src/aggkit/client.ts
* (plus the 503) threw. `/claim-proof` is the LAST call `getClaimInputs` makes
* — the source is settled and the destination has injected — so throwing there
* rendered a hard failure for a transient wait. This activates the not-ready
* arm design §2.2/§2.6 reserved for it; no aggregator change is needed because

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: design §2.2/§2.6 still points at a local design doc. Same at L435 and types.ts:197. Inline the rationale or drop the pointer.

Comment thread src/aggkit/types.ts
* `:1123-1124`) — a single wire state as far as a consumer is concerned; the
* exact prose travels in `detail`. On rc4/rc5 all five answered 500 and threw.
*
* This is the member design §2.2/§2.6 reserved when `getClaimProof` was given

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: same dangling design §2.2/§2.6 citation as client.ts:213.

Comment thread src/aggkit/aggregator.ts

/**
* Picks the aggkit instance that can answer for `recordingNetworkId` — the
* network whose local exit tree recorded the deposit (comment 3847422009).

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: there are ~45 references like comment 3847422009 / PR #33 review across src/ (plus C1/C2/C3 audit labels in the test names). They go stale the moment this merges, and nobody reading the code later will have the context. Would be nice to strip them and keep only the why.

Comment thread src/aggkit/aggregator.ts
// unmodified, and `detail` is aggkit's own wording propagated verbatim
// rather than re-fabricated by the caller.
//
// Was `kind: 'not-injected'` with the reason hard-coded to

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: history narrative ("Was kind: 'not-injected'…"). Same with "It used to loop…" in getActivity's JSDoc and the FIXED: block in getTokenMetadata. That belongs in commit messages, not in the code.

Comment thread src/aggkit/aggregator.ts
* `claim_status === 'readyToClaim'`, exactly as this result's `bridges`
* already has to be interpreted for status display.
*/
async getActivity(params: {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Non-blocking bookkeeping: #30 and #31 both describe code that doesn't exist any more (client-side pagination/fan-out, getReadyToClaimCount). Close them, or rescope to what still applies. The one real leftover I see is that the tracker returns an address's entire history in one response, unpaginated. #38 is also still open even though #39 fixed it.

Comment thread src/aggkit/aggregator.ts
// the wrong tree (comment 3847422009).
if (
'originNetworkId' in params &&
(params as unknown as Record<string, unknown>)['originNetworkId'] !==

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: as unknown as Record<string, unknown> double cast. After the in check, reading the value through a Record<string, unknown>-typed binding (or Reflect.get) avoids the cast chain.

Comment thread src/aggkit/client.ts
txHash: string,
networkId: number = this.networkId
): Promise<AggkitTrackingData> {
const url = `${this.trackerApiUrl}/network/${networkId}/tx/${txHash}`;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: txHash goes into the path without encodeURIComponent. getActivity does encode fromAddress. Harmless for well-formed hashes, but it should be consistent.

@joanestebanr
joanestebanr merged commit ec7caf7 into main Oct 1, 2026
4 checks passed
joanestebanr pushed a commit to agglayer/agglayer-dev-ui that referenced this pull request Oct 13, 2026
…ailure cause

The old comment said "revert to a semver range once the aggkit SDK surface
ships to npm (tracked for S15)" but didn't mention that the file:../sdk
override is the direct reason `pnpm install --frozen-lockfile` fails on
GitHub CI (ENOENT scandir on the missing sibling ../sdk checkout) for both
the lint/typecheck/test and e2e workflows on PR #24. Rewrite the comment
to state that plainly and spell out the dependency ordering to clear it:
agglayer/sdk#28 merges -> a tracker-API sdk version is published via
workflow_dispatch -> swap this override to that published semver. Local
dev and local CI-equivalent checks are unaffected since a sibling ../sdk
checkout is present here; only GitHub's runners lack one. Comment-only
change, no functional effect: lint/typecheck/test (54 tests) stay green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014GNMDLHYqUSAJ3qb2GKfK3
joanestebanr added a commit to agglayer/agglayer-dev-ui that referenced this pull request Oct 13, 2026
Picks up the ChainRegistry networkId-collision fix for reused default
chainIds (agglayer/sdk#28, commit b9a990c): registerChain() now graduates
a re-registered chainId out of defaultChainIds even when it reuses one of
the SDK's own defaults (e.g. Sepolia's real chainId, 11155111). Previously
testnet mode's L1 token-metadata reads (add-custom-token, ERC20 name/
symbol/decimals) resolved to the SDK's pre-seeded Ethereum mainnet entry
instead of Sepolia, routing eth_call requests to eth.llamarpc.com.

This snapshot is the current tip of feat/aggkit-bridge-client, so it also
carries PR #32's follow-up fixes on top -- notably a breaking change to
AggkitBridgeAggregator.getClaimInputs (originNetworkId -> recordingNetworkId,
and a claimable result union instead of a thrown not-ready error). See the
companion commit migrating app/hooks/useClaimExecution.ts to match.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Adapt js/ts SDK to aggkit backend (proxy)

4 participants