Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
4d651f8
feat: migrate backend from Bridge Hub API to aggkit bridge service
arnaubennassar Jul 14, 2026
0ec82c9
fix(devnet): switch wallet to devnet on connect, proxy RPCs, live sta…
arnaubennassar Jul 17, 2026
bae015a
feat(claim): per-route autoclaim grace period before manual claim button
arnaubennassar Jul 17, 2026
7d64533
feat(devnet): generalize devnet script for 2-L2 enclave + config vali…
arnaubennassar Aug 3, 2026
635a3c9
fix(transactions): clear status filter on "All transactions" instead …
arnaubennassar Aug 3, 2026
a408c8e
feat(e2e): destination-chain parameterization + L2->L2 and manual-cla…
arnaubennassar Aug 3, 2026
5138771
fix(e2e): fund manual-claim's own L2-1 balance-tree top-up; fix ambig…
arnaubennassar Aug 3, 2026
51804e3
docs: devnet 2-L2 config and E2E testing guide
arnaubennassar Aug 3, 2026
4efc1ef
fix(e2e): remove l2-to-l2's inherited balance-tree credit; validate d…
arnaubennassar Aug 3, 2026
0f0ffaf
fix(wallet): enforce chain against the wallet's actual chain, not wag…
arnaubennassar Aug 4, 2026
162c577
chore(bridge): log the caught bridge-execution error to the console
arnaubennassar Aug 4, 2026
efd0cb8
feat(bridge-tracking): add useBridgeTracking hook for aggkit tracker …
arnaubennassar Aug 7, 2026
efea40c
feat(bridge-tracking): render tracker progress bar with step dots + t…
arnaubennassar Aug 7, 2026
7cba0a0
feat(bridge-tracking): show full tracker timeline in transaction deta…
arnaubennassar Aug 7, 2026
8d2bce0
test(bridge-tracking): unit-test tracker polling gates and render states
arnaubennassar Aug 7, 2026
dc1c2c1
fix(bridge-tracking): hide tracker bar/detail on a live CLAIMED trans…
arnaubennassar Aug 7, 2026
a1ce0d0
test(bridge-tracking): add e2e spec for the tracker progress bar and …
arnaubennassar Aug 7, 2026
fc5eeee
docs(bridge-tracking): document tracker UI in README
arnaubennassar Aug 7, 2026
1bcb542
test(bridge-tracking): model step-level error as tracking_status 'err…
arnaubennassar Aug 7, 2026
bc5c23c
fix(bridge-tracking): keep polling after a hard query error
arnaubennassar Aug 10, 2026
a6ad237
fix(bridge-tracking): stop calling WaitingClaim "Ready" before it is
arnaubennassar Aug 10, 2026
197fbc2
fix(wallet): fix metadata.url and degrade Reown gracefully without a …
arnaubennassar Aug 10, 2026
ec5ba2f
docs(prod-readiness): vendor team standards, graduate deploy guide, f…
arnaubennassar Aug 10, 2026
6704145
docs(pnpm-workspace): reconcile sdk override comment with actual CI-f…
arnaubennassar Aug 10, 2026
ede8c0e
docs: replace local design-doc citations with self-contained comments
arnaubennassar Aug 11, 2026
2d64ea9
docs: fix stale networkId-0 ChainRegistry caveat in aggLayerSdk.tsx
arnaubennassar Aug 11, 2026
dd070d7
feat: runtime config loading, Docker image, and GHCR publishing
arnaubennassar Aug 11, 2026
af57135
docs: note that docker-publish must be on the default branch
arnaubennassar Aug 12, 2026
a8c5c37
ci(e2e): unblock frozen-lockfile install and add devnet CI scaffolding
arnaubennassar Aug 12, 2026
cd11e71
ci(e2e): vendor devnet snapshot bundle and wire up hermetic bridge run
arnaubennassar Aug 12, 2026
e4f9069
fix(e2e): build the sibling agglayer/sdk checkout before installing
arnaubennassar Aug 12, 2026
4965b36
fix(e2e): point pnpm/action-setup at dev-ui/package.json
arnaubennassar Aug 12, 2026
e0d6f39
docs: document the CI-devnet path, config.ci.devnet.json, and cross-l…
arnaubennassar Aug 12, 2026
c0fcbd7
fix(ci,docs): adversarial-review fixes for the hermetic e2e path (S17)
arnaubennassar Aug 12, 2026
a9a9aac
fix(deps): bump @agglayer/sdk to published snapshot 1.0.0-snapshot-56…
arnaubennassar Aug 13, 2026
5f00a79
feat(config): split aggkitProxy from aggkitBridgeApis, fix devnet dup…
arnaubennassar Aug 13, 2026
b4fb5d9
docs(evidence): add D0b GHCR publish and PR CI evidence
arnaubennassar Aug 13, 2026
7925633
docs(evidence): record final PR #24 CI status including e2e
arnaubennassar Aug 13, 2026
2da5992
feat(config): move mainnet and testnet onto aggkitProxy too
arnaubennassar Aug 13, 2026
8563dd4
feat(config)!: remove the aggkitBridgeApis map form entirely
arnaubennassar Aug 13, 2026
9c42f43
docs(evidence): record D0c evidence (aggkitProxy migration + map-form…
arnaubennassar Aug 13, 2026
d95d9d6
fix(e2e): migrate e2e.yaml to NEXT_PUBLIC_AGGKIT_PROXY, restore parti…
arnaubennassar Aug 14, 2026
bf790fc
fix(e2e): scope partial-failure banner assertion to avoid strict-mode…
arnaubennassar Aug 14, 2026
ad292ff
docs(evidence): record D0d final green PR #24 checks and bridge suite…
arnaubennassar Aug 14, 2026
1b4681a
docs(evidence): reconcile D0d evidence with the actual final HEAD commit
arnaubennassar Aug 14, 2026
7668177
chore(evidence): de-pollute PR with orchestration evidence files
arnaubennassar Aug 14, 2026
89f3917
feat(config): make WalletConnect project id runtime-configurable in D…
arnaubennassar Aug 14, 2026
3c57eaf
feat(devnet): re-vendor v2 bundle (9 services, digest-pinned) + built…
arnaubennassar Aug 14, 2026
07e25e9
fix(tests): add required walletConnect.projectId to container fixtures
arnaubennassar Aug 14, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Docker build context excludes.
#
# NOTE: .sdk-src/ is deliberately NOT excluded here -- the sdk-builder stage
# in the Dockerfile needs it in the build context (see
# plans/dev-ui-docker-ghcr/d2-adr-dependency-strategy.md §4.4).
# TEMPORARY (this note) -- remove per that ADR's §5 once .sdk-src/ itself goes away.

node_modules
.next
.next-partial-failure
out
# `**/.git` (not just `.git`): a .dockerignore pattern is matched against the
# whole path relative to the context root, so a bare `.git` excludes ONLY the
# top-level one. In CI the sdk is staged by `actions/checkout` (see
# .github/workflows/docker-*.yaml), which writes a real `.sdk-src/.git` --
# without this pattern the entire agglayer/sdk history would be uploaded into
# the build context and baked into the sdk-builder layer (and, with
# docker-pr.yaml's `cache-to: type=gha,mode=max`, into the Actions cache).
# It also restores the parity that scripts/stage-sdk-src.sh documents: locally
# `git archive` yields tracked source with no `.git`, so CI must match.
**/.git
test-results
playwright-report
blob-report
coverage

# Secrets / build-time env files. A-1 §6.3(b) binding constraint: never let a
# developer's local .env.local (or .env, .env.staging) leak into the image --
# .env.local in particular can carry a real Reown project ID or devnet
# overrides. .env.production is the one exception: it is git-tracked, contains
# only a NEXT_PUBLIC_PROJECT_ID placeholder (verified), and `pnpm run
# build:production` copies it to .env.local *inside* the build, so it must be
# present in the build context for that RUN step to find it.
.env*
!.env.production

*.tsbuildinfo
.DS_Store
37 changes: 35 additions & 2 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,6 +1,39 @@
# Optional, LOCAL DEV ONLY. Get a real id at https://cloud.reown.com. Leaving
# the placeholder (or empty) runs AppKit in degraded `basic` mode:
# injected-wallet connect fully works; WalletConnect-cloud features (wallet
# directory images, remote config) are skipped and a handful of benign
# 401/403 console lines from Reown endpoints remain.
#
# This is a dev-time convenience only -- it overrides config.json's
# `walletConnect.projectId` when set (same precedence as
# NEXT_PUBLIC_AGGKIT_PROXY below). For a Docker deployment, set the real
# project id in the mounted config.json instead (see docs/docker.md and
# docs/config.md) -- this env var is BUILD-TIME ONLY (Next.js inlines it into
# the JS bundle) and has NO EFFECT in a prebuilt container image, and
# build:production/.env.production deliberately never sets it, precisely so
# a published image is not stuck with whatever value happened to be present
# at build time.
NEXT_PUBLIC_PROJECT_ID=YOUR_PROJECT_ID_HERE
# Optional: overrides config.json bridgeHubApiBaseUrl when set.
# NEXT_PUBLIC_BRIDGE_HUB_API=http://localhost:8080
# Optional: overrides the active mode's config.json aggkitProxy when set (a
# single bare URL, not JSON -- fanned out to every non-L1 network in the mode).
# Used to inject the enclave's ephemeral proxy port for devnet bring-up.
# Build-time only: Next.js inlines NEXT_PUBLIC_* vars into the JS bundle at build
# time, so this has NO EFFECT in a prebuilt container image -- see docs/docker.md.
# In a container the mounted config.json is the only configuration mechanism.
# NEXT_PUBLIC_AGGKIT_PROXY=http://127.0.0.1:33518/aggkitapi
# E2E only: Playwright reads this and injects the derived values automatically.
# Never set NEXT_PUBLIC_E2E_PRIVATE_KEY directly.
E2E_PRIVATE_KEY=0xYOUR_E2E_PRIVATE_KEY

# E2E only, all optional -- defaults target the local Kurtosis `cdk` devnet
# (scripts/kurtosisDevnetEnv.mjs). Set E2E_BACKEND_MODE=testnet to instead
# run against real Sepolia/Bokuto testnet infrastructure. See
# app/constants/e2e.ts for the full default values per mode and
# README.md#testing for details.
# E2E_BACKEND_MODE=devnet
# E2E_FROM_CHAIN_ID=271828
# E2E_ERC20_ADDRESS=0xYOUR_ERC20_ADDRESS # devnet: auto-resolved/deployed by Playwright globalSetup if unset
# E2E_NATIVE_BRIDGE_AMOUNT=0.001
# E2E_ERC20_BRIDGE_AMOUNT=0.01
# E2E_BRIDGE_SUCCESS_TIMEOUT_MS=60000
# E2E_CLAIM_TIMEOUT_MS=150000
19 changes: 18 additions & 1 deletion .env.production
Original file line number Diff line number Diff line change
@@ -1 +1,18 @@
NEXT_PUBLIC_PROJECT_ID=production-project-id-placeholder
# build:production copies this file over .env.local (see Dockerfile) so a
# production build never inherits a developer's local .env.local overrides
# (NEXT_PUBLIC_AGGKIT_PROXY, E2E_* vars, etc.) or a stale NEXT_PUBLIC_PROJECT_ID.
#
# Deliberately empty: NEXT_PUBLIC_PROJECT_ID must NOT be set here. The
# WalletConnect/Reown project id is a prod-required value that now lives in
# the RUNTIME config.json (walletConnect.projectId, see docs/config.md and
# entrypoint.sh) so it can be set per container instance without a rebuild.
# If this file set NEXT_PUBLIC_PROJECT_ID to anything (even the placeholder),
# that value would be inlined into the JS bundle at build time and would
# permanently override config.json's value in every container built from
# this image -- reintroducing the exact bug this file exists to avoid (see
# app/config.ts's resolveProjectIdOverride precedence comment).
#
# The Cloudflare Workers deploy path (wrangler, see wrangler.toml) is
# unaffected: deploy.yaml sets NEXT_PUBLIC_PROJECT_ID as a real shell
# environment variable before invoking this build, and Next.js's env loader
# never overrides an already-set process.env value with one from a .env file.
2 changes: 1 addition & 1 deletion .github/workflows/deploy.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,4 @@ jobs:
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CF_WORKER_ACCOUNT_ID }}
CLOUDFLARE_API_TOKEN: ${{ secrets.CF_WORKER_API_TOKEN }}
NEXT_PUBLIC_PROJECT_ID: ${{ secrets.NEXT_PUBLIC_PROJECT_ID }}
NEXT_PUBLIC_BRIDGE_HUB_API: ${{ secrets.NEXT_PUBLIC_BRIDGE_HUB_API }}
NEXT_PUBLIC_AGGKIT_PROXY: ${{ secrets.NEXT_PUBLIC_AGGKIT_PROXY }}
202 changes: 202 additions & 0 deletions .github/workflows/docker-pr.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
# PR-time image build + smoke test for agglayer-dev-ui.
#
# Closes plan gap P4 (plans/dev-ui-docker-ghcr-plan.md): before this
# workflow, nothing in CI built or smoke-tested the Docker image, so a
# broken Dockerfile or config contract only surfaced at deploy time. This
# workflow builds the image on every pull request (single arch, never
# pushed) and smoke-tests it, so a regression turns the PR red instead.
#
# Deliberately separate from docker-publish.yaml (the release/dispatch
# workflow): that workflow publishes multi-arch images to GHCR and must
# never run on untrusted PR heads with push credentials in scope. This
# workflow never logs in to a registry and never sets `push: true`.
#
# Per plans/dev-ui-docker-ghcr/d2-adr-dependency-strategy.md §6.3, the
# "skip if blocked by sdk#28" escape hatch that the plan originally allowed
# for this step is WITHDRAWN: D-2 proved the image builds today without
# waiting on sdk#28 (§3, verified build EXIT=0), so this job always builds
# and smoke-tests -- no skip condition, no continue-on-error, no `if:`
# guard tied to sdk#28.
name: Docker PR Build and Smoke Test

