Transfer Status: chain-switch UX breaks stepper; withdrawSubmit stalled until refresh; MegaETH wallet chip shows ETH #131

Open
opened 2026-05-01 12:55:58 +00:00 by PlasticDigits · 10 comments
PlasticDigits commented 2026-05-01 12:55:58 +00:00 (Migrated from gitlab.com)

Summary

Several related UX/regression issues on Transfer Status (/transfer/:xchainHashId) during hash submission when the connected EVM wallet is not on the destination chain, plus incorrect MegaETH branding in the header wallet chip.


Symptoms (reported)

  1. Step indicator keeps resetting while a yellow banner shows messaging equivalent to “Switch chain” / “Switching Chain…” (approve chain switch in wallet). From the user’s perspective this feels like step 1 (deposit / early step) flickering instead of staying focused on hash submission.
  2. No explicit control in the notice to switch chains (users expect a “Switch to <destination>” button, not only wallet-pop-up-driven flow).
  3. After the user switches to the correct network (sometimes manually), the withdrawSubmit transaction is not sent to the wallet until a full page refresh. After refresh with the wallet already on the correct chain, submission works.
  4. When connected on MegaETH, the EVM wallet UI shows “ETH” (text fallback) instead of the MegaETH logo (despite MegaETH being a first-class chain in config).

Affected surfaces (code map)

Area Primary files
Auto hash submit + chain switch packages/frontend/src/hooks/useAutoWithdrawSubmit.ts
Transfer stepper + banners packages/frontend/src/pages/TransferStatusPage.tsx
Multi-chain polling packages/frontend/src/hooks/useMultiChainLookup.ts
EVM withdraw tx packages/frontend/src/hooks/useWithdrawSubmit.ts (wagmi writeContractAsync)
Wallet header / chain glyph packages/frontend/src/components/ConnectWallet.tsx
MegaETH ids packages/frontend/src/lib/megaethMainnet.ts (MEGAETH_MAINNET_CHAIN_ID = 4326)
Chain metadata (includes MegaETH icon path) packages/frontend/public/chains/chainlist.json (icon: "/chains/mega.png")

Technical analysis

A. Stepper “reset” during hash submission (high confidence)

getStepIndex() maps lifecycle === 'deposited' to step index 0 (Deposit row in the vertical stepper).

currentStepIdx overrides that to 1 (Submit Hash) when all of these hold:

  • transfer.lifecycle === 'deposited'
  • source != null
  • dest == null
  • !lookupLoading

See TransferStatusPage.tsx (currentStepIdx useMemo).

Meanwhile, useMultiChainLookup sets loading: true at the start of every lookup (lookup()), including poll ticks.

