Frontend analysis lane
You are doing the frontend-correctness pass of a full-codebase review of the 4626 monorepo at /home/akitav2/projects/4626. READ-ONLY: do not modify files. Stack: Vite 7 + React 19 + TypeScript SPA in frontend/src/, wagmi + Privy auth, two host shells (marketing 4626.fun, app app.4626.fun).
Find CONCRETE bugs/risks with file:line evidence and severity. Focus on correctness, not style. Checklist:
- Double-submit / in-flight protection on money-moving flows: frontend/src/pages/Swap.tsx + frontend/src/hooks or lib useSwapExecution (can a user fire two swaps concurrently? is the submit button disabled during busy states?), DeployVault deploy submission (frontend/src/pages/deploy/DeployVault.tsx), creator strategy payment UI (frontend/src/pages/CreatorStrategyFeatures.tsx — USDC/x402/Stripe buttons).
- Error boundaries: does the app have top-level error boundaries (search ErrorBoundary in frontend/src/)? What happens on a render crash in app shell vs marketing shell?
- Async race conditions in hooks: effects missing cleanup/abort on unmount in data-fetch hooks (sample frontend/src/hooks/ and feature hooks); stale-closure setState after unmount; quote effects on Swap pausing during signing.
- Marketing-shell crashes: Privy/wagmi hooks reachable from marketing-only routes (the known invariant: marketing shells render outside PrivyClientProvider — verify Layout gating, look at frontend/src/components/Layout or AuthenticatedAppLayout).
- Unsafe HTML: any dangerouslySetInnerHTML usage — is content sanitized?
- localStorage/sessionStorage JSON.parse without try/catch reachable on boot (crash loops).
- Form validation gaps on waitlist email entry, telegram link flow (frontend/src/features/waitlist/, telegram link page).
- window.open / external links with user-controlled URLs (javascript: scheme risk).
- BigInt/Number precision in amount parsing/formatting for swap and deploy (find parseUnits/formatUnits misuse, parseFloat on token amounts that feed transactions).
- Route guards: can unauthenticated users reach app routes and what happens (redirect loop risks)?
Return findings sorted by severity with file:line, plus verified-sound items. Thoroughness: very thorough.
Analysis
Running a read-only frontend correctness pass across the checklist areas.
[REDACTED]
Tools invoked: UpdateCurrentStep, Grep, Grep, Grep, Grep, Glob
Analysis
[REDACTED]
Tools invoked: Grep, Grep, Grep, Grep, Glob, Glob
Analysis
[REDACTED]
Tools invoked: Read, Read, Read, Grep, Grep
Analysis
[REDACTED]
Tools invoked: Read, Read, Grep, Read, Grep
Analysis
[REDACTED]
Tools invoked: Read, Read, Read, Read, Grep
Analysis
[REDACTED]
Tools invoked: Grep, Read, Read, Grep, Read
Analysis
[REDACTED]
Tools invoked: Grep, Glob, Read, Read, Read, Read
Analysis
[REDACTED]
Tools invoked: Grep, Grep, Grep, Grep, Read, Read
Analysis
[REDACTED]
Tools invoked: Read, Read, Grep, Read, Read, Grep
Analysis
[REDACTED]
Tools invoked: Read, Read, Read, Read, Read, Grep
Analysis
[REDACTED]
Tools invoked: Read, Grep, Grep, Read, Grep
Analysis
[REDACTED]
Tools invoked: Grep, Read, Grep, Read, Read, Grep
Analysis
[REDACTED]
Tools invoked: Read, Grep, Read, Grep, Read
Analysis
[REDACTED]
Tools invoked: Read, Read, Grep, Grep, Read
Analysis
[REDACTED]
Tools invoked: Read, Read, Read, Read, Grep
Analysis
[REDACTED]
Tools invoked: Read, Read, Grep, Read, Grep
Analysis
[REDACTED]
Tools invoked: Read, Read, Grep, Read
Analysis
[REDACTED]
Tools invoked: Read, Grep, Read, Grep, Read
Analysis
[REDACTED]
Tools invoked: Grep, Glob, Grep, Read
Analysis
[REDACTED]
Tools invoked: Grep, Read, Read, Read
Analysis
[REDACTED]
Tools invoked: Grep, Read, Grep
Analysis
[REDACTED]
Tools invoked: Grep, Read
Analysis
[REDACTED]
Tools invoked: Grep
Analysis
[REDACTED]
Tools invoked: Grep
Analysis
[REDACTED]
Tools invoked: Read
Analysis
[REDACTED]
Tools invoked: Grep, Grep, Grep, Read
Analysis
[REDACTED]
Tools invoked: Read, Grep, Read
Analysis
[REDACTED]
Tools invoked: Read, Grep
Analysis
[REDACTED]
Tools invoked: Grep, Read
Analysis
[REDACTED]
Tools invoked: UpdateCurrentStep
Analysis
Critical / High
1. Live deploy can start while dry-run is in flight
Severity: High
Dry-run sets dryRunBusy but the primary deploy button and submit() do not treat it as blocking.
const submit = async (opts?: { planOnly?: boolean; validateDepositBalance?: boolean }): Promise<DeployPlanExport | null> => {
const planOnly = opts?.planOnly === true
const validateDepositBalance = !planOnly || opts?.validateDepositBalance === true
if (busy || exportBusy) return null
<Button
type="button"
variant="primary"
className="w-full rounded-lg"
onClick={() => void submit()}
disabled={disabled || exportBusy}
>
{busy ? 'Deploying…' : '1‑Click Deploy (Gas-Free)'}
Dry-run correctly gates itself (7250:7251) and disables its own button (8108, 8117), but 1-Click Deploy ignores dryRunBusy. A user can start a real gas-sponsored deploy while submit({ planOnly: true }) is still building/simulating on the fork.
2. Auto-quote can abort an in-progress swap review
Severity: High (swap correctness / failed mid-submit UX)
The debounced quote effect only skips when txState === 'signing' and when busyRef === 'executeSwap'. It does not skip during busy === 'review' or busy === 'quote'.
useEffect(() => {
...
if (txState === 'signing') return
...
const timer = window.setTimeout(() => {
if (busyRef.current === 'executeSwap') return
void handleQuote()
}, 450)
handleReviewTrade and handleQuote share quoteRunRef (1759, 1520). Starting auto-quote during review bumps the epoch and causes review to bail with if (runId !== quoteRunRef.current) return mid-flight — potentially after partial signing/approval work.
handleReviewTrade also does not guard on general busy (only executeSwap / signing / pending):
if (busy === 'executeSwap' || txState === 'signing' || txState === 'pending') {
setStatus('Swap already in progress. Wait for the current transaction to finish.')
return
}
3. Swap submit lacks an atomic in-flight mutex (double-submit window)
Severity: Medium–High
Guards check busy / txState, then setBusy('executeSwap') later — classic check-then-act race. Same pattern in confirmAndExecute:
const confirmAndExecute = useCallback(async () => {
if (!confirmIntent || busy) return
...
setConfirmIntent(null)
try {
setTxState('signing')
...
await executeSwapNow({
Two rapid paths (auto confirmIntent effect in Swap.tsx:935-938 + double activation) can both pass before busy updates. UI disables the primary button when busy !== null (SwapCard.tsx:70-71), but programmatic/StrictMode timing gaps remain.
Medium
4. Creator strategy: concurrent payments across different features
Severity: Medium
inflightFeature is per feature key only; startPayment does not block when another feature is already in flight:
const startPayment = useCallback(
async (feature: CatalogDto, path: PaymentPath) => {
if (!data || !creatorToken) return
setInflightFeature(feature.key)
User can start Stripe on feature A and x402/USDC on feature B concurrently → duplicate authorizations / conflicting wallet prompts.
5. Creator strategy USDC path: no client tx-hash validation
Severity: Medium (bad UX / wasted API round-trips)
case 'usdc_txhash': {
const txHash = window.prompt(...)
if (!txHash) return
const res = await fetch(`${base}/api/creator/strategy/activate`, {
Any non-empty string is sent; no ^0x[a-fA-F0-9]{64}$ check before POST (server should reject, but client allows garbage).
6. Stripe checkout: no URL allowlist before navigation
Severity: Medium (trust-boundary)
window.location.href = json.data.sessionUrl
If API/session were compromised, arbitrary URL assignment is possible. Should restrict to https://checkout.stripe.com/ (or similar).
7. Server deploy session auto-resume effect re-fires when busy clears
Severity: Medium
useEffect(() => {
if (!useServerContinue) return
if (busy) return
const sessionId = loadDeploySession()
if (!sessionId) return
...
}, [busy, ...])
clearDeploySession runs on terminal poll outcomes (sessionClient.ts:136-142), but transient poll/network errors leave the session in localStorage and the effect re-runs whenever busy flips false — duplicate resume ticks / surprise continuation after partial failure.
8. toParsableAmount uses Number() before parseUnits
Severity: Medium (precision edge cases)
function toParsableAmount(value: string): string | null {
...
const n = Number(normalized)
if (!Number.isFinite(n) || n <= 0) return null
return normalized
}
Validation goes through IEEE double; extremely long fractional strings or scientific notation can pass validation inconsistently vs what parseUnits accepts. Balance-percent path is safer (amountUnitsFromBalancePercent uses bigint math in swapDisplayAmount.ts:14-22).
Low
9. Waitlist email: no client-side format gate before Privy login
Severity: Low
Waitlist defers entirely to Privy login() (WaitlistFlow.tsx ~598+). Unlike Telegram (telegramLinkHelpers.ts:181-183, getEmailSubmitAssessment), there is no local regex/disabled-state before OTP send. Privy likely validates server-side, but UX/error timing is weaker.
10. Deploy double-submit: 2s localStorage throttle only
Severity: Low
const retryWindowMs = 2000
if (Number.isFinite(last) && last > 0 && now - last < retryWindowMs) {
Bypassable via second tab or waiting 2s; busy guard helps but is not set until after the throttle check path continues.
11. openTelegramExternalLink has no scheme allowlist
Severity: Low (current call sites are fixed URLs)
export function openTelegramExternalLink(url: string): boolean {
const targetUrl = asTrimmed(url)
...
window.open(targetUrl, '_blank', 'noopener,noreferrer')
Live callers use constructed same-origin URLs (TelegramLink.tsx:252-257) or constants — not user-controlled today. Helper itself would allow javascript: if misused later.
Verified sound
Money-moving flows (partial)
| Area | Assessment |
|---|---|
| Swap UI | Primary button disabled when busy !== null (SwapCard.tsx:70-73, 152-153); shows loading state. |
| Swap execute guards | Blocks when busy === 'executeSwap' or signing/pending (useSwapExecution.ts:2133-2135); uses submitEpoch + assertSwapSubmitEpochUnchanged for dev HMR. |
| Swap quote during signing | Auto-quote paused when txState === 'signing' (Swap.tsx:908). |
| Deploy | busy || exportBusy early return; 2s throttle; button disabled when disabled || exportBusy (8319). |
| Creator strategy | Per-feature isInflight disables all three payment buttons (PaymentPathGrid.tsx:523-538); finally clears inflight (CreatorStrategyFeatures.tsx:190-192). |
| Deploy deposit amounts | Enforced 50M integer tokens → bigint (DeployVault.tsx:8605-8608, parsePositiveTokenAmount in deployVaultHelpers.ts:122-132). |
Error boundaries
- App-wide:
RootErrorBoundarywraps full tree inmain.tsx:379-388(marketing + app SPA). - Deploy-only:
DeployVaultErrorBoundary(DeployVault.tsx:905+,992-994). - Dev tactical map: local
HoloRenderErrorBoundary. - Marketing
/faq,/arena, etc. rely on root boundary only (no route-scoped boundary) — acceptable; crash shows recovery UI, not blank page.
Marketing shell / Privy / wagmi gating
- Layout:
useSiweAuth/useAccountTrayPortfolioonly wheninteractive && hostMode === 'app'(Layout.tsx:129-136, 144-146). - Marketing routes in App:
Layout interactive={false}(App.tsx:119-121); FAQ/explore marketing paths do not mount Privy. - Waitlist: Own
PrivyClientProvider(Waitlist.tsx:43-48); group chat mountsWalletProvidersbeforeAccountContextProvider(WaitlistSetupWorkspace.tsx:25-27) — avoids wagmi hook crash. - Explore: No
usePrivyin explore pages/components (grep clean); wagmi viaLazyAccessBoundaryonly.
Async / hook hygiene (samples)
useAccountMe:cancelledflag + no null cache (useAccountMe.ts:114-150).useSwapExecution: receipt pollAbortController(2692+); quote run IDs for stale updates.Leaderboard:inFlightRefdedupes fetches (Leaderboard.tsx:43-47).MermaidDiagram:isCancelledcleanup (Arena.tsx:605-626).
Unsafe HTML
- Only production
dangerouslySetInnerHTMLis Arena Mermaid output (Arena.tsx:648) withsecurityLevel: 'strict'and static diagram strings (208,233, etc.) — low XSS risk.
localStorage JSON.parse on boot
Most hot paths wrapped in try/catch: useSwapRecentTokens.ts:12-24, useDeploymentTracker.ts:52-58, deployVaultVanityPersistence.ts:77-91, xmtp/provider.tsx:412-427, appActivityJournal.ts:33-40, DeployVault session load 2708-2720.
Form validation
- Telegram link: Strong —
isValidEmail, disabled reasons, OTP length checks, gesture lock (telegramLinkHelpers.ts,TelegramLink.tsx, extensive tests inTelegramLink.test.tsx).
window.open / external links
- Share/waitlist intents build fixed HTTPS URLs via
URLSearchParams(ShareVaultButton.tsx:27-41,WaitlistDailyActionsHub.tsx:39-41) — no user URL injection in current paths.
Route guards
- App-only on marketing host:
RootRouter.tsx:71-89,HostGuard+HandoffOnMount(routeGuuards.tsx:78-87). - Session + waitlist acceptance:
SessionAcceptedRoute→RequireSession+RequireAccepted(routeGuards.tsx:125-134,accessRuntime.tsx:115-136). - Waitlist-me hydration race: guarded with
acceptedUnknownto prevent bounce redirect (accessRuntime.tsx:66-79) — tested inaccessRuntime.test.tsx. - Unauthenticated
/swap: redirects to marketing waitlist URL, not an in-app loop (accessShared.tsx:48-55). /accounts//add: UnderLazyAuthWalletBoundarybut withoutRequireSession— intentional for owner-install deep links; APIs must enforce auth server-side.
Summary priority fixes (if you switch to Agent mode)
- Add
dryRunBusyto deploysubmit()guard and deploy buttondisabled. - Pause auto-quote when
busyis'review','quote', ortxState === 'pending'. - Add a ref-based submit mutex in
executeSwapNow/confirmAndExecute. - Global
inflightPayment(or disable all pay buttons) inCreatorStrategyFeatures.tsx.
I can implement these in Agent mode if you want.
[REDACTED]
Was this page helpful?