on:
pull_request:
branches: [main]

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

permissions:
contents: read

env:
IMAGE_TAG: agglayer-dev-ui:pr-smoke
# TEMPORARY -- remove per plans/dev-ui-docker-ghcr/d2-adr-dependency-strategy.md §5
# Pinned to a full 40-character commit SHA, never a branch name -- see
# docker-publish.yaml's identical env entry for the full rationale.
# Verified identical across local HEAD, `git ls-remote`, and
# `gh api repos/agglayer/sdk/pulls/28 --jq .head.sha` as of the D-2 ADR
# (2026-08-11). Keep in sync with docker-publish.yaml's SDK_REF; bump both
# together when sdk#28 moves, delete both entirely once the D-2 ADR §5
# migration trigger fires.
SDK_REF: 5680d837b168cd3b250110660332aa110eb88aae

jobs:
build-and-smoke-test:
name: Build (single-arch, no push) and smoke test
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- name: Checkout agglayer-dev-ui
uses: actions/checkout@v4
with:
persist-credentials: false

# TEMPORARY -- remove per plans/dev-ui-docker-ghcr/d2-adr-dependency-strategy.md §5
# D-2 ADR §4.1: a second, SHA-pinned checkout of the public
# agglayer/sdk repo, staged at .sdk-src/ inside the dev-ui build
# context so the Dockerfile's sdk-builder stage can compile it in
# place of the `file:../sdk` sibling checkout this repo's
# pnpm-workspace.yaml override expects locally. agglayer/sdk is
# public (`gh repo view agglayer/sdk --json isPrivate` ->
# {"isPrivate":false}), so the default GITHUB_TOKEN suffices -- no
# PAT, no new secret. Must run AFTER the primary checkout above: it
# writes into .sdk-src/ under the dev-ui workspace root that checkout
# just populated.
- name: Checkout agglayer/sdk (pinned, TEMPORARY)
uses: actions/checkout@v4
with:
repository: agglayer/sdk
ref: ${{ env.SDK_REF }}
path: .sdk-src
persist-credentials: false

