Skip to content

Fix/remove duplicate stellar sdk#294

Merged
Junman140 merged 83 commits into
Pi-Defi-world:devfrom
DavisVT:fix/remove-duplicate-stellar-sdk
Apr 29, 2026
Merged

Fix/remove duplicate stellar sdk#294
Junman140 merged 83 commits into
Pi-Defi-world:devfrom
DavisVT:fix/remove-duplicate-stellar-sdk

Conversation

@DavisVT
Copy link
Copy Markdown
Contributor

@DavisVT DavisVT commented Apr 25, 2026

closes #181

Summary by CodeRabbit

Release Notes

  • New Features

    • Help Center page with FAQ, support contact form, and system status
    • Lending service for loan applications
    • SME Services onboarding page
    • Session and API key management in security settings
  • Security Improvements

    • Redesigned authentication to use secure httpOnly cookies
    • Enhanced password requirements (minimum 12 characters with strength meter)
    • Wallet secrets now encrypted with account passcode
    • Added Content Security Policy headers for improved protection
  • Bug Fixes & Improvements

    • Currency page displays live exchange rates
    • Enhanced error messages with recovery options across all flows
    • Improved form validation and user feedback
    • Better session handling on page refresh

temisan0x and others added 30 commits April 23, 2026 09:05
…xit (Pi-Defi-world#220)

- Add BackButton component with fallback-safe navigation pattern
- Component uses router.back() only when in-app history exists
- Falls back to explicit in-app route when arriving via direct URL/external link
- SSR-safe with window access guard
- Audit confirms 0 router.back() calls exist in codebase
- All existing back navigation already uses explicit Link components
…EMENT docs

Closes Pi-Defi-world#228

- Add MetricLabel component with Info icon tooltip on each health metric
- Metrics covered: Total reserves, Total ACBU supply, Collateral ratio, Health
- Per-currency metrics: Balance, USD value, Target weight, Actual weight
- Each tooltip links to /docs/RESERVE_MANAGEMENT for deeper reading
- Addresses F-057: users misinterpreting health metrics on reserves page
…p-passcode-enforcement

Harden signup passcode policy and add client-side strength meter
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
- Remove API key storage from sessionStorage (XSS vulnerability)
- Implement httpOnly cookie-based authentication
- Move passcode to secure in-memory storage
- Add Content Security Policy headers via middleware
- Add security headers (X-Frame-Options, X-Content-Type-Options, etc.)

BREAKING CHANGE: Users will need to re-authenticate after deployment.
Backend must set httpOnly session cookies on login.

Fixes critical security vulnerability where long-lived API keys
stored in sessionStorage could be exfiltrated via XSS attacks.

Files changed:
- contexts/auth-context.tsx: Remove API key state
- lib/api/client.ts: Remove Bearer token headers
- app/auth/signin/page.tsx: Update login flow
- app/auth/2fa/page.tsx: Update 2FA flow
- lib/passcode-manager.ts: NEW - In-memory passcode storage
- lib/wallet-storage.ts: Use in-memory passcode
- middleware.ts: NEW - CSP and security headers
- SECURITY_FIX_SUMMARY.md: Complete documentation
- DEPLOYMENT_CHECKLIST.md: Deployment guide
- Create new Help Center page with FAQ section (top 10 questions)
- Add system status banner with link to status page
- Add contact support form for ticket submission
- Add quick links to documentation, status, and community
- Add Help Center link to Me page navigation

Addresses low severity issue: Help page too thin
Impact: Reduces support load by enabling self-service for common questions

Features:
- FAQ accordion with 10 most common questions
- Contact form with name, email, subject, and message fields
- Status page integration
- Links to external resources (docs, community)
- Security contact information for urgent issues
- Mobile-responsive design

Acceptance criteria met:
 Users can self-serve top 10 questions via FAQ
 Status page link for service uptime
 Ticket form for support requests
 Quick access from Me page
- Remove token-based auth remnants
- Fix passcode storage shadowing in signin
- Add session validation on page load
- Tighten CSP for production
- Replace plaintext wallet storage with encryption
- Add 2FA passcode validation guard
- Store passcode in memory instead of sessionStorage
- Fix passcode storage before 2FA redirect
- Remove API key from sessionStorage
- Use secure in-memory passcode manager
- Fix stale comment in wallet-setup-modal
- Add passcode guard in wallet-setup page to prevent dead-end state
- Improve wallet page error handling with logout and redirect
- Fix double handler registration in auth context with cleanup
Distinguish between 401 (invalid session) and network errors.
Only clear auth state on 401, preserve it on transient failures.
- Remove legacy stellar-sdk@^13.3.0 from dependencies
- Add pnpm override to force all transitive deps to @stellar/stellar-sdk@^15.0.1
- Add peerDependencyRules to silence the trezor plugin peer warning
- Lockfile now resolves a single stellar-sdk version (15.0.1)

Closes Pi-Defi-world#181
- Add KycBadge component driven by user.kyc_status from the existing
  /users/me endpoint (UserMe already exposes kyc_status)
- Four status variants: verified/approved → green, pending/under_review/
  submitted → yellow, rejected/failed → red, default → grey 'KYC Required'
- Badge shows animated skeleton while profile data is loading
- No extra API call needed — kyc_status is already returned by getMe()

Closes Pi-Defi-world#200
Junman140 and others added 21 commits April 25, 2026 06:04
…pi-key-from-sessionstorage

fix: remove API key from sessionStorage to prevent XSS attacks
…rehensive-help-page

feat: add comprehensive help center page
…uccess-message

fix: improve signin security and passcode handling
…e-stellar-sdk

fix(deps): remove duplicate stellar-sdk, keep only @stellar/stellar-sdk
…ge-dynamic

fix(me): bind KYC badge to live kyc_status — F-029
…rn-tab-real-flow

fix(mint): route burn tab to /burn with prefilled state — F-023
…ings-withdraw-recipient-editable

Feature/f 027 savings withdraw recipient editable
…-ctas-inert

F-017: Fix SME CTA route and activate application mailto flow
…avings-mock-data

fix: remove mock savings account constants, derive balance from API only
…urity-settings-placeholder

F-022: Add security settings management UI with sessions and API keys
…avings-deposit-handler

fix: wire savings deposit dialog to API and show pending/completed st…
…ce-middleware

fix(security): add strict CSP via nonce middleware — F-064
- Remove legacy stellar-sdk@^13.3.0 from dependencies
- Add pnpm.overrides to force all transitive deps to @stellar/stellar-sdk@^15.0.1
- Add peerDependencyRules entry to silence the trezor plugin peer warning
- Lockfile now resolves a single stellar-sdk version (15.0.1)

Closes Pi-Defi-world#181
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 25, 2026

📝 Walkthrough

Walkthrough

This PR implements comprehensive authentication hardening and security infrastructure updates. It removes sessionStorage-based API keys and passcodes, switches to httpOnly cookie-based authentication, introduces an in-memory passcode manager, adds application-wide error reporting and handling, centralizes API error state management, and adds features for lending and help center support.

Changes

Cohort / File(s) Summary
Configuration & Documentation
.claude/settings.local.json, DEPLOYMENT_CHECKLIST.md, HELP_PAGE_SUMMARY.md, SECURITY_FIX_SUMMARY.md, SECURITY_FIXES_PART2.md, pr.md
New configuration, deployment checklists, and security/feature documentation capturing authentication refactoring, security headers, passcode encryption, and deployment verification steps.
Security & Auth Infrastructure
lib/passcode-manager.ts, lib/wallet-storage.ts, contexts/auth-context.tsx, lib/api/client.ts, lib/api/user.ts
Core security refactoring: removes bearer-token/API-key management, adds in-memory passcode-only storage, switches to httpOnly cookie auth (credentials: 'include'), implements request timeout, and updates session rehydration via getMe().
Auth Pages
app/auth/signin/page.tsx, app/auth/signup/page.tsx, app/auth/2fa/page.tsx, app/auth/wallet-setup/page.tsx
Updates authentication flows to use in-memory passcode storage, enforce 12+ character passcode with strength meter on signup, validate passcode presence before 2FA verification, and encrypt wallet secrets with passcode.
Error Reporting & Boundary Infrastructure
lib/error-reporting.ts, components/error-boundary.tsx, components/error-test-trigger.tsx, components/global-error-handler.tsx
New error reporting module with global error handler, localStorage persistence of errors, and expanded ErrorBoundary with level-aware layouts (component/page/app) and test trigger for development.
Error Pages
app/auth/error.tsx, app/bills/error.tsx, app/error.tsx, app/global-error.tsx, app/me/error.tsx, app/send/error.tsx, app/savings/error.tsx, app/transactions/error.tsx, app/wallet/error.tsx
New error UI components across app routes reporting errors via errorReporter, displaying digest/error details, and providing retry/navigation actions.
API Error Handling
hooks/use-api-error.ts, components/ui/api-error-display.tsx
New hook and component for centralized API error state (maps HTTP errors, rate-limiting, payment processor down) with timed retry blocks and contextual action buttons.
Form & Money Flow Pages
app/currency/page.tsx, app/burn/page.tsx, app/mint/page.tsx, app/fiat/page.tsx, app/rates/page.tsx
Refactors forms to use react-hook-form/zod, centralizes API error handling via useApiError, derives exchange rates from /rates endpoint, updates success dialogs with backend response details, and replaces hardcoded fees/amounts.
Savings & Lending Features
app/savings/page.tsx, app/savings/deposit/page.tsx, app/savings/withdraw/page.tsx, lib/lending-store.ts, app/lending/page.tsx, app/lending/admin/page.tsx
Implements recipient resolution flow with async identifier validation, removes savings-account UI, adds lending product selector/application form with local persistence, and new admin dashboard for approving/rejecting applications.
Send & Profile Pages
app/send/page.tsx, app/send/page.test.tsx, app/me/page.tsx, app/me/settings/security/page.tsx, app/business/page.tsx, app/business/sme/page.tsx
Refactors send confirmation to snapshot amount, virtualizes contacts list, adds new KYC badge to Me page, extends security settings with 2FA toggle/sessions/API key management, adds SME onboarding route, and test coverage for send amount preservation.
Help Center & UI Enhancements
app/help/page.tsx, app/page.tsx, app/reserves/page.tsx, components/navigation/BackButton.tsx, components/mobile-nav.tsx, components/wallet-setup-modal.tsx
Adds client-rendered help/FAQ page with status banner and support form, home grid lending tile, metric tooltips with docs links in reserves, new back-button component, aria-label accessibility fix, and wallet setup messaging about passcode encryption.
Layout & Configuration
app/layout.tsx, middleware.ts, app/api/csp-report/route.ts, next.config.mjs, lib/features.ts
Adds x-nonce support for Vercel Analytics, mounts GlobalErrorHandler, integrates ErrorBoundary at app level, new CSP header middleware, CSP violation reporting endpoint, disables "powered by" header, and documents feature flag defaults.
Package Management
package.json
Removes duplicate stellar-sdk dependency, pins @stellar/stellar-sdk@^15.0.1 via pnpm overrides, and adds peer-dependency rules for plugin compatibility.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes


Possibly related issues

Possibly related PRs


Poem

🐰 Hop along, the auth flows gleam,
No tokens hiding—just secure dreams.
Passwords encrypted, errors well-caught,
The frontend's fortress is tightly wrought! 🔐

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning The PR contains extensive out-of-scope changes unrelated to the stellar-sdk dependency fix, including security hardening (CSP, httpOnly cookies, passcode encryption), new features (lending, help page, 2FA), error handling infrastructure, and multiple UI refactors across ~50 files. Isolate the stellar-sdk fix (package.json + related imports) into a separate focused PR. Address security/feature work in dedicated PRs with appropriate titles and issue linkage.
Docstring Coverage ⚠️ Warning Docstring coverage is 38.10% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'Fix/remove duplicate stellar sdk' directly and clearly describes the main change: removing the duplicate stellar-sdk dependency to resolve issue #181.
Linked Issues check ✅ Passed The PR successfully addresses issue #181's requirement to remove the legacy stellar-sdk dependency, update the lockfile to resolve only one version, and migrate to @stellar/stellar-sdk. The package.json changes demonstrate both removal and override configuration.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

Note

Due to the large number of review comments, Critical severity comments were prioritized as inline comments.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (15)
app/wallet/page.tsx (1)

69-86: ⚠️ Potential issue | 🟠 Major

Reorder: persist the encrypted secret only after the backend accepts the new wallet address.

Right now the encrypted secret is written to local storage on line 73 before putWalletAddress runs on line 76. If the PUT fails (network error, 4xx, or backend mismatch on result.ok), the throw at line 78 leaves a local key for an address the backend doesn't know about — exactly the divergence that components/wallet-setup-modal.tsx:71-81 explicitly warns against and is structured to avoid (PUT → store → confirm).

The same ordering issue exists in handleImportSeed (lines 122 → 125).

🐛 Proposed fix (handleGenerateConfirm)
       const kp = Keypair.fromSecret(passphrase);
       const newAddress = kp.publicKey();

-      // Store encrypted with passcode (secure)
-      await storeWalletSecret(userId, passphrase, passcode);
-
       // Sync public key to backend.
       const result = await userApi.putWalletAddress(newAddress, opts);
       if (!result?.ok) {
         throw new Error("Backend did not accept the new wallet address. Please retry.");
       }

+      // Backend accepted the address — now persist the encrypted secret.
+      await storeWalletSecret(userId, passphrase, passcode);
+
       // Confirm wallet activation on backend
       try {
         await userApi.postWalletConfirm({ wallet_address: newAddress }, opts);

Apply the same swap to handleImportSeed (move the storeWalletSecret(userId, importSeed, passcode) call to after the putWalletAddress ok-check).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/wallet/page.tsx` around lines 69 - 86, The code stores the encrypted
secret (storeWalletSecret) before the backend accepts the new wallet address,
which can leave a local key for an unknown address; update both
handleGenerateConfirm and handleImportSeed so they first call
userApi.putWalletAddress(newAddress/...) and verify result?.ok, only then call
storeWalletSecret(userId, passphrase/importSeed, passcode), and finally call
userApi.postWalletConfirm(...) (preserving the existing try/catch around
confirm). Ensure you throw on non-ok PUT results before calling
storeWalletSecret so local persistence only happens after a successful backend
acceptance.
app/auth/signin/page.tsx (2)

84-88: ⚠️ Potential issue | 🟡 Minor

Clear in-memory passcode on signin failure.

If authApi.signin succeeds (so storePasscode(passcode) runs on the 2FA branch at line 60) but a subsequent step throws — e.g., result.challenge_token is missing, sessionStorage.setItem fails, or router.push rejects — the catch handler surfaces the error but leaves the passcode lingering in memory tied to a session that never completed. Recommend calling clearPasscode() (and removing 2fa_challenge_token) in the catch to keep state consistent with a failed login.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/auth/signin/page.tsx` around lines 84 - 88, When signin fails after a
successful authApi.signin that previously called storePasscode(passcode), ensure
the catch block in page.tsx clears that transient state by invoking
clearPasscode() and removing the stored 2FA token (e.g.,
sessionStorage.removeItem("2fa_challenge_token") or the equivalent
token-clearing helper) before setting the error; this guarantees any in-memory
passcode and leftover challenge token are cleaned up when the login flow aborts.

77-82: ⚠️ Potential issue | 🟠 Major

Wallet passphrase still persisted in sessionStorage — undermines the in-memory passcode story.

Lines 60 and 73 deliberately move the passcode from sessionStorage to in-memory storage to reduce XSS exposure. But on the wallet-creation branch the freshly-issued result.passphrase (the Stellar wallet seed) is written straight to sessionStorage, where any XSS payload can read it for the lifetime of the tab — and the passphrase is far more sensitive than the passcode (full custody of the wallet).

If the goal is to harden secrets handling, the passphrase should ride the same in-memory channel as the passcode (e.g. a sibling helper in lib/passcode-manager.ts or a dedicated wallet-bootstrap store) and be consumed by app/auth/wallet-setup/page.tsx from memory. As written, the threat model improvement is only partial.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/auth/signin/page.tsx` around lines 77 - 82, The code writes the
newly-issued wallet seed (result.passphrase) into sessionStorage
(sessionStorage.setItem) which defeats the in-memory passcode model; instead
stop persisting the passphrase to sessionStorage in the wallet-creation branch
and store it in the same in-memory manager used for the passcode (e.g. add a
setTempPassphrase/getTempPassphrase API to lib/passcode-manager.ts or a
dedicated wallet-bootstrap store), then navigate to wallet-setup
(router.push('/auth/wallet-setup')) where app/auth/wallet-setup/page.tsx reads
and consumes the passphrase from that in-memory API (and ensure it is cleared
after consumption); remove the sessionStorage.setItem call and any fallbacks
that persist the seed to browser storage.
app/mint/page.tsx (1)

328-403: ⚠️ Potential issue | 🟡 Minor

Dead code — handleExecuteBurn is no longer reachable.

Now that handleBurnConfirm deep-links to /burn and handleExecute only dispatches on activeTab === "mint", this entire ~75-line burn execution path is unreachable (ESLint also flags it: 'handleExecuteBurn' is assigned a value but never used). Removing it will also let you drop the submitBurnRedeemSingleClient import (used only here) and shrink the bundle. A bonus: the "Confirm Burn" branch in the success/confirm <AlertDialog> (lines 705-718, 753-756) becomes dead too — step only flips to "confirm" on mint now — so the dialog can be simplified to mint-only copy.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/mint/page.tsx` around lines 328 - 403, The handleExecuteBurn function
(and its import usage) is dead code because burning is no longer reachable;
remove the entire handleExecuteBurn function definition and any references to
it, delete the now-unused submitBurnRedeemSingleClient import, and simplify the
success/confirm AlertDialog by removing the "Confirm Burn" branch and any logic
that sets step to "confirm" for burn flows (leave only mint-related
confirm/success logic). Ensure you also remove any state or helpers used
exclusively by handleExecuteBurn (e.g., burn-specific setTxId/setBurnApiError
usage if they become unused) to avoid leftover unused variables and ESLint
warnings.
pr.md (1)

1-199: ⚠️ Potential issue | 🟠 Major

Delete pr.md — appears to be an accidentally committed PR description, and for the wrong PR.

Two problems:

  1. Wrong content. This file is a pull-request description for "fix(currency): replace mock balance/rates with real API + add toasts" closing #190. PR #294 is "Fix/remove duplicate stellar sdk" closing #181. None of the changes described here (app/currency/page.tsx, <Toaster /> mount in app/layout.tsx, useBalance() rewiring, etc.) are part of the file set under review.
  2. Wrong location. PR descriptions belong in the GitHub PR body, not committed to the repo. As a tracked file, it will pollute search results, drift out of sync with reality, and ship with every release artifact.

Recommend git rm pr.md and pasting the relevant prose into the PR body on GitHub instead.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pr.md` around lines 1 - 199, pr.md is an accidental committed PR description
that doesn't belong in the repo; remove the tracked file by running git rm
pr.md, commit the removal (e.g., "chore: remove accidental pr.md"), and push the
change so it no longer ships or pollutes search results; then copy any useful
text into the GitHub PR body (not the repository) and ensure no other commits
reintroduce a PR body file.
app/savings/page.tsx (2)

178-197: ⚠️ Potential issue | 🟠 Major

"Total Savings" card now duplicates "Savings balance (API)".

After the change, both the "Savings balance (API)" card (lines 160-176) and the "Total Savings" card (lines 178-197) render the same apiBalance value (totalSavings = apiBalance on line 135). The only differences are:

  • A still-hardcoded "Earning 8% APY interest" caption.
  • A still-hardcoded (totalSavings * 0.08) / 12 "+this month" line that has no relation to any backend rate.

A previously-retrieved learning notes that the "Total Savings" card was intentional mock/placeholder data on the route-grouped variant of this page. Now that it shows the real balance alongside another card showing the same real balance, the duplication is user-visible and the 8% APY copy is misleading. Either:

  • delete this card entirely (was that the intent?), or
  • replace 8% APY + "+this month" with values sourced from the savings API.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/savings/page.tsx` around lines 178 - 197, The "Total Savings" Card
duplicates the "Savings balance (API)" by showing totalSavings (which equals
apiBalance) and uses a hardcoded 8% APY and derived monthly amount; either
remove the Card or wire it to real API fields—update the Card that renders
totalSavings/positionsLoading/formatAmount to instead read the savings APY and
monthly interest from the API response (e.g., use the API field name such as
savings.apy or savingsRate if available), compute monthlyInterest = totalSavings
* apy / 12, replace the hardcoded "Earning 8% APY interest" and the hardcoded
+this month line with values derived from that apy and monthlyInterest, or
delete the Card entirely if duplication was intended.

51-59: ⚠️ Potential issue | 🔴 Critical

Remove the dead SavingsAccount interface with unimported LucideIcon type reference.

The SavingsAccount interface (lines 51–59) is completely unused throughout the codebase and references LucideIcon which is not imported in this file. Since the entire Savings Accounts UI was removed, this interface should be deleted.

Additionally, the "Total Savings" card (lines 178–197) duplicates the "Savings balance (API)" card immediately above it—both display the same positionsBalance value. Consider removing the duplicate card or clarifying its purpose.

♻️ Suggested change
-interface SavingsAccount {
-    id: string;
-    name: string;
-    apy: number;
-    balance: number;
-    icon: LucideIcon;
-    description: string;
-    color: string;
-}
-
 interface SavingsGoal {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/savings/page.tsx` around lines 51 - 59, Remove the dead SavingsAccount
interface (named SavingsAccount) that references the unimported LucideIcon type;
delete the entire interface declaration and any unused imports related to
LucideIcon. Also remove or consolidate the duplicate UI card that displays
positionsBalance: locate the two cards labeled "Savings balance (API)" and
"Total Savings" (both rendering positionsBalance) and either delete the "Total
Savings" card or change its data/label so it is not a duplicate (ensure only one
card shows positionsBalance or that each card displays distinct computed
values).
app/savings/withdraw/page.tsx (1)

1-285: ⚠️ Potential issue | 🔴 Critical

Critical: this file is broken — multiple undefined references, missing imports, and dead refactor remnants. Will not compile.

Going through the actual identifiers used:

  • Line 48: RecipientResponse is referenced as a type but is not imported in this file. Add import type { RecipientResponse } from "@/types/api";.
  • Line 76: setUser(resolved) — there is no user / setUser state in this file. Lines 217 and 276 also read a non-existent user (and call user.trim()), so the form will throw ReferenceError at render time. Either rename to homeRecipient / setHomeRecipient consistently, or reintroduce a user state.
  • Line 100: recipientApi.resolveRecipient(...) — the import on line 15 is import { resolveRecipient } from "@/lib/api/recipient";. There is no recipientApi namespace import, so this throws ReferenceError the first time validateRecipient runs.
  • Line 151: setApiError(e)useApiError is imported (line 11) but never called/destructured. setApiError is undefined here. Also ApiErrorDisplay is imported but never rendered (the catch block falls back to a manual destructive <div> at lines 188-193 keyed off the legacy error string state).
  • Line 39 / 53: setHomeRecipient is declared but never called, so homeRecipient stays ""; isRecipientChanged = recipient.trim() !== homeRecipient.trim() then becomes true as soon as the user types anything, which forces the "confirm different recipient" checkbox / submission gating on every flow — including the "default" one.
  • Lines 49–50, 94–114, 157–172: recipientValidationLoading, recipientValidationError, validateRecipient, and handleRecipientChange are all defined but never wired to the recipient <Input> (which itself is missing entirely from the JSX — only the read-only id="withdraw-account" Input bound to the broken user exists). The "Change/Reset" button toggles editingRecipient but the rest of the JSX never branches on it.

In its current state the page won't render and the "edit recipient" feature is unreachable. Suggest either:

  1. Reverting the recipient-edit feature for now and shipping only the setApiError/ApiErrorDisplay migration; or
  2. Completing the migration: drop the legacy user/setUser references, add the missing recipient <Input> and confirmation checkbox bound to recipient/handleRecipientChange, destructure useApiError(), render <ApiErrorDisplay /> instead of the manual destructive <div> at 188–193, and import RecipientResponse.

I'd recommend (1) for this PR since the recipient-validation flow has no UI, then a follow-up PR to fully add the editable recipient experience.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/savings/withdraw/page.tsx` around lines 1 - 285, This file contains
unfinished recipient-edit refactor bits and undefined identifiers; revert the
incomplete edit flow for this PR: remove or ignore recipient-edit related state
and helpers (recipient, setRecipient, editingRecipient, resolvedRecipient,
recipientValidationLoading, recipientValidationError, confirmDifferentRecipient,
validateRecipient, handleRecipientChange, handleToggleEdit), ensure the initial
receive address is saved via setHomeRecipient (replace the incorrect
setUser(resolved) call in the useEffect with setHomeRecipient(resolved) and use
homeRecipient everywhere instead of user), destructure useApiError to get
setApiError and call setApiError(e) in handleSubmit catch, replace the manual
destructive error block with rendering <ApiErrorDisplay /> using the api error
state, and remove or stop referencing the nonexistent recipientApi and
RecipientResponse types for now; this yields a compileable page and allows
reintroducing the full recipient-edit/validation flow in a follow-up.
app/burn/page.tsx (1)

70-368: ⚠️ Potential issue | 🔴 Critical

Critical: file is in an inconsistent half-migrated state and will not compile/run.

This file mixes the old useState-based form with a partial react-hook-form migration, leaving multiple undefined references and a broken JSX structure. Concretely:

  • Line 117–122, 136–137, 169–170, 177–178, 184: values.* is referenced but values is never defined in handleSubmit's scope (the function signature is (e: React.FormEvent)). These would throw ReferenceError at runtime.
  • Line 230: <form onSubmit={formHandleSubmit(onSubmit)}> — neither formHandleSubmit nor onSubmit exist; this matches the Biome parse errors.
  • Line 360: stray </div> with no matching opening <div> — the JSX tree is unbalanced (Biome reports missing closing tags for <Form>/<form>).
  • Lines 75–104 still maintain the old per-field useState and the isValid derivation, but the inputs now bind to form.control only — so the old state is dead/contradictory and isValid no longer reflects what the user typed.

The submit path needs to be a single react-hook-form flow, e.g.:

🔧 Sketch of the corrected wiring
-  const isValid = ... old logic ...
-
-  const handleSubmit = async (e: React.FormEvent) => {
-    e.preventDefault();
-    if (!isValid) return;
-    ...
-    const recipientAccount: BurnRecipientAccount = {
-      account_number: values.accountNumber.trim(),
-      ...
-    };
-    ...
-  };
+  const onSubmit = async (values: BurnFormValues) => {
+    clearError();
+    setLoading(true);
+    setTxId(null);
+    try {
+      if (!userId) throw new Error("Not signed in");
+      if (!stellarAddress) throw new Error("No linked Stellar wallet address.");
+      const recipientAccount: BurnRecipientAccount = {
+        account_number: values.accountNumber.trim(),
+        bank_code: values.bankCode.trim(),
+        account_name: values.accountName.trim(),
+        type: "bank",
+      };
+      // ... rest using `values.acbuAmount` / `values.currency`
+      form.reset({ ...values, acbuAmount: "" });
+    } catch (e) {
+      setApiError(e);
+    } finally {
+      setLoading(false);
+    }
+  };
@@
-          <Form {...form}>
-            <form onSubmit={formHandleSubmit(onSubmit)} className="space-y-4">
+          <Form {...form}>
+            <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
@@
-              />
-            </div>
-            <Button
+              />
+            <Button
                 type="submit"
-                disabled={!isValid || loading || isSubmitDisabled}
+                disabled={!form.formState.isValid || loading || isSubmitDisabled}

Also drop the now-dead per-field useState declarations and the old isValid.

Additionally, the duplicate txId blocks at 214–218 and 220–227 render the transaction ID twice (plain paragraph + success banner) — the plain-text version on 214–218 should be removed.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/burn/page.tsx` around lines 70 - 368, This file mixes old useState form
bits with react-hook-form causing undefined references and broken JSX; remove
the per-field useState declarations (acbuAmount, currency, accountNumber,
bankCode, accountName) and the old isValid logic, then replace the current
handleSubmit(e) with a react-hook-form onSubmit that accepts values (e.g., const
onSubmit = async (values: BurnFormValues) => { ... }); inside onSubmit use
values.accountNumber, values.bankCode, values.accountName, values.acbuAmount and
values.currency, keep the existing secret/kit/burn flow
(submitBurnRedeemSingleClient, getWalletSecretAnyLocal, burnApi.burnAcbu) but
reference form.reset and form.formState.isValid for disabling submit instead of
the old isValid; change the JSX form tag to use form.handleSubmit(onSubmit) (not
formHandleSubmit/onSubmit mismatch), remove the duplicate plain txId paragraph
so only the success banner remains, and fix the unbalanced JSX by ensuring every
opened <Form> and wrapper <div> is properly closed around the <form> block.
lib/api/client.ts (2)

93-129: ⚠️ Potential issue | 🟠 Major

AbortSignal handling: pre-aborted check missing, and listener leaks for long-lived caller signals.

Two related issues in the new abort/timeout block:

  1. Pre-aborted signal is ignored. If a caller passes an opts.signal that is already aborted, line 100 attaches an 'abort' listener that will never fire (the event already happened), and the request still hits the network. Fetch will then be issued with the controller's non-aborted signal. The fix is to abort the local controller immediately:
    if (opts.signal) {
      if (opts.signal.aborted) {
        controller.abort();
      } else {
        opts.signal.addEventListener('abort', () => controller.abort(), { once: true });
      }
    }
  2. Listener leak on long-lived caller signals. addEventListener('abort', ..., { once: true }) is only auto-removed when the event fires. If opts.signal belongs to a component-scoped or app-scoped AbortController (the typical case), every completed request leaves a stale listener attached until that signal eventually aborts. Over a long session this accumulates. Remove the listener when the request resolves:
    const onCallerAbort = () => controller.abort();
    if (opts.signal) {
      if (opts.signal.aborted) controller.abort();
      else opts.signal.addEventListener('abort', onCallerAbort, { once: true });
    }
    try {
      // ... fetch
    } finally {
      clearTimeout(timeoutId);
      opts.signal?.removeEventListener('abort', onCallerAbort);
    }

This also lets you collapse the duplicated clearTimeout calls (lines 119 and 129) into a single finally block.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/api/client.ts` around lines 93 - 129, Handle pre-aborted and leaking
caller signals: if opts.signal exists, immediately abort the local
AbortController when opts.signal.aborted is true instead of only attaching a
listener; otherwise attach a named handler (e.g., onCallerAbort = () =>
controller.abort()) to opts.signal and ensure you remove that listener after the
request completes (opts.signal.removeEventListener('abort', onCallerAbort)).
Move clearTimeout(timeoutId) into a single finally block that also removes the
caller listener to avoid stale handlers, and keep using the local
controller.signal for fetch; update references to controller, signal,
opts.signal, timeoutId and DEFAULT_TIMEOUT accordingly.

56-60: ⚠️ Potential issue | 🟡 Minor

Backend must set XSRF-TOKEN cookie without httpOnly flag — the frontend reads this token via document.cookie (line 58) and sends it as X-XSRF-TOKEN header for CSRF protection. If the backend incorrectly sets httpOnly=true, CSRF protection silently degrades without visible errors.

Recommended XSRF-TOKEN cookie flags:

  • httpOnly=false (must be JavaScript-readable)
  • secure=true
  • sameSite=strict or lax

While SECURITY_FIX_SUMMARY.md documents that the session/auth cookie must be httpOnly=true, the XSRF-TOKEN cookie configuration is not explicitly documented. Add explicit backend requirements to clarify this distinction to prevent accidental misconfigurations.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/api/client.ts` around lines 56 - 60, The frontend's getCsrfToken function
reads the XSRF-TOKEN cookie (match for "XSRF-TOKEN" and then sent as
X-XSRF-TOKEN header), so ensure the backend explicitly sets the XSRF-TOKEN
cookie with httpOnly=false (so document.cookie can read it), secure=true, and
sameSite=lax or strict; update backend cookie-setting logic and add clear
documentation (e.g., in SECURITY_FIX_SUMMARY.md or backend auth config) that
session/auth cookies remain httpOnly=true while XSRF-TOKEN must be non-httpOnly
and JavaScript-readable to preserve CSRF protection.
app/currency/page.tsx (2)

658-676: ⚠️ Potential issue | 🟡 Minor

Confirmation dialog: passing raw string amounts to formatAmount and not showing the computed local amount for burn/international.

Two small UX issues in the confirm dialog:

  1. formatAmount(burnAmount) / formatAmount(intlAmount) (lines 659, 661, 669, 670) pass user-typed strings; if the user typed "10." or "10.5e2" the formatted output may surprise. Prefer the already-coerced burnNumeric/intlNumeric/mintNumeric.
  2. The confirm dialog still says "Calculated by backend" / "Processing fee" without surfacing the computed estimatedBurnNgn or estimatedIntlLocal you already have in scope — the user just clicked through a screen showing "₦X" but the confirm screen hides it. Consider mirroring those estimates here so the confirmation matches the input screen.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/currency/page.tsx` around lines 658 - 676, The confirm dialog is
formatting raw string inputs (formatAmount(burnAmount)/formatAmount(intlAmount))
and hiding backend-computed local estimates; change uses of formatAmount to
accept the already-coerced numeric values (burnNumeric, intlNumeric,
mintNumeric) instead of the raw burnAmount/intlAmount strings, and replace the
static "Calculated by backend" processing fee row with the corresponding
computed estimates (estimatedBurnNgn and estimatedIntlLocal) so the confirmation
dialog mirrors the amounts shown earlier; update references around
AlertDialogDescription / the "Amount:" and "Processing fee:" spans to display
these numeric/coerced values and the estimatedBurnNgn or estimatedIntlLocal when
activeTab is 'burn' or 'international'.

80-200: ⚠️ Potential issue | 🔴 Critical

Critical: balance, balanceLoading, refreshBalance, and toast are referenced but never declared/imported in this file.

After the refactor, the component body uses several identifiers that have no corresponding hook call or import:

  • balance, balanceLoading — used at lines 119, 235, 240, 242, 408, 412, 485 with no useBalance() call and no import.
  • refreshBalance() — called at line 191 after a successful submit.
  • toast({...}) — called at lines 144–147, 168–171, 185–188; there is no useToast() call and no toast import.

The page will throw a ReferenceError (or TS compile error in strict mode) on render and handleExecute will throw before setStep("success") runs, so users will see a stale error and the success dialog will never open.

🐛 Likely fix — restore the missing hooks and imports
 import { useApiOpts } from "@/hooks/use-api";
 import { useApiError } from "@/hooks/use-api-error";
 import { ApiErrorDisplay } from "@/components/ui/api-error-display";
+import { useBalance } from "@/hooks/use-balance";
+import { useToast } from "@/hooks/use-toast";
 import * as mintApi from "@/lib/api/mint";
@@
 export default function CurrencyPage() {
   const opts = useApiOpts();
   const { uiError, setApiError, clearError, isSubmitDisabled } = useApiError();
+  const { balance, loading: balanceLoading, refresh: refreshBalance } = useBalance();
+  const { toast } = useToast();

Adjust to whatever the actual hook names/exports are in this codebase.

#!/bin/bash
# Confirm none of these identifiers are declared in app/currency/page.tsx.
rg -n -C1 -P '\b(balance|balanceLoading|refreshBalance|toast|useToast|useBalance)\b' app/currency/page.tsx

# Confirm the hook signatures we'd be importing.
fd -t f use-balance hooks
fd -t f use-toast hooks components
ast-grep --pattern 'export function useBalance($$$) { $$$ }'
ast-grep --pattern 'export function useToast($$$) { $$$ }'
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/currency/page.tsx` around lines 80 - 200, The file is referencing
balance, balanceLoading, refreshBalance and toast but never imports or calls the
hooks that provide them; add the appropriate hook imports (e.g. import {
useBalance } and import { useToast }) and call them at the top of the component
to expose those identifiers (for example call const { balance, balanceLoading,
refreshBalance } = useBalance(/*args*/); and const toast = useToast();), then
use those values in handleExecute and throughout the component where balance,
balanceLoading, refreshBalance, and toast are used so the references resolve and
refreshBalance() and toast(...) work as intended.
app/send/page.tsx (1)

152-242: ⚠️ Potential issue | 🔴 Critical

Stale closure: confirmedAmount is missing from handleConfirmTransfer's dependency array.

handleConfirmTransfer reads confirmedAmount at lines 176, 209, 217, and 225, and writes it to setLastSentAmount at line 225, but confirmedAmount is not in the deps array (line 242).

Repro: user types amount → setConfirmedAmount(amount) runs on Continue → React re-renders, but because amount didn't change again between the previous render and now, useCallback returns the previously memoized closure where confirmedAmount is still "". Clicking "Send ACBU" then submits amount_acbu: "" to transfersApi.createTransfer and signs a Stellar payment for "". Best case: backend 400. Worst case: the Stellar SDK interprets it as 0 and signs a no-op transaction, but the user sees "Transfer Sent!" anyway.

Also worth adding to deps: clearError and setApiError (custom-hook setters that aren't necessarily stable across renders).

🐛 Proposed fix
-  }, [amount, getToValue, note, userId, stellarAddress, kit, opts, loadTransfers, refreshBalance]);
+  }, [
+    amount,
+    confirmedAmount,
+    getToValue,
+    note,
+    userId,
+    stellarAddress,
+    kit,
+    opts,
+    loadTransfers,
+    refreshBalance,
+    clearError,
+    setApiError,
+  ]);

If useApiError's setters are guaranteed stable (returned from useCallback/useMemo), they can be omitted, but lock that down with a comment in the hook so future refactors don't silently break this callback.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/send/page.tsx` around lines 152 - 242, handleConfirmTransfer closes over
confirmedAmount (and uses clearError and setApiError) but doesn't include them
in its useCallback dependency array, which can cause stale values to be
submitted; update the deps for useCallback that defines handleConfirmTransfer to
include confirmedAmount, clearError, and setApiError (or ensure those setters
are stable and document that in useApiError) so the latest confirmedAmount and
error handlers are captured when creating transfers and setting API errors.
SECURITY_FIX_SUMMARY.md (1)

1-122: ⚠️ Potential issue | 🟠 Major

PR scope mismatch with stated objectives.

This PR's title is "Fix/remove duplicate stellar sdk" and it claims to close issue #181 (duplicate @stellar/stellar-sdk and stellar-sdk). However, this file (and the entire diff under review) documents and implements a large auth/security hardening effort — httpOnly cookie auth, in-memory passcode manager, CSP middleware, error-reporting infrastructure, lending admin page, etc. None of the reviewed files touch package.json, pnpm-lock.yaml, or stellar-sdk imports.

Either:

  • The PR title/description is wrong (these security changes belong to a different ticket and need their own description, security review, and acceptance criteria), or
  • The dedup commits were rebased into the wrong branch and the security changes here are unintended for this PR.

Please reconcile before merging — reviewers/deployers will read "remove duplicate stellar-sdk" and miss the substantive auth/CSP changes that need careful sign-off.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@SECURITY_FIX_SUMMARY.md` around lines 1 - 122, The PR title/issue (`#181`) and
branch appear mismatched: SECURITY_FIX_SUMMARY.md documents large
auth/CSP/security changes (files like contexts/auth-context.tsx,
lib/api/client.ts, lib/passcode-manager.ts, middleware.ts,
lib/wallet-storage.ts) but the PR claims to "remove duplicate stellar-sdk" and
contains no package.json/pnpm-lock changes; reconcile by either (A) updating the
PR title, description, and issue reference to accurately reflect the security
hardening work (list the affected symbols/files in the description and add
acceptance criteria from SECURITY_FIX_SUMMARY.md) or (B) remove/revert the
security commits from this branch and open a separate PR for the security
changes (use git revert/cherry-pick to isolate commits touching
contexts/auth-context.tsx, lib/api/client.ts, lib/passcode-manager.ts,
middleware.ts, lib/wallet-storage.ts, SECURITY_FIX_SUMMARY.md), then ensure the
"remove duplicate stellar-sdk" PR only contains package.json/pnpm-lock and
actual import fixes for `@stellar/stellar-sdk` vs stellar-sdk before
re-submitting.
🟡 Minor comments (14)
app/business/sme/page.tsx-8-8 (1)

8-8: ⚠️ Potential issue | 🟡 Minor

Remove unused ArrowRight import.

ESLint flags ArrowRight as imported but never used in this file.

🧹 Proposed fix
-import { ArrowLeft, Briefcase, Mail, ArrowRight } from 'lucide-react';
+import { ArrowLeft, Briefcase, Mail } from 'lucide-react';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/business/sme/page.tsx` at line 8, The import list includes an unused
symbol ArrowRight which ESLint flags; remove ArrowRight from the named import
(the import statement that currently reads something like import { ArrowLeft,
Briefcase, Mail, ArrowRight } from 'lucide-react') so only used icons
(ArrowLeft, Briefcase, Mail) remain, then run lint to confirm the warning is
gone.
components/navigation/BackButton.tsx-47-58 (1)

47-58: ⚠️ Potential issue | 🟡 Minor

Redundant accessible name when no children are provided.

The <button> already has aria-label={label}, which becomes the accessible name and causes screen readers to ignore the button's inner text content. The <span className="sr-only">{label}</span> on Line 56 therefore contributes nothing for assistive tech in the default (icon-only) case, and when consumers pass custom children containing visible text, aria-label will override that visible text as the accessible name — usually the opposite of what's intended (WCAG 2.5.3 "Label in Name").

Consider only setting aria-label when there are no visible-text children, and dropping the redundant sr-only span:

♻️ Suggested change
   return (
     <button 
       onClick={handleBack} 
       className={className} 
-      aria-label={label}
+      aria-label={children ? undefined : label}
       type="button"
     >
       {children || (
         <>
           <ArrowLeft className="w-5 h-5 text-primary" aria-hidden="true" />
-          {label && <span className="sr-only">{label}</span>}
         </>
       )}
     </button>
   );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/navigation/BackButton.tsx` around lines 47 - 58, The BackButton
component currently always sets aria-label={label} and also renders a visually
hidden <span className="sr-only">{label}</span>, causing redundant/overriding
accessible names; update the component so it only applies aria-label when there
are no visible-text children (i.e., when children is falsy or only icon), remove
the redundant sr-only span from the default rendering, and ensure the button
still renders the ArrowLeft icon and respects className and onClick (handleBack)
while letting provided children serve as the accessible name when present.
HELP_PAGE_SUMMARY.md-158-158 (1)

158-158: ⚠️ Potential issue | 🟡 Minor

Stale/incorrect PR link.

The "PR Link" points to coderchris1234/acbu-frontend on a feature/comprehensive-help-page branch, but this PR is on Pi-Defi-world/acbu-frontend from fix/remove-duplicate-stellar-sdk. Update or remove the link to avoid confusion for future readers of this doc.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@HELP_PAGE_SUMMARY.md` at line 158, The "PR Link" entry in
HELP_PAGE_SUMMARY.md is stale; update or remove it. Either replace the URL under
the "**PR Link:**" line with the correct PR for Pi-Defi-world/acbu-frontend from
the fix/remove-duplicate-stellar-sdk branch, or remove the PR Link line entirely
to avoid confusion; make the change to the "**PR Link:**" line so the document
references the correct repository/branch.
HELP_PAGE_SUMMARY.md-67-70 (1)

67-70: ⚠️ Potential issue | 🟡 Minor

Add a language to the fenced code block (MD040).

Flagged by markdownlint.

📝 Proposed fix
-```
+```text
 app/help/page.tsx          - Main help center page
 app/me/page.tsx            - Updated with help link

</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against the current code and only fix it if needed.

In @HELP_PAGE_SUMMARY.md around lines 67 - 70, The fenced code block in
HELP_PAGE_SUMMARY.md is missing a language tag (MD040); update the opening fence
from totext for the block that contains the lines "app/help/page.tsx

  • Main help center page" and "app/me/page.tsx - Updated with help
    link" so markdownlint recognizes the language and the rule is satisfied.

</details>

</blockquote></details>
<details>
<summary>package.json-88-91 (1)</summary><blockquote>

`88-91`: _⚠️ Potential issue_ | _🟡 Minor_

**Remove the `eslint-plugin-react>eslint: "^10"` allowedVersions entry.**

eslint-plugin-react@7.37.5 already declares native peer support for `eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7`. Since the installed eslint is pinned to `^9.11.1` (line 73), which falls within `^9.7`, no override rule is needed. The `^10` entry doesn't match the installed version and serves no purpose—it's a leftover from before the eslint downgrade.

<details>
<summary>Proposed fix</summary>

```diff
     "peerDependencyRules": {
       "allowedVersions": {
-        "eslint-plugin-react>eslint": "^10",
         "@trezor/connect-plugin-stellar>@stellar/stellar-sdk": "^15.0.1"
       }
     }
```
</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
Verify each finding against the current code and only fix it if needed.

In `@package.json` around lines 88 - 91, Remove the unnecessary allowedVersions
override for "eslint-plugin-react>eslint": "^10" from the package.json
allowedVersions block; locate the allowedVersions object (key "allowedVersions")
and delete the entry with the key "eslint-plugin-react>eslint" so that
eslint-plugin-react's native peer range is used (no other changes needed).
```

</details>

</blockquote></details>
<details>
<summary>app/auth/error.tsx-25-29 (1)</summary><blockquote>

`25-29`: _⚠️ Potential issue_ | _🟡 Minor_

**Possible recovery loop if the error originates on `/auth/signin` itself.**

This boundary covers the entire `app/auth/*` segment, so an error thrown while rendering `/auth/signin` will mount this component, and the "Sign in" button will redirect right back to the same broken route. Consider either pointing the recovery action at a stable landing (e.g. `/`) when `window.location.pathname` already starts with `/auth/signin`, or letting `reset()` be the only retry path for that case.

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
Verify each finding against the current code and only fix it if needed.

In `@app/auth/error.tsx` around lines 25 - 29, handleGoToSignIn currently always
sets window.location.href to '/auth/signin' which can cause a recovery redirect
loop if the error occurred on /auth/signin; modify handleGoToSignIn to first
check window.location.pathname (or startsWith('/auth/signin')) and if already on
that path call the provided reset() retry function (or redirect to a stable
landing like '/'), otherwise navigate to '/auth/signin'; update references in
the error boundary component so the button uses this new logic instead of
unconditionally setting window.location.href.
```

</details>

</blockquote></details>
<details>
<summary>lib/error-reporting.ts-13-13 (1)</summary><blockquote>

`13-13`: _⚠️ Potential issue_ | _🟡 Minor_

**Replace `any` with `unknown` (or a narrower record type).**

ESLint is flagging this. `Record<string, unknown>` is the safer default; consumers that need to index into it can narrow at the call site.

<details>
<summary>♻️ Diff</summary>

```diff
-  context?: Record<string, any>;
+  context?: Record<string, unknown>;
```
</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
Verify each finding against the current code and only fix it if needed.

In `@lib/error-reporting.ts` at line 13, Replace the permissive type on the
context property with a safer type: change the declaration of context (currently
"context?: Record<string, any>;") to use Record<string, unknown> (or a narrower
record type) in lib/error-reporting.ts; update any call sites that index into
context to perform proper type narrowing or type assertions before using
properties so consumers can safely access keys.
```

</details>

</blockquote></details>
<details>
<summary>lib/error-reporting.ts-73-82 (1)</summary><blockquote>

`73-82`: _⚠️ Potential issue_ | _🟡 Minor_

**`getStoredErrors` doesn't validate that the parsed value is an array.**

If `localStorage['app_errors']` is ever corrupted to a non-array (manual edit, version skew, an older entry written by a future change), `JSON.parse` will succeed, the cast to `ErrorReport[]` lies, and the very next call site — `errors.push(report)` at line 59 — throws a `TypeError` on whatever is returned. That throw lands in the outer catch (line 65) and gets logged, but every subsequent error report silently fails the same way until storage is cleared.

<details>
<summary>♻️ Suggested guard</summary>

```diff
-      const stored = localStorage.getItem('app_errors');
-      return stored ? JSON.parse(stored) : [];
+      const stored = localStorage.getItem('app_errors');
+      if (!stored) return [];
+      const parsed = JSON.parse(stored);
+      return Array.isArray(parsed) ? (parsed as ErrorReport[]) : [];
```
</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
Verify each finding against the current code and only fix it if needed.

In `@lib/error-reporting.ts` around lines 73 - 82, The getStoredErrors function
should validate that JSON.parse(localStorage.getItem('app_errors')) returns an
array before treating it as ErrorReport[]; replace the blind cast with a check
(e.g., if (!Array.isArray(parsed)) return []) and optionally verify elements are
objects with the expected keys, returning [] on any validation failure so
callers like the errors.push(report) site won't hit a TypeError when storage is
corrupted or holds non-array data.
```

</details>

</blockquote></details>
<details>
<summary>app/send/page.test.tsx-1-198 (1)</summary><blockquote>

`1-198`: _⚠️ Potential issue_ | _🟡 Minor_

**This `.test.tsx` file contains no executable tests — it exports test source as strings.**

Two practical problems:

1. Jest/Vitest will discover this file (via the `.test.tsx` suffix) but find no `test(...)`/`it(...)` calls. Most default configs treat that as a hard failure ("Your test suite must contain at least one test"). At best it's a no-op; at worst it breaks CI.
2. Tests pasted as template literals never actually execute — they will silently rot as `app/send/page.tsx` evolves.

Recommendation: either convert these to real RTL tests (one `test(...)` per scenario, importing `SendPage` directly), or move the documentation portion to a `.md`/`README` and delete this file. If you want, I can draft a real `app/send/page.test.tsx` that exercises `confirm-amount` end-to-end.

Also, `import React from 'react'` on line 24 is unused (flagged by ESLint).

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
Verify each finding against the current code and only fix it if needed.

In `@app/send/page.test.tsx` around lines 1 - 198, This file currently exports
test strings (TEST_1_CONFIRM_AMOUNT_DISPLAYED,
TEST_2_AMOUNT_PRESERVED_AFTER_CANCEL, TEST_3_AMOUNT_CLEARED_AFTER_SUCCESS) and
has an unused import React, so CI will fail and the tests won't run; either (A)
convert those string fixtures into real RTL tests by replacing the exported
template literals with actual test(...) blocks that import SendPage and use
`@testing-library/react` and user-event to assert data-testid="confirm-amount"
behavior (create tests named to match the TEST_... constants), or (B) move the
long documentation block into a markdown/README and delete this .test.tsx so
Jest/Vitest no longer discovers it; in both cases remove the unused "import
React from 'react'" and ensure at least one real test(...) exists in the final
file.
```

</details>

</blockquote></details>
<details>
<summary>app/global-error.tsx-31-35 (1)</summary><blockquote>

`31-35`: _⚠️ Potential issue_ | _🟡 Minor_

**"Reload page" button navigates to home instead of reloading.**

The button is labeled "Reload page" but `handleReload` sets `window.location.href = '/'`, which navigates away (and discards any URL/query state useful for debugging). If the intent is to truly reload, use `window.location.reload()`. If the intent is to escape to home, rename the button accordingly (e.g., "Go home").

<details>
<summary>🔧 Proposed fix</summary>

```diff
   const handleReload = () => {
     if (typeof window !== 'undefined') {
-      window.location.href = '/';
+      window.location.reload();
     }
   };
```
</details>




Also applies to: 82-84

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
Verify each finding against the current code and only fix it if needed.

In `@app/global-error.tsx` around lines 31 - 35, The "Reload page" button handler
handleReload currently sets window.location.href = '/' which navigates home
instead of reloading; change handleReload to call window.location.reload() so
the button truly reloads the current page (and update any duplicate
implementation at the other occurrence around the same file), or if you intend
to go home instead, rename the button label to "Go home" and keep the navigation
behavior—adjust both handleReload implementations and the corresponding button
text to be consistent.
```

</details>

</blockquote></details>
<details>
<summary>components/ui/api-error-display.tsx-29-46 (1)</summary><blockquote>

`29-46`: _⚠️ Potential issue_ | _🟡 Minor_

**Countdown timer can be inadvertently reset by parent re-renders.**

The effect depends on the whole `error` object. If a parent rebuilds the `UIError` on each render (or callers wrap the same logical error in a new object), this timer restarts mid-countdown and the rate-limit gating is unreliable. Depend on the actual scalar that drives the timer instead:

```diff
-  }, [error]);
+  }, [error.action?.disableSubmitFor]);
```

That way, identical 30s windows don't reset just because the parent rendered.

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
Verify each finding against the current code and only fix it if needed.

In `@components/ui/api-error-display.tsx` around lines 29 - 46, The useEffect for
the countdown is depending on the whole error object which causes mid-countdown
resets when parents recreate the object; change the effect to depend only on the
scalar that drives the timer (error.action?.disableSubmitFor) instead of error,
initialize total via Math.ceil(error.action.disableSubmitFor / 1000) only when
that scalar is present, keep the interval logic and clearInterval cleanup using
the same local interval variable, and ensure setSecondsLeft is set to null when
disableSubmitFor is falsy; adjust the dependency array to
[error.action?.disableSubmitFor] so the timer only restarts when the actual
timeout value changes.
```