The Transfer Status page runs setInterval(() => lookup(hash), POLLING_INTERVAL) for all non-terminal lifecycles—including deposited—see polling useEffect with comment referencing stuck transfer detection (#42).

Default POLLING_INTERVAL is 10s (packages/frontend/src/utils/constants.ts), so roughly every 10 seconds:

  1. lookupLoading becomes true for the duration of the parallel RPC sweep.
  2. The Submit Hash override does not apply.
  3. currentStepIdx falls back to getStepIndex('deposited') → 0.
  4. The UI highlights Deposit again while banners may still say Switching Chain… — exactly the reported “reset” sensation.

There is already an inline comment in useMultiChainLookup (“Keep prior source/dest while refreshing…”) acknowledging stepper churn for Terra→Solana, but loading still toggles, so the Transfer Status step logic remains vulnerable.

Hypothesis: This is primarily a step-index bug coupled to lookupLoading, not the wallet failing to switch chains.

B. Missing “switch chain” affordance (high confidence)

During autoPhase === 'switching-chain', TransferStatusPage only shows copy such as “Please approve the chain switch in your wallet.” There is no button wired to switchChainAsync({ chainId: destChainId }) as a fallback when:

  • The wallet extension never surfaces a prompt,
  • The user dismissed the prompt,
  • The chain must be added manually first,

even though switchChainAsync is already imported on the page for other flows (e.g. broken-transfer fix).

useAutoWithdrawSubmit.triggerSubmit does call switchChainAsync internally, but there is no surfaced retry/switch control in that specific banner path except indirect flows (e.g. error → Retry Submit).

C. Transaction not appearing until refresh (medium confidence — likely race / stalled async flow)

In useAutoWithdrawSubmit.triggerSubmit:

  1. submittedRef.current = true is set before attempting switchChainAsync + submitOnEvm.
  2. After switchChainAsync resolves, submitOnEvm immediately calls wagmi writeContractAsync.

Plausible failure modes:

  1. Stale wallet chain vs viem/wagmi: Some wallets resolve switchChain before useAccount().chain / wallet connector state reflects the new chain. writeContractAsync may still target the prior chain or fail in ways that surface as null hash + error, leaving users to refresh so triggerSubmit runs again under stable chain state.
  2. Hung switchChainAsync: If the pop-up is ignored or never shown, the promise may remain pending; manual switching does not necessarily fulfill that promise → flow stuck until reload re-runs orchestration from a clean hook state.
  3. Invariant interactions: Worth validating MegaETH-specific connector behavior (packages/frontend/src/lib/wagmi.ts registers megaEthChain) — e.g. network-add prompts vs silent failures.

This deserves instrumentation + reproduction with MetaMask/Rabby on MegaETH ↔ other EVM routes.

D. MegaETH shows “ETH” instead of logo (high confidence)

ConnectWallet.tsx:

  • getChainLogoPath(chainId) returns logo paths for BSC, opBNB, Anvil, Ethereum mainnet only, etc.
  • MEGAETH_MAINNET_CHAIN_ID (4326) is not handled, so chainLogoPath is undefined.
  • When no logo loads, the UI falls back to the gasSymbol pill — and getGasSymbol defaults unknown chains to ETH.

Meanwhile chainlist.json already declares /chains/mega.png for MegaETH — the header widget simply does not use it.

Disconnected CONNECT EVM button always renders /chains/ethereum-icon.png regardless of last-used chain (minor vs connected-state bug).


Proposed fixes (engineering direction)

1. Decouple stepper highlight from ephemeral lookupLoading

Options (pick one or combine):

  • While lifecycle === 'deposited' and we already know source != null && dest == null, treat the active step as Submit Hash even during lookup refresh (use sticky “post-deposit phase” derived from last successful lookup snapshot, not transient loading).
  • Or: compute “submit-hash phase” from transfer.lifecycle plus autoPhase / withdrawSubmitTxHash intent flags rather than lookupLoading.

Goal: No flicker back to Deposit during periodic polls.

2. Add explicit chain-switch UI on Transfer Status

When destination is EVM and evmChain.id !== destChainId:

  • Show Switch to <DestinationName> calling switchChainAsync with dest id from BRIDGE_CHAINS.
  • Consider pairing with useSwitchChain().chains / connector capabilities detection when chain isn’t added yet (surface explorer docs link).

3. Harden post-switch submission

After successful switchChainAsync:

  • Await wallet agreement: poll until useAccount().chainId === destChainId or timeout with actionable error + Retry.
  • Optionally split triggerSubmit so chain alignment is idempotent and user-triggerable from the banner.

4. MegaETH branding in ConnectWallet

  • Extend getChainLogoPath for 4326 → /chains/mega.png (align with chainlist.json).
  • Extend getGasSymbol if MegaETH native symbol must not display as ETH (confirm desired ticker — product/network docs).

Acceptance criteria

Transfer Status / hash submission

  1. With lifecycle === 'deposited', deposit confirmed on source (source != null), and no pending withdraw on destination yet (dest == null), the stepper does not revert to highlighting Deposit merely because a background multi-chain lookup poll started (lookupLoading === true).
  2. When the wallet must be on the destination EVM chain before withdrawSubmit, the UI presents a clear primary action (button) Switch to <chain> that invokes the configured wagmi/network switch for that destination — not only passive text asking to approve in the wallet.
  3. After the user completes a chain switch (wallet prompt or button), withdrawSubmit reliably prompts for signing without requiring a manual page refresh, across at least:
    • MetaMask / injected EVM wallet,
    • One secondary wallet (e.g. Rabby or WalletConnect target),
    • MegaETH as destination or source where applicable.
  4. If chain alignment fails (timeout, rejection, unsupported chain add), the UI lands in a recoverable error state with Retry / Switch network — never a silent stall.

MegaETH branding

  1. When connected with chain id 4326, the wallet chip shows /chains/mega.png (or equivalent approved asset), not the ETH text fallback — assuming mega.png is present in packages/frontend/public/chains/.
  2. Gas/native balance label follows product-approved naming for MegaETH (document chosen ticker if not ETH).

Regression / QA

  1. Existing polling behavior still detects completion when users remain on Transfer Status (#42 scenarios): Terra / EVM / Solana destinations continue to advance lifecycle correctly.
  2. Add or extend automated coverage where feasible:
    • Unit test / narrow hook test for currentStepIdx stability across lookupLoading toggles given deposited + source present + dest absent.
    • Optional Playwright smoke asserting banner button visibility when mocked wrong-chain state.

Reproduction notes (for QA)

  • Route: /transfer/&lt;xchainHashId&gt; after deposit confirms (deposited).
  • Wallet starts on wrong EVM chain relative to configured transfer.destChain.
  • Observe during switching-chain window across ≥15–20s to intersect POLLING_INTERVAL (default 10s).
  • MegaETH: connect while RPC/network set to MegaETH — inspect header wallet glyph.

  • Step logic: TransferStatusPage.tsx — currentStepIdx override gated on !lookupLoading.
  • Poll loop: TransferStatusPage.tsx — setInterval(... lookup ...).
  • Lookup loading flag: useMultiChainLookup.ts — setResult(... loading: true ...).
  • Auto submit / switch: useAutoWithdrawSubmit.ts — triggerSubmit, phases 'switching-chain' | submitting-hash'.
  • MegaETH chain constant: megaethMainnet.ts.
  • MegaETH icon path in manifest: public/chains/chainlist.json → mega.png.

Labels / priority suggestion

  • Bug, frontend, bridge UX, MegaETH
  • Severity: High — blocks completing withdrawals without refresh and damages trust during chain switches.
## Summary Several related UX/regression issues on **Transfer Status** (`/transfer/:xchainHashId`) during **hash submission** when the connected EVM wallet is not on the **destination chain**, plus incorrect **MegaETH** branding in the header wallet chip. --- ## Symptoms (reported) 1. **Step indicator keeps resetting** while a yellow banner shows messaging equivalent to **“Switch chain” / “Switching Chain…”** (approve chain switch in wallet). From the user’s perspective this feels like **step 1** (deposit / early step) flickering instead of staying focused on hash submission. 2. **No explicit control in the notice** to switch chains (users expect a **“Switch to &lt;destination&gt;”** button, not only wallet-pop-up-driven flow). 3. After the user **switches to the correct network** (sometimes manually), the **`withdrawSubmit` transaction is not sent to the wallet** until a **full page refresh**. After refresh with the wallet already on the correct chain, submission works. 4. When connected on **MegaETH**, the **EVM wallet UI shows “ETH”** (text fallback) instead of the **MegaETH logo** (despite MegaETH being a first-class chain in config). --- ## Affected surfaces (code map) | Area | Primary files | |------|----------------| | Auto hash submit + chain switch | `packages/frontend/src/hooks/useAutoWithdrawSubmit.ts` | | Transfer stepper + banners | `packages/frontend/src/pages/TransferStatusPage.tsx` | | Multi-chain polling | `packages/frontend/src/hooks/useMultiChainLookup.ts` | | EVM withdraw tx | `packages/frontend/src/hooks/useWithdrawSubmit.ts` (wagmi `writeContractAsync`) | | Wallet header / chain glyph | `packages/frontend/src/components/ConnectWallet.tsx` | | MegaETH ids | `packages/frontend/src/lib/megaethMainnet.ts` (`MEGAETH_MAINNET_CHAIN_ID = 4326`) | | Chain metadata (includes MegaETH icon path) | `packages/frontend/public/chains/chainlist.json` (`icon: "/chains/mega.png"`) | --- ## Technical analysis ### A. Stepper “reset” during hash submission (high confidence) **`getStepIndex()`** maps `lifecycle === 'deposited'` to step index **0** (Deposit row in the vertical stepper). **`currentStepIdx`** overrides that to **1** (Submit Hash) when **all** of these hold: - `transfer.lifecycle === 'deposited'` - `source != null` - `dest == null` - `!lookupLoading` See `TransferStatusPage.tsx` (`currentStepIdx` `useMemo`). Meanwhile, **`useMultiChainLookup`** sets **`loading: true`** at the **start** of every lookup (`lookup()`), including **poll ticks**. The Transfer Status page runs **`setInterval(() => lookup(hash), POLLING_INTERVAL)`** for **all non-terminal** lifecycles—including **`deposited`**—see polling `useEffect` with comment referencing stuck transfer detection (#42). **Default `POLLING_INTERVAL`** is **10s** (`packages/frontend/src/utils/constants.ts`), so roughly every 10 seconds: 1. `lookupLoading` becomes **true** for the duration of the parallel RPC sweep. 2. The Submit Hash override **does not apply**. 3. `currentStepIdx` falls back to **`getStepIndex('deposited')` → 0**. 4. The UI highlights **Deposit** again while banners may still say **Switching Chain…** — exactly the reported “reset” sensation. There is already an inline comment in `useMultiChainLookup` (“Keep prior source/dest while refreshing…”) acknowledging stepper churn for Terra→Solana, but **`loading` still toggles**, so the Transfer Status step logic remains vulnerable. **Hypothesis:** This is primarily a **step-index bug coupled to `lookupLoading`**, not the wallet failing to switch chains. ### B. Missing “switch chain” affordance (high confidence) During `autoPhase === 'switching-chain'`, `TransferStatusPage` only shows copy such as **“Please approve the chain switch in your wallet.”** There is **no button** wired to `switchChainAsync({ chainId: destChainId })` as a fallback when: - The wallet extension never surfaces a prompt, - The user dismissed the prompt, - The chain must be **added** manually first, even though **`switchChainAsync`** is already imported on the page for other flows (e.g. broken-transfer fix). `useAutoWithdrawSubmit.triggerSubmit` does call `switchChainAsync` internally, but **there is no surfaced retry/switch control** in that specific banner path except indirect flows (e.g. error → Retry Submit). ### C. Transaction not appearing until refresh (medium confidence — likely race / stalled async flow) In `useAutoWithdrawSubmit.triggerSubmit`: 1. `submittedRef.current = true` is set **before** attempting `switchChainAsync` + `submitOnEvm`. 2. After `switchChainAsync` resolves, `submitOnEvm` immediately calls wagmi **`writeContractAsync`**. **Plausible failure modes:** 1. **Stale wallet chain vs viem/wagmi:** Some wallets resolve `switchChain` before `useAccount().chain` / wallet connector state reflects the new chain. `writeContractAsync` may still target the prior chain or fail in ways that surface as **null hash + error**, leaving users to refresh so `triggerSubmit` runs again under stable chain state. 2. **Hung `switchChainAsync`:** If the pop-up is ignored or never shown, the promise may remain pending; manual switching does not necessarily fulfill that promise → flow stuck until reload re-runs orchestration from a clean hook state. 3. **Invariant interactions:** Worth validating MegaETH-specific connector behavior (`packages/frontend/src/lib/wagmi.ts` registers `megaEthChain`) — e.g. network-add prompts vs silent failures. This deserves **instrumentation + reproduction with MetaMask/Rabby** on MegaETH ↔ other EVM routes. ### D. MegaETH shows “ETH” instead of logo (high confidence) `ConnectWallet.tsx`: - **`getChainLogoPath(chainId)`** returns logo paths for BSC, opBNB, Anvil, Ethereum **mainnet only**, etc. - **`MEGAETH_MAINNET_CHAIN_ID` (4326)** is **not handled**, so `chainLogoPath` is **`undefined`**. - When no logo loads, the UI falls back to the **`gasSymbol`** pill — and **`getGasSymbol`** defaults unknown chains to **`ETH`**. Meanwhile **`chainlist.json`** already declares **`/chains/mega.png`** for MegaETH — the header widget simply **does not use it**. Disconnected **CONNECT EVM** button always renders **`/chains/ethereum-icon.png`** regardless of last-used chain (minor vs connected-state bug). --- ## Proposed fixes (engineering direction) ### 1. Decouple stepper highlight from ephemeral `lookupLoading` Options (pick one or combine): - While `lifecycle === 'deposited'` **and** we already know **`source != null && dest == null`**, treat the active step as **Submit Hash** even during lookup refresh (use **sticky** “post-deposit phase” derived from last successful lookup snapshot, not transient loading). - Or: compute “submit-hash phase” from **`transfer.lifecycle`** plus **`autoPhase`** / **`withdrawSubmitTxHash`** intent flags rather than `lookupLoading`. Goal: **No flicker back to Deposit** during periodic polls. ### 2. Add explicit chain-switch UI on Transfer Status When destination is EVM and `evmChain.id !== destChainId`: - Show **Switch to &lt;DestinationName&gt;** calling **`switchChainAsync`** with dest id from `BRIDGE_CHAINS`. - Consider pairing with **`useSwitchChain().chains`** / connector capabilities detection when chain isn’t added yet (surface explorer docs link). ### 3. Harden post-switch submission After successful `switchChainAsync`: - **Await wallet agreement**: poll until `useAccount().chainId === destChainId` or timeout with actionable error + Retry. - Optionally split **`triggerSubmit`** so chain alignment is **idempotent** and **user-triggerable** from the banner. ### 4. MegaETH branding in `ConnectWallet` - Extend **`getChainLogoPath`** for **`4326`** → **`/chains/mega.png`** (align with `chainlist.json`). - Extend **`getGasSymbol`** if MegaETH native symbol must **not** display as ETH (confirm desired ticker — product/network docs). --- ## Acceptance criteria ### Transfer Status / hash submission 1. With `lifecycle === 'deposited'`, deposit **confirmed on source** (`source != null`), and **no pending withdraw on destination yet** (`dest == null`), the stepper **does not revert** to highlighting **Deposit** merely because a **background multi-chain lookup poll** started (`lookupLoading === true`). 2. When the wallet must be on the **destination EVM chain** before `withdrawSubmit`, the UI presents a **clear primary action** (button) **Switch to &lt;chain&gt;** that invokes the configured wagmi/network switch for that destination — not only passive text asking to approve in the wallet. 3. After the user completes a chain switch (wallet prompt **or** button), **`withdrawSubmit` reliably prompts** for signing **without requiring a manual page refresh**, across at least: - MetaMask / injected EVM wallet, - One secondary wallet (e.g. Rabby or WalletConnect target), - **MegaETH as destination or source** where applicable. 4. If chain alignment fails (timeout, rejection, unsupported chain add), the UI lands in a **recoverable error state** with **Retry** / **Switch network** — never a silent stall. ### MegaETH branding 5. When connected with chain id **4326**, the wallet chip shows **`/chains/mega.png`** (or equivalent approved asset), **not** the **`ETH`** text fallback — assuming `mega.png` is present in `packages/frontend/public/chains/`. 6. Gas/native balance label follows **product-approved naming** for MegaETH (document chosen ticker if not ETH). ### Regression / QA 7. Existing polling behavior still detects completion when users remain on Transfer Status (#42 scenarios): Terra / EVM / Solana destinations continue to advance lifecycle correctly. 8. Add or extend automated coverage where feasible: - Unit test / narrow hook test for **`currentStepIdx`** stability across `lookupLoading` toggles **given deposited + source present + dest absent**. - Optional Playwright smoke asserting banner button visibility when mocked wrong-chain state. --- ## Reproduction notes (for QA) - Route: **`/transfer/&lt;xchainHashId&gt;`** after deposit confirms (`deposited`). - Wallet starts on **wrong EVM chain** relative to configured **`transfer.destChain`**. - Observe during **`switching-chain`** window across **≥15–20s** to intersect **`POLLING_INTERVAL`** (default **10s**). - MegaETH: connect while RPC/network set to MegaETH — inspect header wallet glyph. --- ## Related implementation hints - Step logic: `TransferStatusPage.tsx` — `currentStepIdx` override gated on `!lookupLoading`. - Poll loop: `TransferStatusPage.tsx` — `setInterval(... lookup ...)`. - Lookup loading flag: `useMultiChainLookup.ts` — `setResult(... loading: true ...)`. - Auto submit / switch: `useAutoWithdrawSubmit.ts` — `triggerSubmit`, phases `'switching-chain' | submitting-hash'`. - MegaETH chain constant: `megaethMainnet.ts`. - MegaETH icon path in manifest: `public/chains/chainlist.json` → **`mega.png`**. --- ## Labels / priority suggestion - **Bug**, **frontend**, **bridge UX**, **MegaETH** - **Severity:** High — blocks completing withdrawals without refresh and damages trust during chain switches.
Brouie commented 2026-05-02 00:58:19 +00:00 (Migrated from gitlab.com)

Source-level confirmation of items A and D on 27a5e42 HEAD — your analysis matches the code exactly. Plus a draft failing-test scenario for the stepper flicker bug.

Item D — MegaETH logo gap (high confidence — confirmed)

packages/frontend/src/components/ConnectWallet.tsx getChainLogoPath:

function getChainLogoPath(chainId?: number): string | undefined {
  if (chainId === 56 || chainId === 97) return '/chains/binancesmartchain-icon.png'
  if (chainId === 204 || chainId === 5611) return '/chains/opbnb-icon.png'
  if (chainId === 31337) return '/chains/anvil-icon.png'
  if (chainId === 31338) return '/chains/anvil2-icon.png'
  if (chainId === 1) return '/chains/ethereum-icon.png'
  return undefined
}

No branch for 4326 (MegaETH). Confirmed:

  • Asset present: ls public/chains/mega.png → file exists.
  • Manifest declares it: chainlist.json has "id": "megaeth", "icon": "/chains/mega.png".

Pure data-vs-code gap. One-line fix: add if (chainId === 4326) return '/chains/mega.png'.

Item A — currentStepIdx flicker on poll tick (high confidence — confirmed)

packages/frontend/src/pages/TransferStatusPage.tsx currentStepIdx useMemo:

if (
  transfer?.lifecycle === 'deposited' &&
  source != null &&
  dest == null &&
  !lookupLoading           // <-- override fails when lookup is mid-fetch
) return 1

When the setInterval(lookup, POLLING_INTERVAL) poll runs (default 10s per utils/constants.ts), useMultiChainLookup flips loading: true for the duration of the parallel RPC sweep. The override condition fails, currentStepIdx falls back to getStepIndex('deposited') → 0, and the stepper visibly snaps from "Submit Hash" back to "Deposit". Then resolves back to 1 when the poll completes. Exactly the "reset" sensation reported.

No TransferStatusPage.test.tsx exists today — so this regression has no automated guard.

Draft failing-test scenario (for whoever lands the fix)

If you extract currentStepIdx logic into a pure helper (e.g., computeTransferStepIdx(args)), this test captures the bug deterministically:

// packages/frontend/src/pages/TransferStatusPage.test.ts (new)
import { describe, it, expect } from 'vitest'
import { computeTransferStepIdx } from './TransferStatusPage'  // after extract

describe('computeTransferStepIdx (GitLab #131-A)', () => {
  const baseArgs = {
    transfer: { lifecycle: 'deposited' as const },
    source: { /* non-null deposit receipt */ } as any,
    dest: null,
    autoPhase: 'idle' as const,
    retryingHash: false,
    destIsSolana: false,
    effectiveCancelWindowRemaining: 0,
  }

  it('stays at Submit Hash (idx 1) across lookupLoading toggles', () => {
    expect(computeTransferStepIdx({ ...baseArgs, lookupLoading: false })).toBe(1)
    expect(computeTransferStepIdx({ ...baseArgs, lookupLoading: true })).toBe(1)   // <-- currently fails: returns 0
    expect(computeTransferStepIdx({ ...baseArgs, lookupLoading: false })).toBe(1)
  })
})

The middle assertion fails on 27a5e42 HEAD per the override gate — that's the regression. Once the fix decouples step highlight from lookupLoading (per your proposed direction 1), all three assertions pass.

Happy to verify the eventual fix the same way I verified #127 / #128 today: source-level walkthrough, classifier/helper unit tests if applicable, full npx vitest run regression check.

cc @PlasticDigits

Source-level confirmation of items A and D on `27a5e42` HEAD — your analysis matches the code exactly. Plus a draft failing-test scenario for the stepper flicker bug. ### Item D — MegaETH logo gap (high confidence — confirmed) `packages/frontend/src/components/ConnectWallet.tsx getChainLogoPath`: ```ts function getChainLogoPath(chainId?: number): string | undefined { if (chainId === 56 || chainId === 97) return '/chains/binancesmartchain-icon.png' if (chainId === 204 || chainId === 5611) return '/chains/opbnb-icon.png' if (chainId === 31337) return '/chains/anvil-icon.png' if (chainId === 31338) return '/chains/anvil2-icon.png' if (chainId === 1) return '/chains/ethereum-icon.png' return undefined } ``` No branch for `4326` (MegaETH). Confirmed: - Asset present: `ls public/chains/mega.png` → file exists. - Manifest declares it: `chainlist.json` has `"id": "megaeth", "icon": "/chains/mega.png"`. Pure data-vs-code gap. One-line fix: add `if (chainId === 4326) return '/chains/mega.png'`. ### Item A — `currentStepIdx` flicker on poll tick (high confidence — confirmed) `packages/frontend/src/pages/TransferStatusPage.tsx currentStepIdx` useMemo: ```ts if ( transfer?.lifecycle === 'deposited' && source != null && dest == null && !lookupLoading // <-- override fails when lookup is mid-fetch ) return 1 ``` When the `setInterval(lookup, POLLING_INTERVAL)` poll runs (default 10s per `utils/constants.ts`), `useMultiChainLookup` flips `loading: true` for the duration of the parallel RPC sweep. The override condition fails, `currentStepIdx` falls back to `getStepIndex('deposited')` → 0, and the stepper visibly snaps from "Submit Hash" back to "Deposit". Then resolves back to 1 when the poll completes. Exactly the "reset" sensation reported. No `TransferStatusPage.test.tsx` exists today — so this regression has no automated guard. ### Draft failing-test scenario (for whoever lands the fix) If you extract `currentStepIdx` logic into a pure helper (e.g., `computeTransferStepIdx(args)`), this test captures the bug deterministically: ```ts // packages/frontend/src/pages/TransferStatusPage.test.ts (new) import { describe, it, expect } from 'vitest' import { computeTransferStepIdx } from './TransferStatusPage' // after extract describe('computeTransferStepIdx (GitLab #131-A)', () => { const baseArgs = { transfer: { lifecycle: 'deposited' as const }, source: { /* non-null deposit receipt */ } as any, dest: null, autoPhase: 'idle' as const, retryingHash: false, destIsSolana: false, effectiveCancelWindowRemaining: 0, } it('stays at Submit Hash (idx 1) across lookupLoading toggles', () => { expect(computeTransferStepIdx({ ...baseArgs, lookupLoading: false })).toBe(1) expect(computeTransferStepIdx({ ...baseArgs, lookupLoading: true })).toBe(1) // <-- currently fails: returns 0 expect(computeTransferStepIdx({ ...baseArgs, lookupLoading: false })).toBe(1) }) }) ``` The middle assertion fails on `27a5e42` HEAD per the override gate — that's the regression. Once the fix decouples step highlight from `lookupLoading` (per your proposed direction 1), all three assertions pass. Happy to verify the eventual fix the same way I verified #127 / #128 today: source-level walkthrough, classifier/helper unit tests if applicable, full `npx vitest run` regression check. cc @PlasticDigits
PlasticDigits commented 2026-05-02 04:41:37 +00:00 (Migrated from gitlab.com)

mentioned in commit 67a48e46d9

mentioned in commit 67a48e46d96d1be9946de83bd5656e1fecb35cf6
PlasticDigits commented 2026-05-02 04:41:53 +00:00 (Migrated from gitlab.com)

Landed on main (67a48e4) — GL-131

Summary: Addressed transfer-status stepper flicker during multi-chain polling, added an explicit Switch to <destination> control for wrong-chain EVM hash submission, hardened post-switch flow (timeout + chain-id alignment before withdrawSubmit), and fixed the MegaETH header wallet icon for chain 4326.

@brouie — could you verify per the checklist below? Leaving the issue open until you sign off.

Verification checklist

  • Stepper (A): On /transfer/:xchainHashId with deposited, source confirmed (source present), no dest pending yet (dest null), leave the page open ≥20s so a background lookup poll runs. The active step should stay on “Submit Hash”, not flash back to “Deposit”.
  • Switch button (B): With EVM destination and wallet on a wrong chain, confirm a “Switch to <chain name>” button appears in the yellow banner (and on hash error when still wrong chain) and that it triggers the wallet network switch.
  • No refresh stall (C): After approving a switch or using the button, withdrawSubmit should prompt without a full page refresh (MetaMask and one other wallet, e.g. Rabby/WC target if applicable; include MegaETH as dest or src where relevant).
  • Timeout / retry: If you dismiss or ignore the chain prompt for ~60s, you should see a recoverable error with Retry Submit instead of an indefinite spinner.
  • MegaETH chip (D): Connected on 4326, header wallet shows /chains/mega.png (MegaETH branding), gas label remains ETH per chain config — see INV-UX3 in docs/FRONTEND_BRIDGE_INVARIANTS.md.
  • Regression (#42): Non-terminal lifecycle still advances when remaining on Transfer Status (Terra / EVM / Solana dest smoke).

Tests / docs

  • Unit: packages/frontend/src/utils/transferStatusStep.test.ts (computeTransferStepIdx stability across lookupLoading).
  • Invariants: INV-UX3 — docs/FRONTEND_BRIDGE_INVARIANTS.md; agent cross-link — skills/agent-frontend-bridge-chains.md.
## Landed on `main` (67a48e4) — GL-131 **Summary:** Addressed transfer-status stepper flicker during multi-chain polling, added an explicit **Switch to \<destination\>** control for wrong-chain EVM hash submission, hardened post-switch flow (timeout + chain-id alignment before `withdrawSubmit`), and fixed the MegaETH header wallet icon for chain **4326**. **@brouie** — could you verify per the checklist below? Leaving the issue **open** until you sign off. ### Verification checklist - [ ] **Stepper (A):** On `/transfer/:xchainHashId` with `deposited`, source confirmed (`source` present), no dest pending yet (`dest` null), leave the page open **≥20s** so a background lookup poll runs. The active step should **stay on “Submit Hash”**, not flash back to “Deposit”. - [ ] **Switch button (B):** With EVM destination and wallet on a **wrong** chain, confirm a **“Switch to \<chain name\>”** button appears in the yellow banner (and on hash error when still wrong chain) and that it triggers the wallet network switch. - [ ] **No refresh stall (C):** After approving a switch **or** using the button, `withdrawSubmit` should prompt **without** a full page refresh (MetaMask and one other wallet, e.g. Rabby/WC target if applicable; include **MegaETH** as dest or src where relevant). - [ ] **Timeout / retry:** If you dismiss or ignore the chain prompt for ~60s, you should see a **recoverable error** with **Retry Submit** instead of an indefinite spinner. - [ ] **MegaETH chip (D):** Connected on **4326**, header wallet shows **`/chains/mega.png`** (MegaETH branding), gas label remains **ETH** per chain config — see **INV-UX3** in `docs/FRONTEND_BRIDGE_INVARIANTS.md`. - [ ] **Regression (#42):** Non-terminal lifecycle still advances when remaining on Transfer Status (Terra / EVM / Solana dest smoke). ### Tests / docs - Unit: `packages/frontend/src/utils/transferStatusStep.test.ts` (`computeTransferStepIdx` stability across `lookupLoading`). - Invariants: **`INV-UX3`** — `docs/FRONTEND_BRIDGE_INVARIANTS.md`; agent cross-link — `skills/agent-frontend-bridge-chains.md`.
PlasticDigits commented 2026-05-02 04:45:18 +00:00 (Migrated from gitlab.com)

mentioned in issue #130

mentioned in issue #130
Brouie commented 2026-05-03 22:10:02 +00:00 (Migrated from gitlab.com)

mentioned in issue #127

mentioned in issue #127
Brouie commented 2026-05-03 22:10:04 +00:00 (Migrated from gitlab.com)

mentioned in issue #128

mentioned in issue #128
Brouie commented 2026-05-03 22:10:05 +00:00 (Migrated from gitlab.com)

Production deploy gap — sign-off blocked

Verified prod bundle on bridge.cl8y.com today: footer reads v0.1.345 · 27a5e42. Repo main HEAD is currently at 95f8fd5, which is the merge commit containing all four fixes (#127 eaa3d0a, #128 b6c5b5a, #130 Terra || clause drop, #131 67a48e4). Prod is therefore still on the pre-fix bundle and none of the four fixes are live yet.

Confirming repro on prod (proves bundle is pre-fix):

  • EVM wallet chip in bridge header shows ETH text fallback while MetaMask is on MegaETH (chain 4326). This is the clean #131-D pre-fix behavior — getChainLogoPath has no branch for 4326, falls through to getGasSymbol default ETH.

Cannot run mainnet sign-off on the verification checklist for #127 / #128 / #130 / #131 until prod cuts to current HEAD (or whichever release commit contains the four fixes).

Will re-run the combined live walk on bridge.cl8y.com once deploy is confirmed:

  • #128 — Solana → MegaETH × 3 (testa / testb / tdec) for fresh-blockhash retry path
  • #127 — deliberate rate-limit row, observe amber "Resets in" countdown banner
  • #130 — MegaETH → Terra tdec 0.001, expect COMPLETE clean
  • #131 — A (stepper stable across poll), B (Switch button visible on wrong chain), C (no refresh stall after switch), D (mega.png header chip)

Plus the 4 #123 blocked rows pick-up that rides on #128 / #130 fixes.

cc @PlasticDigits

## Production deploy gap — sign-off blocked Verified prod bundle on bridge.cl8y.com today: footer reads `v0.1.345 · 27a5e42`. Repo `main` HEAD is currently at `95f8fd5`, which is the merge commit containing all four fixes (#127 `eaa3d0a`, #128 `b6c5b5a`, #130 Terra `||` clause drop, #131 `67a48e4`). Prod is therefore still on the pre-fix bundle and none of the four fixes are live yet. Confirming repro on prod (proves bundle is pre-fix): - EVM wallet chip in bridge header shows `ETH` text fallback while MetaMask is on MegaETH (chain 4326). This is the clean #131-D pre-fix behavior — `getChainLogoPath` has no branch for 4326, falls through to `getGasSymbol` default `ETH`. Cannot run mainnet sign-off on the verification checklist for #127 / #128 / #130 / #131 until prod cuts to current HEAD (or whichever release commit contains the four fixes). Will re-run the combined live walk on bridge.cl8y.com once deploy is confirmed: - #128 — Solana → MegaETH × 3 (testa / testb / tdec) for fresh-blockhash retry path - #127 — deliberate rate-limit row, observe amber "Resets in" countdown banner - #130 — MegaETH → Terra tdec 0.001, expect COMPLETE clean - #131 — A (stepper stable across poll), B (Switch button visible on wrong chain), C (no refresh stall after switch), D (mega.png header chip) Plus the 4 #123 blocked rows pick-up that rides on #128 / #130 fixes. cc @PlasticDigits
Brouie commented 2026-05-03 22:16:38 +00:00 (Migrated from gitlab.com)

mentioned in issue #123

mentioned in issue #123
PlasticDigits commented 2026-05-04 03:38:47 +00:00 (Migrated from gitlab.com)

@Brouie bridge.cl8y.com frontend is now deployed at latest commit.

@Brouie bridge.cl8y.com frontend is now deployed at latest commit.
Brouie commented 2026-05-07 05:43:39 +00:00 (Migrated from gitlab.com)

queued in the bundled bridge live-walk along with #128 — see https://gitlab.com/PlasticDigits/cl8y-bridge-monorepo/-/work_items/128#note_3322750554 for timing.

queued in the bundled bridge live-walk along with #128 — see https://gitlab.com/PlasticDigits/cl8y-bridge-monorepo/-/work_items/128#note_3322750554 for timing.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
code/cl8y-bridge-monorepo#131
No description provided.