# SDK_REF is duplicated (not shared) between this workflow and
# docker-publish.yaml -- GitHub Actions has no cross-workflow env. Left
# unguarded, a one-sided bump would make this PR job build and smoke-test
# a different @agglayer/sdk revision than the one docker-publish.yaml
# actually ships, so a green PR would prove nothing about the published
# image. This turns that silent drift into a red PR instead.
# TEMPORARY -- remove per plans/dev-ui-docker-ghcr/d2-adr-dependency-strategy.md §5
- name: Assert SDK_REF matches docker-publish.yaml
run: |
set -euo pipefail
publish_ref="$(grep -oE '^ SDK_REF: [0-9a-f]{40}$' .github/workflows/docker-publish.yaml | awk '{print $2}')"
if [ -z "$publish_ref" ]; then
echo "::error::could not read a 40-char SDK_REF from .github/workflows/docker-publish.yaml -- keep both workflows' SDK_REF entries in the documented 'SDK_REF: <40 hex chars>' form" >&2
exit 1
fi
if [ "$publish_ref" != "$SDK_REF" ]; then
echo "::error::SDK_REF drift: docker-pr.yaml pins '$SDK_REF' but docker-publish.yaml pins '$publish_ref'. Bump both together (D-2 ADR §5.2 items 7 and 8)." >&2
exit 1
fi
echo "SDK_REF in sync across both workflows: $SDK_REF"

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