</details>

</blockquote></details>
<details>
<summary>app/lending/page.tsx-118-136 (1)</summary><blockquote>

`118-136`: _⚠️ Potential issue_ | _🟡 Minor_

**Failed balance fetch is rendered as `ACBU 0` — misleading for a financial UI.**

When `getLendingBalance` throws, the catch sets `balance` to `null`, and the JSX falls back to `formatAmount(balance ?? 0)`, displaying `ACBU 0`. A user who is signed in but whose balance call failed will appear to have zero — which they may treat as authoritative. Distinguish "unavailable" from "zero":

<details>
<summary>🔧 Proposed fix</summary>

```diff
-            <p className="text-2xl font-bold text-foreground">
-              {balanceLoading
-                ? '—'
-                : apiUser
-                  ? `ACBU ${formatAmount(balance ?? 0)}`
-                  : 'Sign in to view'}
-            </p>
+            <p className="text-2xl font-bold text-foreground">
+              {balanceLoading
+                ? '—'
+                : !apiUser
+                  ? 'Sign in to view'
+                  : balance == null
+                    ? 'Unavailable'
+                    : `ACBU ${formatAmount(balance)}`}
+            </p>
```
</details>




Also applies to: 259-265

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
Verify each finding against the current code and only fix it if needed.

In `@app/lending/page.tsx` around lines 118 - 136, The effect that calls
lendingApi.getLendingBalance sets balance to null on error, and the UI uses
formatAmount(balance ?? 0) which renders "ACBU 0" for failures; change the catch
behavior and rendering so failed/unknown balances are distinguished from zero:
in the useEffect (the getLendingBalance promise handlers where setBalance and
setBalanceLoading are called) set a sentinel for "unavailable" (e.g., undefined
or a specific state) instead of treating errors as zero, and update the JSX that
uses formatAmount(balance ?? 0) to branch — only call formatAmount when balance
is a real number and otherwise render a placeholder like "—" or "Unavailable";
apply the same change to the other occurrence around the commented 259-265 block
that uses formatAmount with balance.
```

</details>

</blockquote></details>
<details>
<summary>app/send/page.tsx-84-84 (1)</summary><blockquote>

`84-84`: _⚠️ Potential issue_ | _🟡 Minor_

**Two small follow-ups: leftover `useToast` and inconsistent label.**

1. Line 84 still destructures `toast` from `useToast()`, but the AI summary states the file moved away from toast notifications to `ApiErrorDisplay`. If `toast` is no longer called anywhere in the file, drop the hook to avoid an unused-variable warning and unnecessary subscription.
2. Line 554 displays `Send ACBU ${amount}` on the confirm action button. Since the rest of the confirm dialog uses `confirmedAmount` (line 539) — which is a stable snapshot — the button should match, otherwise editing the input while the dialog is open creates a label/value mismatch (the dialog shows X but submits Y).

<details>
<summary>♻️ Suggested fix for the label</summary>

```diff
-            <AlertDialogAction onClick={handleConfirmTransfer} className="flex-1 bg-primary text-primary-foreground hover:bg-primary/90" disabled={sending || isSubmitDisabled}>{sending ? 'Sending...' : `Send ACBU ${amount}`}</AlertDialogAction>
+            <AlertDialogAction onClick={handleConfirmTransfer} className="flex-1 bg-primary text-primary-foreground hover:bg-primary/90" disabled={sending || isSubmitDisabled}>{sending ? 'Sending...' : `Send ACBU ${confirmedAmount}`}</AlertDialogAction>
```

</details>




Also applies to: 554-554

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
Verify each finding against the current code and only fix it if needed.

In `@app/send/page.tsx` at line 84, Remove the unused hook by deleting the
useToast() destructure (remove "const { toast } = useToast();" and any remaining
references to toast in this file) to avoid unused-variable/subscription
warnings, and update the confirm action button label to use the stable snapshot
variable confirmedAmount instead of amount (ensure the confirm dialog’s submit
button text displays `confirmedAmount` so the dialog label/value matches what
will be submitted).
```