# No push, no registry login: this is the deliberate boundary between
# this workflow and docker-publish.yaml. `load: true` pulls the built
# image into the local Docker daemon so the smoke-test steps below can
# `docker run` it. Single-arch (the runner's native linux/amd64) per
# the plan's acceptance criterion -- multi-arch + `load` is not
# supported by the default docker driver anyway.
- name: 'Build image (single-arch, push: false)'
id: build
uses: docker/build-push-action@v6
with:
context: .
platforms: linux/amd64
push: false
load: true
# TEMPORARY -- remove per plans/dev-ui-docker-ghcr/d2-adr-dependency-strategy.md §5
# Feeds the Dockerfile's ARG SDK_REF (both stages), which stamps
# the LABEL org.agglayer.sdk.revision onto the built image. Not
# load-bearing for a PR build (nothing publishes this image), kept
# only so the build-arg contract matches docker-publish.yaml
# exactly and the label is present if anyone inspects the image
# locally.
build-args: |
SDK_REF=${{ env.SDK_REF }}
tags: ${{ env.IMAGE_TAG }}
cache-from: type=gha,scope=docker-pr
cache-to: type=gha,mode=max,scope=docker-pr

- name: Smoke test image (HTTP 200, valid /config.json)
run: |
set -euo pipefail

docker run -d --name devui-smoke -p 8080:80 "${{ env.IMAGE_TAG }}"

ready=""
for _ in $(seq 1 30); do
if curl -fsS -o /dev/null http://localhost:8080/; then
ready=1
break
fi
sleep 1
done
if [ -z "$ready" ]; then
echo "::error::container never became ready on http://localhost:8080/ -- likely a broken Dockerfile, build, or entrypoint" >&2
docker logs devui-smoke >&2 || true
exit 1
fi

root_status="$(curl -s -o /dev/null -w '%{http_code}' http://localhost:8080/)"
if [ "$root_status" != "200" ]; then
echo "::error::expected HTTP 200 from /, got $root_status" >&2
docker logs devui-smoke >&2 || true
exit 1
fi

config_status="$(curl -s -o /dev/null -w '%{http_code}' http://localhost:8080/config.json)"
if [ "$config_status" != "200" ]; then
echo "::error::expected HTTP 200 from /config.json, got $config_status" >&2
docker logs devui-smoke >&2 || true
exit 1
fi

config_body="$(curl -fsS http://localhost:8080/config.json)"
if ! echo "$config_body" | jq empty 2>/dev/null; then
echo "::error::config.json served by the container is not valid JSON -- config contract is broken" >&2
echo "$config_body" >&2
docker logs devui-smoke >&2 || true
exit 1
fi

echo "Smoke test passed: / -> $root_status, /config.json -> $config_status (valid JSON)"
docker logs devui-smoke
docker rm -f devui-smoke

# Per C-2 (plans/dev-ui-docker-ghcr-plan.md line ~469): assert the
# app-config-error path too, not only "the container starts". This is
# the container-level analog of the browser-rendered
# data-test-id="app-config-error" screen (that gate lives in
# app/components/appConfigGate.tsx and is T-1's concern, requiring a
# browser) -- entrypoint.sh's own structural jq validation is the
# equivalent contract at the container boundary: a deliberately
# invalid mounted config.json must make the container refuse to start
# (exit non-zero), not silently serve a blank or broken page. Fast
# (a few seconds, no port, no polling loop) and legible (its own
# ::error on failure).
- name: Smoke test invalid mounted config (container must exit non-zero)
run: |
set -euo pipefail

invalid_config="$RUNNER_TEMP/invalid-config.json"
echo '{"not":"a valid agglayer-dev-ui config"}' > "$invalid_config"

set +e
docker run --rm \
-v "$invalid_config:/etc/agglayer-dev-ui/config.json:ro" \
"${{ env.IMAGE_TAG }}"
exit_code=$?
set -e

if [ "$exit_code" -eq 0 ]; then
echo "::error::container exited 0 for a deliberately invalid mounted config.json -- entrypoint.sh's structural validation regressed (config contract broken)" >&2
exit 1
fi

echo "Invalid-config smoke test passed: container correctly exited non-zero ($exit_code) instead of starting with a broken config"
Loading
Loading