</details>

</blockquote></details>
<details>
<summary>SECURITY_FIX_SUMMARY.md-76-84 (1)</summary><blockquote>

`76-84`: _⚠️ Potential issue_ | _🟡 Minor_

**Replace unsupported backend claim with verifiable proof.**

Line 84 claims the backend "already supports" httpOnly, secure, and sameSite=strict cookies "based on code comments," but this is not verifiable. The frontend code correctly expects these flags (e.g., `credentials: 'include'` in API calls), but without explicit backend verification (e.g., a production-tested PR/commit showing the cookie configuration), this claim is on faith alone. If the backend deploys with `secure=false` in preview environments or `sameSite=lax` to allow OAuth redirects, the login flow silently breaks.

Add a direct link to the backend PR/commit implementing the cookie contract, and include an explicit integration test (login → assert `Set-Cookie` headers contain `httpOnly`, `secure`, `sameSite=strict`) before merge.

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
Verify each finding against the current code and only fix it if needed.

In `@SECURITY_FIX_SUMMARY.md` around lines 76 - 84, Replace the unverifiable claim
that the backend "already supports" httpOnly/secure/sameSite=strict cookies with
a concrete link to the backend PR/commit that implemented the cookie contract
and add an integration test that performs a real login and asserts the
Set-Cookie header includes httpOnly, Secure, and SameSite=Strict; specifically,
update SECURITY_FIX_SUMMARY to cite the exact backend change (the auth login
handler / POST /api/auth/login or auth.login implementation that sets the
cookie) and add a test that sends credentials to the login endpoint, captures
the Set-Cookie header, and asserts presence of "HttpOnly", "Secure", and
"SameSite=Strict" (and fail the CI if any flag is missing).
```

</details>

</blockquote></details>

</blockquote></details>

---

<details>
<summary>ℹ️ Review info</summary>

<details>
<summary>⚙️ Run configuration</summary>

**Configuration used**: defaults

**Review profile**: CHILL

**Plan**: Pro

**Run ID**: `a9781804-04cb-4b7a-b338-0aa340cec470`

</details>

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between b791eee54522cd4aabe61a53d04eb538bf84711d and ceb5c01b171da85471845ffe0da51901c42dcaf0.

</details>

<details>
<summary>⛔ Files ignored due to path filters (1)</summary>

* `pnpm-lock.yaml` is excluded by `!**/pnpm-lock.yaml`

</details>

<details>
<summary>📒 Files selected for processing (61)</summary>

* `.claude/settings.local.json`
* `.vscode/settings.json`
* `DEPLOYMENT_CHECKLIST.md`
* `HELP_PAGE_SUMMARY.md`
* `SECURITY_FIXES_PART2.md`
* `SECURITY_FIX_SUMMARY.md`
* `app/api/csp-report/route.ts`
* `app/auth/2fa/page.tsx`
* `app/auth/error.tsx`
* `app/auth/signin/page.tsx`
* `app/auth/signup/page.tsx`
* `app/auth/wallet-setup/page.tsx`
* `app/bills/error.tsx`
* `app/burn/page.tsx`
* `app/business/page.tsx`
* `app/business/sme/page.tsx`
* `app/currency/page.tsx`
* `app/error.tsx`
* `app/fiat/page.tsx`
* `app/global-error.tsx`
* `app/help/page.tsx`
* `app/layout.tsx`
* `app/lending/admin/page.tsx`
* `app/lending/page.tsx`
* `app/me/error.tsx`
* `app/me/page.tsx`
* `app/me/settings/security/page.tsx`
* `app/mint/page.tsx`
* `app/page.tsx`
* `app/rates/page.tsx`
* `app/reserves/page.tsx`
* `app/savings/deposit/page.tsx`
* `app/savings/error.tsx`
* `app/savings/page.tsx`
* `app/savings/withdraw/page.tsx`
* `app/send/error.tsx`
* `app/send/page.test.tsx`
* `app/send/page.tsx`
* `app/transactions/error.tsx`
* `app/wallet/error.tsx`
* `app/wallet/page.tsx`
* `components/error-boundary.tsx`
* `components/error-test-trigger.tsx`
* `components/global-error-handler.tsx`
* `components/mobile-nav.tsx`
* `components/navigation/BackButton.tsx`
* `components/ui/api-error-display.tsx`
* `components/wallet-setup-modal.tsx`
* `contexts/auth-context.tsx`
* `hooks/use-api-error.ts`
* `lib/api/client.ts`
* `lib/api/user.ts`
* `lib/error-reporting.ts`
* `lib/features.ts`
* `lib/lending-store.ts`
* `lib/passcode-manager.ts`
* `lib/wallet-storage.ts`
* `middleware.ts`
* `next.config.mjs`
* `package.json`
* `pr.md`

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->

Comment thread app/me/page.tsx
import { Badge } from '@/components/ui/badge';
import { useRouter } from 'next/navigation';
import { ArrowRight, User, Settings, LogOut, Eye, Clock, Building2, Shield } from 'lucide-react';
import { ArrowRight, User, Settings, LogOut, Eye, Clock, Building2, Shield, HelpCircle } from 'lucide-react';
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Missing icon imports — KycBadge will crash at build/render.

getKycBadgeConfig references CheckCircle2 (Line 40), Clock3 (Line 49), XCircle (Line 57), and AlertCircle (Line 64), but none of these are in the lucide-react import on Line 8. This will fail TypeScript compilation (or throw ReferenceError at runtime if it slips past). The library summary explicitly mentions these icons should be imported here.

🐛 Proposed fix
-import { ArrowRight, User, Settings, LogOut, Eye, Clock, Building2, Shield, HelpCircle } from 'lucide-react';
+import {
+  ArrowRight,
+  User,
+  Settings,
+  LogOut,
+  Eye,
+  Clock,
+  Building2,
+  Shield,
+  HelpCircle,
+  CheckCircle2,
+  Clock3,
+  XCircle,
+  AlertCircle,
+} from 'lucide-react';
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import { ArrowRight, User, Settings, LogOut, Eye, Clock, Building2, Shield, HelpCircle } from 'lucide-react';
import {
ArrowRight,
User,
Settings,
LogOut,
Eye,
Clock,
Building2,
Shield,
HelpCircle,
CheckCircle2,
Clock3,
XCircle,
AlertCircle,
} from 'lucide-react';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/me/page.tsx` at line 8, The import list at the top is missing icons used
by getKycBadgeConfig (CheckCircle2, Clock3, XCircle, AlertCircle), causing
build/runtime failures; update the lucide-react import that currently lists
ArrowRight, User, Settings, LogOut, Eye, Clock, Building2, Shield, HelpCircle to
also include CheckCircle2, Clock3, XCircle and AlertCircle so the symbols
referenced in getKycBadgeConfig resolve correctly and TypeScript compiles.

Comment thread app/mint/page.tsx
Comment thread app/mint/page.tsx
Comment on lines +684 to 689
rateRows.map((r) => (
<Card key={r.currency} className="border-border p-4">
<div className="flex justify-between">
<p className="font-semibold text-foreground">ACBU/{r.currency ?? 'Rate'}</p>
<p className="text-lg font-bold text-primary">{r.rate != null ? String(r.rate) : '—'}</p>
<p className="font-semibold text-foreground">ACBU/{r.currency}</p>
<p className="text-lg font-bold text-primary">{formatRate(r.rate)}</p>
</div>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify formatRate is imported into app/mint/page.tsx.
rg -nP '\bformatRate\b' app/mint/page.tsx
rg -nP '^\s*export\s+(?:const|function)\s+formatRate\b' lib/

Repository: Pi-Defi-world/acbu-frontend

Length of output: 168


🏁 Script executed:

#!/bin/bash
# Search for import statements containing formatRate
echo "=== Searching for formatRate imports in app/mint/page.tsx ==="
rg -nP 'import.*formatRate' app/mint/page.tsx

# Search for any formatRate definition in the entire codebase
echo -e "\n=== Searching for formatRate definition in the codebase ==="
rg -nP '(?:export\s+)?(?:const|function|async\s+function)\s+formatRate\b' --type ts --type tsx

# Check the import statement around line 33 to see what's actually imported from `@/lib/utils`
echo -e "\n=== Checking imports around line 33 in app/mint/page.tsx ==="
sed -n '30,40p' app/mint/page.tsx

Repository: Pi-Defi-world/acbu-frontend

Length of output: 712


🏁 Script executed:

#!/bin/bash
# Get file size to see if we can review the entire file
echo "=== File info ==="
wc -l app/mint/page.tsx

# Broader search for formatRate without file type restrictions
echo -e "\n=== Searching for formatRate definition anywhere in repo ==="
rg -nP 'formatRate' --type-list | head -5  # See what types are available

# Try with broader approach
echo -e "\n=== Search without type filter ==="
rg -nP '\bformatRate\b' | grep -v 'node_modules'

# Check if it might be defined in the same file
echo -e "\n=== Checking first 100 lines of app/mint/page.tsx for any formatRate definition or local function ==="
head -100 app/mint/page.tsx | grep -n formatRate

Repository: Pi-Defi-world/acbu-frontend

Length of output: 991


Import formatRate from app/rates/page.tsx.

formatRate is used on line 688 but never imported or defined in this file. formatRate is defined in app/rates/page.tsx and must be imported to avoid a ReferenceError at runtime.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/mint/page.tsx` around lines 684 - 689, The render uses formatRate inside
the rateRows.map in app/mint/page.tsx but formatRate is not defined here; import
the helper from app/rates/page.tsx (where formatRate is declared) and add an
import for formatRate at the top of app/mint/page.tsx so the JSX line using
formatRate(r.rate) resolves correctly.

Comment thread app/savings/deposit/page.tsx
Comment thread app/send/page.tsx
Comment thread middleware.ts Outdated
@Junman140
Copy link
Copy Markdown
Member

@DavisVT resolve conflicts

@Junman140
Copy link
Copy Markdown
Member

@DavisVT RESOLVE

1 similar comment
@Junman140
Copy link
Copy Markdown
Member

@DavisVT RESOLVE

@DavisVT
Copy link
Copy Markdown
Contributor Author

DavisVT commented Apr 28, 2026

@Junman140 Done

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 17

Note

Due to the large number of review comments, Critical severity comments were prioritized as inline comments.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (8)
app/send/page.tsx (3)

3-52: ⚠️ Potential issue | 🔴 Critical

Add the missing imports for useToast and ApiErrorDisplay.

Line 83 calls useToast() and line 530 renders ApiErrorDisplay, but neither symbol is imported in this file. That leaves the page in a compile-failing state even after the parser issue below is fixed.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/send/page.tsx` around lines 3 - 52, The file is missing imports for
useToast and ApiErrorDisplay which causes compile errors; add the appropriate
imports at the top of the module: import useToast from the toast hook (or the
named export used elsewhere in the repo) and import ApiErrorDisplay from its
UI/error component, then use those symbols (useToast used where called on line
~83 and ApiErrorDisplay where rendered around line ~530) so the references
resolve; ensure the import paths match your project's existing convention for
toast and error-display components.

161-251: ⚠️ Potential issue | 🔴 Critical

Wire this handler to useApiError instead of the removed submit-error helpers.

Line 164 still calls clearSubmitError() and line 247 still calls handleSubmitError(e), but the file now exposes clearError / setApiError from useApiError. As written, this path won't compile.

Suggested fix
-    clearSubmitError();
+    clearError();-      handleSubmitError(e);
+      setApiError(e);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/send/page.tsx` around lines 161 - 251, The handler handleConfirmTransfer
still calls removed helpers clearSubmitError() and handleSubmitError(e); replace
clearSubmitError() with clearError() and replace handleSubmitError(e) with
setApiError(e) (imported from useApiError), ensure useApiError's clearError and
setApiError are used in this component and included in the handleConfirmTransfer
dependency array, and remove any leftover imports/usages of the old submit-error
helpers.

161-251: ⚠️ Potential issue | 🔴 Critical

Include confirmedAmount in the submit callback's source of truth.

This callback submits confirmedAmount, but it is memoized without confirmedAmount in the dependency list. After clicking Continue, the dialog shows the new snapshot while handleConfirmTransfer can still close over the previous value and create the transfer with "" or an older amount. The validation guard should also use the snapshot, not the live input.

Suggested fix
-    if (!amount || parseFloat(amount) <= 0 || !to) return;
+    if (!confirmedAmount || parseFloat(confirmedAmount) <= 0 || !to) return;-  }, [amount, getToValue, note, userId, stellarAddress, kit, opts, loadTransfers, refreshBalance]);
+  }, [confirmedAmount, getToValue, note, userId, stellarAddress, kit, opts, loadTransfers, refreshBalance]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/send/page.tsx` around lines 161 - 251, handleConfirmTransfer is closing
over confirmedAmount and validating against the live amount, causing stale
submissions; update the validation to use the snapshot confirmedAmount (e.g.
change the guard to if (!confirmedAmount || parseFloat(confirmedAmount) <= 0 ||
!to) return) and add confirmedAmount to the useCallback dependency array so
handleConfirmTransfer is re-memoized when the confirmed value changes; keep
references to getToValue, userId, stellarAddress, kit, opts, loadTransfers,
refreshBalance as-is.
lib/api/client.ts (1)

56-58: ⚠️ Potential issue | 🔴 Critical

Finish removing the bearer-token path.

RequestOptions no longer has a token property, but the request() function at line 84 still reads opts.token and references an undefined currentToken variable, and apiOpts() at lines 175–176 still attempts to return a token field. This causes TypeScript compilation to fail: RequestOptions does not define token, making the type mismatch at apiOpts() a contract violation and the opts.token access at line 84 a property-not-found error.

Complete the cookie-only auth migration by removing all token-handling code and updating the interface contract.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/api/client.ts` around lines 56 - 58, RequestOptions was stripped of token
but code still reads opts.token and references currentToken; remove all
token-handling: delete any access to opts.token and the currentToken variable
usage in the request() function, and update apiOpts() to stop returning a token
field (only return headers/cookie or whatever remains), ensuring RequestOptions
and apiOpts() signatures match; search for and remove or refactor any
token-related logic in request(), apiOpts(), and any helpers so compilation no
longer references the undefined token/currentToken symbols.
app/currency/page.tsx (1)

130-198: ⚠️ Potential issue | 🟠 Major

Finish the migration to useApiError() in the execute path.

The UI now clears/displays uiError, but the catch block still writes to setSubmitError(...). That means submission failures won't show up in ApiErrorDisplay, and if the old submit-error state was removed this path no longer compiles.

Suggested fix
-    } catch (e) {
-      logger.error(`Currency operation failed: ${activeTab}`, e); // <-- ADD LOGGER
-      setSubmitError(e instanceof Error ? e.message : "Operation failed");
+    } catch (e) {
+      logger.error(`Currency operation failed: ${activeTab}`, e);
+      setApiError(e);
     } finally {
       setSubmitting(false);
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/currency/page.tsx` around lines 130 - 198, The catch in handleExecute
still calls setSubmitError(...) — replace that with the useApiError hook's
setter (e.g., setUiError or setApiError) so the ApiErrorDisplay sees the error;
update the catch to call setUiError(e instanceof Error ? e.message : "Operation
failed") (and keep logger.error(`Currency operation failed: ${activeTab}`, e)
and clearError/clearApiError usage intact), and ensure the setter from
useApiError (setUiError/setApiError) is imported/declared in the component scope
where handleExecute is defined.
app/mint/page.tsx (1)

242-317: ⚠️ Potential issue | 🔴 Critical

Remove the duplicate handleExecuteBurn declaration.

The file contains two const handleExecuteBurn = async () => { ... } declarations (lines 242 and 318) in the same scope, which creates a duplicate identifier error.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/mint/page.tsx` around lines 242 - 317, There are two duplicate
declarations of the async handler const handleExecuteBurn in the same scope;
remove the redundant one and keep the correct implementation (ensure you
preserve the version that wires up getWalletSecretAnyLocal, Kit fallback flow,
submitBurnRedeemSingleClient, fiatApi.postOffRamp, setTxId and
setStep("success")). After deleting the duplicate, update any references to
handleExecuteBurn if needed (e.g., button onClick or useEffect) so they point to
the remaining handleExecuteBurn and run the app/build to confirm the duplicate
identifier error is resolved.
app/burn/page.tsx (2)

109-187: ⚠️ Potential issue | 🔴 Critical

Replace handleSubmit handler with proper form submission pattern using react-hook-form.

The code references values.accountNumber, values.bankCode, values.accountName, values.acbuAmount, and values.currency (lines 121–123, 139–140, 172–173, 180–181, 187), but values is never defined. The function should be integrated with form.handleSubmit() to receive validated form values:

const handleSubmit = async (values: BurnFormValues) => {
  // Remove the e.preventDefault() and signature—react-hook-form handles this
  if (!isValid) return;
  // ... rest of the handler
};

Then bind it in the form: <form onSubmit={form.handleSubmit(handleSubmit)}> (currently line 233 references undefined formHandleSubmit and onSubmit).

Also fix: undefined uiError (should be error, line 214), undefined isSubmitDisabled (line 366), missing CheckCircle and ApiErrorDisplay imports (lines 215, 225), and duplicate useSearchParams import (lines 5, 11).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/burn/page.tsx` around lines 109 - 187, The submit handler currently uses
an undefined "values" and DOM event; change handleSubmit to accept validated
values from react-hook-form (signature: const handleSubmit = async (values:
BurnFormValues) => { ... }) remove e.preventDefault and the React.FormEvent
parameter, and replace all uses of values.* in handleSubmit with that parameter;
bind the form element to form.handleSubmit(handleSubmit). Also rename any
uiError references to error (or the actual error state variable), replace the
undefined isSubmitDisabled usage with a computed boolean (e.g., !isValid ||
loading), add missing imports for CheckCircle and ApiErrorDisplay, and remove
the duplicate useSearchParams import so identifiers (handleSubmit,
form.handleSubmit, error, isSubmitDisabled, CheckCircle, ApiErrorDisplay)
resolve.

232-371: ⚠️ Potential issue | 🔴 Critical

Replace formHandleSubmit(onSubmit) with handleSubmit and add missing closing </Form> tag.

The code has three syntax errors:

  1. formHandleSubmit and onSubmit are undefined. The function handleSubmit is defined in the component but never used. Change line 233 from <form onSubmit={formHandleSubmit(onSubmit)}> to <form onSubmit={handleSubmit}>.

  2. The <Form> wrapper opened on line 232 is never closed. Add </Form> after </form> on line 368.

  3. The </div> on line 361 closes a div with no corresponding opening tag in this block. Either remove this closing tag or add a matching <div> opening tag if a wrapper is intended around the form fields.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/burn/page.tsx` around lines 232 - 371, The form markup has three issues:
replace the undefined formHandleSubmit/onSubmit usage with the existing
handleSubmit by changing the form's onSubmit to use handleSubmit; close the Form
component by adding a closing </Form> tag after the inner </form>; and remove
the stray </div> (or add the corresponding opening <div> wrapper if intended) so
that tags balance—look for the Form wrapper, the inner <form
onSubmit={handleSubmit}> and the stray </div> near the end of the block when
making the fixes.
♻️ Duplicate comments (4)
app/send/page.tsx (2)

499-505: ⚠️ Potential issue | 🔴 Critical

Use isValid here instead of the removed isFormValid().

This still calls an undefined function, so the dialog will throw as soon as React evaluates disabled. The memoized boolean at lines 256-261 is the value this button should be using.

Suggested fix
-                disabled={!isFormValid()}
+                disabled={!isValid}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/send/page.tsx` around lines 499 - 505, The Button currently references a
removed function isFormValid(), causing an undefined call; update the disabled
prop to use the memoized boolean isValid instead (i.e., replace
disabled={!isFormValid()} with disabled={!isValid}) so the Button (component
using onClick that calls setConfirmedAmount and setShowConfirmDialog) uses the
existing isValid state; ensure you only change the disabled expression and leave
the onClick handlers (setConfirmedAmount, setShowConfirmDialog) and className
intact.

103-148: ⚠️ Potential issue | 🔴 Critical

Resolve the broken merge/conflict artifact in the loader section.

The stray dev tokens and duplicated loadContacts branches leave this useCallback block unbalanced, which is why Biome now sees the return at line 315 as being outside the component. This needs to be cleaned up before the file will build.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/send/page.tsx` around lines 103 - 148, Remove the stray "dev" tokens and
the duplicated/conflicting loadContacts blocks so the useCallback is a single
well-formed function; specifically, keep one loadContacts useCallback that (1)
clears errors via setContactsError(""), (2) awaits userApi.getContacts(opts) and
calls setContacts(data.contacts ?? []), (3) catches errors and sets
setContactsError(message) and calls toast({...}) with the same message, and (4)
calls setLoadingContacts(false) in finally; also ensure there is no duplicated
closing braces or parentheses left over from the merge and that related symbols
(loadContacts, useCallback, userApi.getContacts, setContacts, setContactsError,
setLoadingContacts, toast) are referenced consistently with loadTransfers.
app/savings/deposit/page.tsx (1)

25-27: ⚠️ Potential issue | 🔴 Critical

This resolution flow is missing its backing state/imports.

setResolving, setError, resolveUserUri, resolving, and AlertCircle are all referenced here without a declaration/import in this file, so the page will not build. Finish wiring the new flow end-to-end or revert these references to the previous implementation.

Also applies to: 38-39, 84-85, 102-108

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/savings/deposit/page.tsx` around lines 25 - 27, The page references
missing state and imports: declare resolving and error state via React useState
(e.g., const [resolving, setResolving] = useState(false); const [error,
setError] = useState(""); import React/useState if needed), import or implement
resolveUserUri (or import its module) and import the AlertCircle component from
your icon library (e.g., lucide-react) so setResolving, setError,
resolveUserUri, resolving, and AlertCircle are defined; alternatively revert the
new flow to the previous implementation by removing these references. Ensure the
cancellation flag (cancelled) is handled consistently with the resolved flow and
update any useEffect cleanup where used.
app/mint/page.tsx (1)

662-666: ⚠️ Potential issue | 🔴 Critical

formatRate is still undefined in this module.

The Rates tab still renders formatRate(r.rate), but this file does not import or define that helper.

Recon script
#!/bin/bash
rg -nP '\bformatRate\b' app/mint/page.tsx
echo
rg -nP '^import .*formatRate|function\s+formatRate\b|const\s+formatRate\s*=' app/mint/page.tsx
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/mint/page.tsx` around lines 662 - 666, The Rates tab uses
formatRate(r.rate) but formatRate is not defined or imported in this module; fix
it by either importing the helper (e.g., add an import for formatRate from its
utility module) or add a local formatRate function in app/mint/page.tsx and use
it where rateRows.map calls formatRate; locate the usage in the JSX
(rateRows.map((r) => ... formatRate(r.rate))) and ensure the symbol is exported
from the chosen module or declared before the component so the reference
resolves.
🟠 Major comments (10)
app/send/page.tsx-117-136 (1)

117-136: ⚠️ Potential issue | 🟠 Major

Don't share one loadError across both async loaders.

loadTransfers() and loadContacts() both clear and overwrite the same error state while running concurrently on mount. A contacts success can therefore clear a transfers failure (and vice versa), so the banner can disappear even though one section never loaded. Keep separate error states or derive a combined banner from both results.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/send/page.tsx` around lines 117 - 136, loadTransfers and loadContacts
share the same loadError state (setLoadError) so one loader can clear the
other's error; introduce separate error state variables (e.g., transfersError
and contactsError with setTransfersError and setContactsError) and update
loadTransfers to setTransfersError (and not touch contactsError) and update
loadContacts to setContactsError (and not touch transfersError), keep
setLoadingTransfers and setTransfers/setContacts behavior unchanged, and render
any combined banner by deriving it from both transfersError and contactsError
instead of a single shared loadError.
components/error-boundary.tsx-17-17 (1)

17-17: ⚠️ Potential issue | 🟠 Major

Wire the new onError API through componentDidCatch.

The onError callback and errorInfo state property were added to the public API but are never used. componentDidCatch only logs the error; it does not store errorInfo to state or invoke the callback, making the new API non-functional.

Suggested fix
   componentDidCatch(error: Error, errorInfo: React.ErrorInfo): void {
+    this.setState({ errorInfo });
     logger.error('ErrorBoundary caught an error:', { error, errorInfo });
+    this.props.onError?.(error, errorInfo);
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/error-boundary.tsx` at line 17, componentDidCatch currently only
logs errors and doesn't wire the new onError API or persist errorInfo; update
the ErrorBoundary.componentDidCatch method to call this.setState({ error,
errorInfo }) to store the React.ErrorInfo in state (referencing the state field
errorInfo) and invoke this.props.onError?.(error, errorInfo) so consumers
receive the callback; ensure you reference the onError prop and the errorInfo
state property names exactly as declared.
app/savings/withdraw/page.tsx-88-90 (1)

88-90: ⚠️ Potential issue | 🟠 Major

The new confirmation checkbox is not enforced.

handleSubmit and the submit disabled condition ignore confirmDifferentRecipient, recipientValidationLoading, and recipientValidationError. Once the editor is wired up, a form submit can still proceed with an unconfirmed or unresolved alternate recipient. Gate the handler itself as well as the button state.

Also applies to: 214-230

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/savings/withdraw/page.tsx` around lines 88 - 90, The form currently
allows submission even when the new recipient confirmation or validation are
unresolved; update the submit gating in handleSubmit to also require
confirmDifferentRecipient === true and that recipientValidationLoading is false
and recipientValidationError is falsy, and mirror those same checks in the
submit button's disabled condition so the button is disabled while
recipientValidationLoading is true, recipientValidationError exists, or
confirmDifferentRecipient is false (in addition to the existing user/amount
checks); reference the handleSubmit function, the confirmDifferentRecipient
flag, recipientValidationLoading, recipientValidationError, and the submit
button disabled logic to locate and apply these changes.
app/savings/page.tsx-33-51 (1)

33-51: ⚠️ Potential issue | 🟠 Major

The new resolver is dead code, so this page still uses the raw identifier.

resolveUserUri() is added but never called, which is why ESLint flags it on Line 39. More importantly, setApiUser(uri) keeps feeding getSavingsPositions() the unresolved alias/pay identifier instead of the canonical value the helper was added to produce.

Suggested fix
   useEffect(() => {
     setReceiveError("");
     userApi.getReceive(opts).then(async (data) => {
       const uri = (data.pay_uri ?? data.alias) as string | undefined;
-      if (uri && typeof uri === "string") setApiUser(uri);
+      if (uri && typeof uri === "string") {
+        const resolved = await resolveUserUri(uri, opts);
+        setApiUser(resolved);
+      }
       setReceiveError("");
     }).catch((e) => {
       logger.error("Failed to load user info", e);
       setReceiveError(e instanceof Error ? e.message : "Failed to load user info");
     });

Also applies to: 107-109

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/savings/page.tsx` around lines 33 - 51, resolveUserUri is defined but
never used so setApiUser/getSavingsPositions keep receiving raw identifiers;
update the callers to await resolveUserUri(rawIdentifier, opts) and pass the
returned canonical URI into setApiUser (and into the subsequent
getSavingsPositions invocation) instead of the raw value; specifically locate
usages that feed setApiUser and the call sites around the block referenced
(including the other occurrence around lines 107-109) and replace feeding raw
with the awaited resolveUserUri(...) result, preserving the same opts parameter
shape passed to resolveRecipient.
app/savings/withdraw/page.tsx-153-179 (1)

153-179: ⚠️ Potential issue | 🟠 Major

Change never makes the recipient editable.

The field is still readOnly and bound to user, so recipient, handleRecipientChange, validateRecipient, and the new confirmation flow never participate. As implemented, withdrawing to a different recipient is impossible.

Suggested fix
-                            <Input
-                                id="withdraw-account"
-                                value={resolving ? "Resolving…" : user}
-                                readOnly
-                                className="border-border font-mono text-sm bg-muted"
-                            />
+                            <Input
+                                id="withdraw-recipient"
+                                value={resolving ? "Resolving…" : recipient}
+                                readOnly={!editingRecipient}
+                                onChange={(e) => handleRecipientChange(e.target.value)}
+                                onBlur={() => {
+                                    const value = recipient.trim();
+                                    if (editingRecipient && value) void validateRecipient(value);
+                                }}
+                                className="border-border font-mono text-sm bg-muted"
+                            />
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/savings/withdraw/page.tsx` around lines 153 - 179, The "Change" button
toggles editingRecipient but the Input (id="withdraw-account") is always
readOnly and bound to user, so recipient, handleRecipientChange,
validateRecipient and the confirmation flow are never used; update the Input to
use value={editingRecipient ? recipient : (resolving ? "Resolving…" : user)},
remove/readOnly only when editingRecipient is true (i.e., set
readOnly={!editingRecipient}), and wire its onChange to handleRecipientChange
(so validateRecipient can run) and ensure handleToggleEdit resets recipient to
user when cancelling (or preserves when switching to edit) so the confirmation
flow can operate on the edited recipient.
app/wallet/page.tsx-70-74 (1)

70-74: ⚠️ Potential issue | 🟠 Major

Persist the new seed only after the backend accepts the wallet change.

Both flows overwrite local wallet material before putWalletAddress() / postWalletConfirm() succeeds. If the backend update fails, the device now holds a secret for a different account than the backend still references. app/send/page.tsx:174-183 explicitly rejects that mismatch, so a partial failure can strand the user until they re-import the old seed.

Suggested fix
-      // Store encrypted with passcode (secure)
-      await storeWalletSecret(userId, passphrase, passcode);
-
       // Sync public key to backend.
       const result = await userApi.putWalletAddress(newAddress, opts);
       if (!result?.ok) {
         throw new Error("Backend did not accept the new wallet address. Please retry.");
       }
@@
       try {
         await userApi.postWalletConfirm({ wallet_address: newAddress }, opts);
       } catch (err) {
         console.warn("Wallet confirm failed, but wallet address was set. User can continue.", err);
       }
+
+      await storeWalletSecret(userId, passphrase, passcode);

Also applies to: 119-123

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/wallet/page.tsx` around lines 70 - 74, The code currently calls
storeWalletSecret(userId, passphrase, passcode) before contacting the backend
(userApi.putWalletAddress and the similar postWalletConfirm flow), which can
leave local secrets inconsistent if the backend call fails; change the flow so
you only call storeWalletSecret (and any local overwrite of the seed/secret)
after the backend call returns a successful response (check result.ok / HTTP 2xx
or equivalent) for userApi.putWalletAddress(newAddress, opts) and the
postWalletConfirm path, and if the backend call fails surface the error to the
caller and do not persist or overwrite local wallet material (or implement an
explicit rollback if needed).
app/wallet/page.tsx-299-303 (1)

299-303: ⚠️ Potential issue | 🟠 Major

Don’t promise “stored securely” with the current implementation.

These banners say the seed is securely encrypted on-device, but lib/wallet-storage.ts:26-29 still “encrypts” with btoa(\${passcode}:${secret}`)`, which is trivially reversible. That copy overpromises the protection level for a wallet seed.

Also applies to: 324-328

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/wallet/page.tsx` around lines 299 - 303, The banner overpromises security
because lib/wallet-storage.ts currently uses btoa(`${passcode}:${secret}`) to
“encrypt” the seed; replace this with real crypto: in the functions that
save/load the secret in lib/wallet-storage.ts (the code path using
btoa(`${passcode}:${secret}`)) derive an encryption key from the passcode (e.g.,
PBKDF2 with a per-item random salt and sufficient iterations), encrypt the
secret with a modern algorithm (AES-GCM) using a random IV, and persist the
ciphertext together with the salt and IV; update the corresponding load/decrypt
function to reverse this (derive key from stored salt and passcode, then
decrypt) and add robust error handling for failed decryption, and if you keep
the UI text, change “stored securely” to a less absolute phrase unless you can
guarantee stronger protections.
lib/api/client.ts-95-98 (1)

95-98: ⚠️ Potential issue | 🟠 Major

Handle already-aborted caller signals before fetch().

When opts.signal.aborted is already true, the addEventListener('abort', ...) call does not register the listener (per AbortSignal spec), so the internal controller never gets aborted and the request proceeds to the backend. Callers expect cancellation to fail fast without hitting the backend.

Suggested fix
   // If caller provides signal, abort our controller when caller's aborts
   if (opts.signal) {
-    opts.signal.addEventListener('abort', () => controller.abort(), { once: true });
+    if (opts.signal.aborted) {
+      controller.abort(opts.signal.reason);
+    } else {
+      opts.signal.addEventListener('abort', () => controller.abort(opts.signal.reason), { once: true });
+    }
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/api/client.ts` around lines 95 - 98, The current logic adds an 'abort'
listener to opts.signal but doesn't handle the case where opts.signal.aborted is
already true, so the internal AbortController (controller) never gets aborted
and the request proceeds; update the code around the opts.signal handling in
lib/api/client.ts to first check if opts.signal.aborted is true and if so call
controller.abort() immediately, otherwise register the
opts.signal.addEventListener('abort', () => controller.abort(), { once: true });
so that the internal controller is always aborted when the caller's signal is
already aborted or aborts later (refer to opts.signal and controller in the
surrounding function).
app/burn/page.tsx-364-367 (1)

364-367: ⚠️ Potential issue | 🟠 Major

Drive the submit button from RHF state, not the removed local state.

isValid is still computed from the old useState fields, so after this migration it no longer tracks what the user typed into the form.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/burn/page.tsx` around lines 364 - 367, The submit button is still using
the removed local isValid state; update the form to derive validity from React
Hook Form instead: ensure your useForm call includes a mode that validates on
change (e.g., useForm({... , mode: "onChange"})) and destructure the RHF
formState (const { formState: { isValid } } = useForm(...)) or read
form.getState().isValid, remove the old local isValid state and any setIsValid
handlers, and change the Button disabled prop to disabled={!isValid || loading
|| isSubmitDisabled} so the button is driven by RHF's isValid; keep loading and
isSubmitDisabled as-is.
app/burn/page.tsx-86-96 (1)

86-96: ⚠️ Potential issue | 🟠 Major

Keep the deep-link defaults in the form state.

The page still reads amount and currency from the URL, but the new form ignores them and resets both fields to empty/NGN. That breaks the /mint -> /burn prefill flow.

Suggested fix
   const form = useForm<BurnFormValues>({
     resolver: zodResolver(burnSchema),
     defaultValues: {
-      acbuAmount: "",
-      currency: "NGN",
+      acbuAmount: searchParams.get("amount") || "",
+      currency: (searchParams.get("currency") || "NGN").toUpperCase(),
       accountNumber: "",
       bankCode: "",
       accountName: "",
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/burn/page.tsx` around lines 86 - 96, The form's defaultValues are
overwriting deep-link params; update the useForm initialization
(useForm<BurnFormValues> with resolver zodResolver(burnSchema)) so defaultValues
for acbuAmount and currency are derived from the existing URL/deep-link values
(e.g., the amount and currency you already read elsewhere on the page) instead
of hardcoding "" and "NGN"; preserve the current fallbacks (empty string and
"NGN") if the URL values are absent and ensure types match BurnFormValues.
🟡 Minor comments (1)
app/layout.tsx-9-9 (1)

9-9: ⚠️ Potential issue | 🟡 Minor

Remove the stale AuthGuard import while the wrapper stays commented out.

ESLint is already flagging this import as unused in the current state. Either drop the import now or restore the wrapper in the same change.

Also applies to: 86-88

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/layout.tsx` at line 9, The AuthGuard import is stale while its wrapper is
commented out; remove the unused import statement "AuthGuard" (and any duplicate
unused imports around the other occurrences mentioned at lines 86-88) from the
module, or alternatively restore the commented wrapper that uses AuthGuard so
the import is actually used—choose one approach and make the code and imports
consistent (update imports/usages for the AuthGuard symbol accordingly).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/auth/2fa/page.tsx`:
- Around line 72-73: The call to login is passing the API key as the first
argument (login(result.api_key!, ...)) which no longer matches the new signature
login(userId, stellarAddress?), causing the API key to be stored as userId;
update the call in the authApi.verify2fa result handling to call
login(result.user_id, result.stellar_address) (omitting result.api_key) so
userId and optional stellarAddress map correctly to the new login(userId,
stellarAddress?) signature.

In `@app/burn/page.tsx`:
- Around line 214-230: The JSX uses undeclared symbols: replace uiError with the
error returned from useApiError (the hook referenced as useApiError/ error in
this file), and add the missing imports for CheckCircle (import from
'lucide-react', similar to the existing ArrowLeft import) and ApiErrorDisplay
(import the component where it’s defined); ensure the top-of-file imports
include CheckCircle and ApiErrorDisplay and that the conditional rendering uses
error (not uiError) and the existing txId logic remains unchanged.

In `@app/currency/page.tsx`:
- Around line 29-55: Import the missing types and APIs and initialize the
missing state: add "RatesResponse" from "@/types/api", "ratesApi" from
"@/lib/api/rates", and the "toast" helper (where your project keeps it) to the
top of the file; initialize React state for "balance" and "balanceLoading"
(e.g., useState<number | null>(...) and useState<boolean>(...)) where component
state is declared so code referencing balance/balanceLoading compiles; replace
any calls to the nonexistent setSubmitError with the already-destructured
setApiError from useApiError(); ensure the functions that use RatesResponse
(localPerAcbu, estimateAcbuFromUsd, estimateLocalFromAcbu) keep their signatures
and now refer to the imported RatesResponse type.
- Around line 146-149: The component calls toast(...) (at the places that use
res.transaction_id and res.status) but never imports or initializes the hook;
import useToast (from "@/hooks/use-toast") at the top of the file and initialize
the hook inside the component to get the toast function (e.g., call useToast and
destructure toast) so that the toast variable used in the mint handlers and
other locations is defined; update references to toast in the component
accordingly.
- Around line 118-125: The component is using balance, balanceLoading, and
refreshBalance but never imports or calls the useBalance hook; import useBalance
and call it near the top of the component to destructure the missing values
(e.g., const { balance, balanceLoading, refreshBalance } = useBalance()), then
remove any hardcoded or undefined references and use these variables in the
existing calculations and handlers (references: useBalance, balance,
balanceLoading, refreshBalance, estimateAcbuFromUsd). Ensure the import is added
to the module imports and the hook call is executed before any code that reads
balance, balanceLoading, or refreshBalance.

In `@app/error.tsx`:
- Line 27: The fallback component renders Lucide icons AlertTriangle, RefreshCw,
and Home but they are not imported, causing a compilation error; add named
imports for AlertTriangle, RefreshCw, and Home from the lucide-react package
(e.g., import { AlertTriangle, RefreshCw, Home } from 'lucide-react') at the top
of the file where the component is defined so the symbols used in the JSX
(AlertTriangle, RefreshCw, Home) are resolved.

In `@app/fiat/page.tsx`:
- Around line 20-23: Consolidate the two useApiError() calls into a single hook
instance so the component uses the same uiError, setApiError, clearError,
isSubmitDisabled and handleError bindings (remove the duplicate declaration that
redeclares clearError and merge error/handleError into the first destructure),
and update all references (calls to handleError on lines where it's used) to use
that single instance; also add the missing import for ApiErrorDisplay so the
component can render the error UI that reads uiError.
- Around line 109-110: The component ApiErrorDisplay is used in the render
(wrapped by the uiError check) but not imported; add an import for
ApiErrorDisplay from '@/components/ui/api-error-display' at the top of the
module so the JSX <ApiErrorDisplay error={uiError} onDismiss={clearError} />
resolves correctly and the symbols ApiErrorDisplay, uiError and clearError
remain unchanged in the component.

In `@app/layout.tsx`:
- Around line 73-78: There are two declarations of const nonce in RootLayout
causing a syntax error; remove the duplicate by keeping a single nonce
assignment that reads the header once (e.g., drop the first "const nonce =
(await headers()).get('x-nonce') || undefined;" or drop the later
headersList/duplicate line), and if you need headersList elsewhere rename or
reuse it so only one nonce variable is declared (referencing nonce and
headersList in RootLayout).

In `@app/mint/page.tsx`:
- Around line 66-67: There are duplicate const bindings for clearMintError and
clearBurnError caused by two useApiError() destructurings; remove the redundant
properties from the first destructuring (the one that defines mintUiError,
setMintApiError, isMintDisabled, and burnUiError, setBurnApiError,
isBurnDisabled) so that clearMintError and clearBurnError are only declared once
(where they are re-declared later), keeping the remaining useApiError()
destructures as mintUiError/setMintApiError/isMintDisabled and
burnUiError/setBurnApiError/isBurnDisabled.

In `@app/reserves/page.tsx`:
- Around line 20-27: The new Tooltip and icon components used in the JSX
(Tooltip, TooltipTrigger, TooltipContent, and Info) are not imported, causing
compilation errors; add the missing imports at the top of the file by importing
Tooltip, TooltipTrigger, TooltipContent from your Tooltip/ui component module
(or the library you use) and import the Info icon from your icons library (or
icon set) so the JSX references resolve, then run the build to verify no
unused-import warnings remain.

In `@app/savings/deposit/page.tsx`:
- Around line 25-45: The effect never clears the resolving state except in the
!uri branch; ensure setResolving(false) is called after successful resolution
and in the catch path (but only when not cancelled) so the UI unblocks — update
the promise chain handling around userApi.getReceive / resolveUserUri: after
await resolveUserUri(uri, opts) and setUser(resolved) call setResolving(false)
if not cancelled, and in the .catch((e) => { ... }) branch call
setResolving(false) (guarded by !cancelled) in addition to logging via
logger.error; apply the same fix to the other occurrence referenced (lines
~147-149) to avoid leaving resolving true.

In `@app/savings/withdraw/page.tsx`:
- Around line 17-27: The file mixes old "user"/"targetRecipient" flow with the
new "recipient"/"resolvedRecipient" state; remove references to undefined
symbols (user, setUser, targetRecipient, setError, setResolving, resolveUserUri,
recipientApi) and consistently wire the new flow using the declared state:
recipient, setRecipient, homeRecipient, setHomeRecipient, resolvedRecipient,
setResolvedRecipient, recipientValidationLoading, setRecipientValidationLoading,
recipientValidationError, setRecipientValidationError, editingRecipient,
setEditingRecipient; update effect/validation/submit handlers to call the new
resolver/validator (or import and use the correct resolve/recipient API and
RecipientResponse type) and to use clearError/handleError and success/loading
state variables instead of the old setError/setResolving symbols so the
component type-checks and renders consistently.

In `@app/wallet/page.tsx`:
- Around line 59-64: The re-auth branches call undefined logout and router; fix
by calling the required hooks at the top of the component: invoke useAuth() and
destructure logout (e.g., const { logout } = useAuth()) and call useRouter() to
get router (e.g., const router = useRouter()) before any conditional returns so
both logout and router.push(...) are defined when used in the getPasscode()
failure branches in the page component (references: logout, useAuth, router,
useRouter, router.push, getPasscode).

In `@components/error-boundary.tsx`:
- Line 59: The JSX references missing Lucide icons AlertTriangle, RefreshCw, and
Home causing a compile error; add imports for these icons (e.g., import {
AlertTriangle, RefreshCw, Home } from 'lucide-react') near the top of
components/error-boundary.tsx (with your other imports) so the fallback UI can
render without errors and the references in the ErrorBoundary component resolve.

In `@contexts/auth-context.tsx`:
- Line 24: Remove the stray shell text "git checkout -b
fix/f-043-remove-console-logs" from contexts/auth-context.tsx so the module can
parse, then fix imports: remove the non-existent setToken import from
"@/lib/api/client" and replace it by importing the actual token setter exported
by your codebase (find and import the correct symbol used to set auth tokens
where setToken was referenced), and add an import for clearPasscode from
"@/lib/passcode-manager" so calls to clearPasscode() (used around the clear/auth
flows) resolve; update the import list at the top of contexts/auth-context.tsx
accordingly so all references (setToken usage and clearPasscode calls) point to
real exported functions.
- Around line 79-87: Remove the now-unused setToken import from
"@/lib/api/client" and add the missing import for clearPasscode (the passcode
utility used in this file) so clearPasscode() calls resolve; e.g., import {
clearPasscode } from the passcode utilities module (for example "@/lib/passcode"
or the module in your project that exports clearPasscode), then remove any
leftover references to setToken to avoid unused-import errors and ensure
clearPasscode is imported once for its calls in auth-context (used around the
86/159/166 call sites).

---

Outside diff comments:
In `@app/burn/page.tsx`:
- Around line 109-187: The submit handler currently uses an undefined "values"
and DOM event; change handleSubmit to accept validated values from
react-hook-form (signature: const handleSubmit = async (values: BurnFormValues)
=> { ... }) remove e.preventDefault and the React.FormEvent parameter, and
replace all uses of values.* in handleSubmit with that parameter; bind the form
element to form.handleSubmit(handleSubmit). Also rename any uiError references
to error (or the actual error state variable), replace the undefined
isSubmitDisabled usage with a computed boolean (e.g., !isValid || loading), add
missing imports for CheckCircle and ApiErrorDisplay, and remove the duplicate
useSearchParams import so identifiers (handleSubmit, form.handleSubmit, error,
isSubmitDisabled, CheckCircle, ApiErrorDisplay) resolve.
- Around line 232-371: The form markup has three issues: replace the undefined
formHandleSubmit/onSubmit usage with the existing handleSubmit by changing the
form's onSubmit to use handleSubmit; close the Form component by adding a
closing </Form> tag after the inner </form>; and remove the stray </div> (or add
the corresponding opening <div> wrapper if intended) so that tags balance—look
for the Form wrapper, the inner <form onSubmit={handleSubmit}> and the stray
</div> near the end of the block when making the fixes.

In `@app/currency/page.tsx`:
- Around line 130-198: The catch in handleExecute still calls
setSubmitError(...) — replace that with the useApiError hook's setter (e.g.,
setUiError or setApiError) so the ApiErrorDisplay sees the error; update the
catch to call setUiError(e instanceof Error ? e.message : "Operation failed")
(and keep logger.error(`Currency operation failed: ${activeTab}`, e) and
clearError/clearApiError usage intact), and ensure the setter from useApiError
(setUiError/setApiError) is imported/declared in the component scope where
handleExecute is defined.

In `@app/mint/page.tsx`:
- Around line 242-317: There are two duplicate declarations of the async handler
const handleExecuteBurn in the same scope; remove the redundant one and keep the
correct implementation (ensure you preserve the version that wires up
getWalletSecretAnyLocal, Kit fallback flow, submitBurnRedeemSingleClient,
fiatApi.postOffRamp, setTxId and setStep("success")). After deleting the
duplicate, update any references to handleExecuteBurn if needed (e.g., button
onClick or useEffect) so they point to the remaining handleExecuteBurn and run
the app/build to confirm the duplicate identifier error is resolved.

In `@app/send/page.tsx`:
- Around line 3-52: The file is missing imports for useToast and ApiErrorDisplay
which causes compile errors; add the appropriate imports at the top of the
module: import useToast from the toast hook (or the named export used elsewhere
in the repo) and import ApiErrorDisplay from its UI/error component, then use
those symbols (useToast used where called on line ~83 and ApiErrorDisplay where
rendered around line ~530) so the references resolve; ensure the import paths
match your project's existing convention for toast and error-display components.
- Around line 161-251: The handler handleConfirmTransfer still calls removed
helpers clearSubmitError() and handleSubmitError(e); replace clearSubmitError()
with clearError() and replace handleSubmitError(e) with setApiError(e) (imported
from useApiError), ensure useApiError's clearError and setApiError are used in
this component and included in the handleConfirmTransfer dependency array, and
remove any leftover imports/usages of the old submit-error helpers.
- Around line 161-251: handleConfirmTransfer is closing over confirmedAmount and
validating against the live amount, causing stale submissions; update the
validation to use the snapshot confirmedAmount (e.g. change the guard to if
(!confirmedAmount || parseFloat(confirmedAmount) <= 0 || !to) return) and add
confirmedAmount to the useCallback dependency array so handleConfirmTransfer is
re-memoized when the confirmed value changes; keep references to getToValue,
userId, stellarAddress, kit, opts, loadTransfers, refreshBalance as-is.

In `@lib/api/client.ts`:
- Around line 56-58: RequestOptions was stripped of token but code still reads
opts.token and references currentToken; remove all token-handling: delete any
access to opts.token and the currentToken variable usage in the request()
function, and update apiOpts() to stop returning a token field (only return
headers/cookie or whatever remains), ensuring RequestOptions and apiOpts()
signatures match; search for and remove or refactor any token-related logic in
request(), apiOpts(), and any helpers so compilation no longer references the
undefined token/currentToken symbols.

---

Major comments:
In `@app/burn/page.tsx`:
- Around line 364-367: The submit button is still using the removed local
isValid state; update the form to derive validity from React Hook Form instead:
ensure your useForm call includes a mode that validates on change (e.g.,
useForm({... , mode: "onChange"})) and destructure the RHF formState (const {
formState: { isValid } } = useForm(...)) or read form.getState().isValid, remove
the old local isValid state and any setIsValid handlers, and change the Button
disabled prop to disabled={!isValid || loading || isSubmitDisabled} so the
button is driven by RHF's isValid; keep loading and isSubmitDisabled as-is.
- Around line 86-96: The form's defaultValues are overwriting deep-link params;
update the useForm initialization (useForm<BurnFormValues> with resolver
zodResolver(burnSchema)) so defaultValues for acbuAmount and currency are
derived from the existing URL/deep-link values (e.g., the amount and currency
you already read elsewhere on the page) instead of hardcoding "" and "NGN";
preserve the current fallbacks (empty string and "NGN") if the URL values are
absent and ensure types match BurnFormValues.

In `@app/savings/page.tsx`:
- Around line 33-51: resolveUserUri is defined but never used so
setApiUser/getSavingsPositions keep receiving raw identifiers; update the
callers to await resolveUserUri(rawIdentifier, opts) and pass the returned
canonical URI into setApiUser (and into the subsequent getSavingsPositions
invocation) instead of the raw value; specifically locate usages that feed
setApiUser and the call sites around the block referenced (including the other
occurrence around lines 107-109) and replace feeding raw with the awaited
resolveUserUri(...) result, preserving the same opts parameter shape passed to
resolveRecipient.

In `@app/savings/withdraw/page.tsx`:
- Around line 88-90: The form currently allows submission even when the new
recipient confirmation or validation are unresolved; update the submit gating in
handleSubmit to also require confirmDifferentRecipient === true and that
recipientValidationLoading is false and recipientValidationError is falsy, and
mirror those same checks in the submit button's disabled condition so the button
is disabled while recipientValidationLoading is true, recipientValidationError
exists, or confirmDifferentRecipient is false (in addition to the existing
user/amount checks); reference the handleSubmit function, the
confirmDifferentRecipient flag, recipientValidationLoading,
recipientValidationError, and the submit button disabled logic to locate and
apply these changes.
- Around line 153-179: The "Change" button toggles editingRecipient but the
Input (id="withdraw-account") is always readOnly and bound to user, so
recipient, handleRecipientChange, validateRecipient and the confirmation flow
are never used; update the Input to use value={editingRecipient ? recipient :
(resolving ? "Resolving…" : user)}, remove/readOnly only when editingRecipient
is true (i.e., set readOnly={!editingRecipient}), and wire its onChange to
handleRecipientChange (so validateRecipient can run) and ensure handleToggleEdit
resets recipient to user when cancelling (or preserves when switching to edit)
so the confirmation flow can operate on the edited recipient.

In `@app/send/page.tsx`:
- Around line 117-136: loadTransfers and loadContacts share the same loadError
state (setLoadError) so one loader can clear the other's error; introduce
separate error state variables (e.g., transfersError and contactsError with
setTransfersError and setContactsError) and update loadTransfers to
setTransfersError (and not touch contactsError) and update loadContacts to
setContactsError (and not touch transfersError), keep setLoadingTransfers and
setTransfers/setContacts behavior unchanged, and render any combined banner by
deriving it from both transfersError and contactsError instead of a single
shared loadError.

In `@app/wallet/page.tsx`:
- Around line 70-74: The code currently calls storeWalletSecret(userId,
passphrase, passcode) before contacting the backend (userApi.putWalletAddress
and the similar postWalletConfirm flow), which can leave local secrets
inconsistent if the backend call fails; change the flow so you only call
storeWalletSecret (and any local overwrite of the seed/secret) after the backend
call returns a successful response (check result.ok / HTTP 2xx or equivalent)
for userApi.putWalletAddress(newAddress, opts) and the postWalletConfirm path,
and if the backend call fails surface the error to the caller and do not persist
or overwrite local wallet material (or implement an explicit rollback if
needed).
- Around line 299-303: The banner overpromises security because
lib/wallet-storage.ts currently uses btoa(`${passcode}:${secret}`) to “encrypt”
the seed; replace this with real crypto: in the functions that save/load the
secret in lib/wallet-storage.ts (the code path using
btoa(`${passcode}:${secret}`)) derive an encryption key from the passcode (e.g.,
PBKDF2 with a per-item random salt and sufficient iterations), encrypt the
secret with a modern algorithm (AES-GCM) using a random IV, and persist the
ciphertext together with the salt and IV; update the corresponding load/decrypt
function to reverse this (derive key from stored salt and passcode, then
decrypt) and add robust error handling for failed decryption, and if you keep
the UI text, change “stored securely” to a less absolute phrase unless you can
guarantee stronger protections.

In `@components/error-boundary.tsx`:
- Line 17: componentDidCatch currently only logs errors and doesn't wire the new
onError API or persist errorInfo; update the ErrorBoundary.componentDidCatch
method to call this.setState({ error, errorInfo }) to store the React.ErrorInfo
in state (referencing the state field errorInfo) and invoke
this.props.onError?.(error, errorInfo) so consumers receive the callback; ensure
you reference the onError prop and the errorInfo state property names exactly as
declared.

In `@lib/api/client.ts`:
- Around line 95-98: The current logic adds an 'abort' listener to opts.signal
but doesn't handle the case where opts.signal.aborted is already true, so the
internal AbortController (controller) never gets aborted and the request
proceeds; update the code around the opts.signal handling in lib/api/client.ts
to first check if opts.signal.aborted is true and if so call controller.abort()
immediately, otherwise register the opts.signal.addEventListener('abort', () =>
controller.abort(), { once: true }); so that the internal controller is always
aborted when the caller's signal is already aborted or aborts later (refer to
opts.signal and controller in the surrounding function).

---

Minor comments:
In `@app/layout.tsx`:
- Line 9: The AuthGuard import is stale while its wrapper is commented out;
remove the unused import statement "AuthGuard" (and any duplicate unused imports
around the other occurrences mentioned at lines 86-88) from the module, or
alternatively restore the commented wrapper that uses AuthGuard so the import is
actually used—choose one approach and make the code and imports consistent
(update imports/usages for the AuthGuard symbol accordingly).

---

Duplicate comments:
In `@app/mint/page.tsx`:
- Around line 662-666: The Rates tab uses formatRate(r.rate) but formatRate is
not defined or imported in this module; fix it by either importing the helper
(e.g., add an import for formatRate from its utility module) or add a local
formatRate function in app/mint/page.tsx and use it where rateRows.map calls
formatRate; locate the usage in the JSX (rateRows.map((r) => ...
formatRate(r.rate))) and ensure the symbol is exported from the chosen module or
declared before the component so the reference resolves.

In `@app/savings/deposit/page.tsx`:
- Around line 25-27: The page references missing state and imports: declare
resolving and error state via React useState (e.g., const [resolving,
setResolving] = useState(false); const [error, setError] = useState(""); import
React/useState if needed), import or implement resolveUserUri (or import its
module) and import the AlertCircle component from your icon library (e.g.,
lucide-react) so setResolving, setError, resolveUserUri, resolving, and
AlertCircle are defined; alternatively revert the new flow to the previous
implementation by removing these references. Ensure the cancellation flag
(cancelled) is handled consistently with the resolved flow and update any
useEffect cleanup where used.

In `@app/send/page.tsx`:
- Around line 499-505: The Button currently references a removed function
isFormValid(), causing an undefined call; update the disabled prop to use the
memoized boolean isValid instead (i.e., replace disabled={!isFormValid()} with
disabled={!isValid}) so the Button (component using onClick that calls
setConfirmedAmount and setShowConfirmDialog) uses the existing isValid state;
ensure you only change the disabled expression and leave the onClick handlers
(setConfirmedAmount, setShowConfirmDialog) and className intact.
- Around line 103-148: Remove the stray "dev" tokens and the
duplicated/conflicting loadContacts blocks so the useCallback is a single
well-formed function; specifically, keep one loadContacts useCallback that (1)
clears errors via setContactsError(""), (2) awaits userApi.getContacts(opts) and
calls setContacts(data.contacts ?? []), (3) catches errors and sets
setContactsError(message) and calls toast({...}) with the same message, and (4)
calls setLoadingContacts(false) in finally; also ensure there is no duplicated
closing braces or parentheses left over from the merge and that related symbols
(loadContacts, useCallback, userApi.getContacts, setContacts, setContactsError,
setLoadingContacts, toast) are referenced consistently with loadTransfers.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 06b87c10-0d7f-41c6-b271-7fd40fce8fb8

📥 Commits

Reviewing files that changed from the base of the PR and between ceb5c01 and 63504cf.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (22)
  • app/auth/2fa/page.tsx
  • app/burn/page.tsx
  • app/business/page.tsx
  • app/currency/page.tsx
  • app/error.tsx
  • app/fiat/page.tsx
  • app/layout.tsx
  • app/me/page.tsx
  • app/mint/page.tsx
  • app/page.tsx
  • app/rates/page.tsx
  • app/reserves/page.tsx
  • app/savings/deposit/page.tsx
  • app/savings/page.tsx
  • app/savings/withdraw/page.tsx
  • app/send/page.tsx
  • app/wallet/page.tsx
  • components/error-boundary.tsx
  • contexts/auth-context.tsx
  • lib/api/client.ts
  • lib/wallet-storage.ts
  • package.json
✅ Files skipped from review due to trivial changes (1)
  • app/business/page.tsx
🚧 Files skipped from review as they are similar to previous changes (4)
  • app/page.tsx
  • app/rates/page.tsx
  • package.json
  • app/me/page.tsx

Comment thread app/auth/2fa/page.tsx
Comment thread app/burn/page.tsx
Comment thread app/currency/page.tsx
Comment thread app/currency/page.tsx
Comment thread app/currency/page.tsx
Comment thread app/savings/withdraw/page.tsx
Comment thread app/wallet/page.tsx
Comment thread components/error-boundary.tsx
Comment thread contexts/auth-context.tsx
Comment thread contexts/auth-context.tsx
@Junman140 Junman140 merged commit 19e3b7e into Pi-Defi-world:dev Apr 29, 2026
1 check passed
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.

F-010 — Duplicate Stellar SDK dependencies