Conversation
- Restored "new users" feature: green nodes for recent signups (within activity window) - Three-state activity status: New (green), Active (purple), Inactive (gray) - Progressive inactive fade with exponential time bands (1w, 2w, 4w, 8w, 16w, 32w, 64w+) - Each band gets progressively lighter gray + lower opacity for visual hierarchy - Updated legend to include "New" status - Fixed Grafana link format (teampeanut.grafana.net/explore-peanut-wallet-user)
- Fix external edge tooltips: show per-user amounts instead of "undefined - Invalid Date" - Display accurate transaction counts/amounts per user-external node pair - Update ExternalNode type to include userTxData for edge weight calculation - Change minConnections from slider to discrete buttons (1, 2, 3, 5, 10) - Lower default minConnections from 2 to 1 to show all external nodes - Add fallback logic for missing userTxData Now edges show "External: 5 txs ($200.50)" with correct per-user data instead of showing node totals on every edge.
- Search now includes external nodes (banks, wallets, merchants)
- Searches both custom labels ("Manteca BR Deposit") and original IDs
- Search results show emoji indicators for external node types (🏦 🏪 💳)
- External results show user count and total USD instead of points
- Allow right-click selection of external nodes for camera zoom
- "Focused on" banner shows custom labels for external nodes with orange styling
- Results styled with orange hover for external nodes vs purple for users
Users can now search for "Manteca", "Bridge SEPA", bank IDs, etc.
Activity Time Window Filtering: - Filter external nodes by activityDays (e.g., 30d filter hides old merchants) - Add lastTxDate to ExternalNode type for client-side filtering - External nodes now respect same time window as user nodes - Fixes bug: devcon-swag-shop (last tx Nov 21) no longer shows with 30d filter Logarithmic Edge Animation Scaling: - OLD: Linear scaling made 9 txs vs 32 txs barely distinguishable - NEW: Log10 scaling for dramatic visual distinction between activity levels P2P Edge Scaling (logarithmic): - Line width: 0.4px → 3.0px (7.5x variance) - Particle speed: 0.0003 → 0.001 (3.3x variance, log-scaled by tx count) - Particle size: 1.5px → 6.0px (4x variance, log-scaled by USD volume) - Particle count: 1 → 5 particles (log-scaled) External Edge Scaling (now matching P2P): - Line width: 0.4px → 3.0px (same formula as P2P) - Particle speed: 0.0002 → 0.0008 (70% of P2P max for visual distinction) - Particle size: 1.5px → 6.0px (same 4x variance, log-scaled by USD) - Particle count: 1 → 4 particles (one less than P2P to reduce clutter) Visual Impact Examples: - 1 tx: slow, tiny, single particle (barely visible) - 9 txs: 2.2x faster, 2 particles, medium (clearly visible) - 32 txs: 2.8x faster, 3 particles, larger (dramatically different) - 100 txs: 3.3x max speed, 5 particles, huge (very obvious) External Node Tooltips: - Simplified to show only formatted label (no full account numbers) - Bank accounts: "🏷️ Account: ES27 **** 7890" - Wallets/Merchants: "🏷️ ID: devcon-swag-shop" Code Cleanup: - Removed all console.log statements (migration logs, force logs, recalc debug) - Cleaner console output for production Performance: Logarithmic calculations add negligible overhead (<0.01ms per frame).
- Add `/dev/payment-graph` page with anonymized P2P-only visualization - Add `/dev/layout.tsx` for dev-only route protection (except full-graph and payment-graph) - Rename invite-graph to full-graph - Replace "All nodes" checkbox with "Top nodes" slider (0-10000, backend-filtered) - Remove timestamps from payment mode response for full anonymization - Remove unused `showAllNodes` prop, replace with `topNodes` (number) - Payment mode defaults: merchants ON, minConnections=10, 5000 node limit Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…chant issue Cleaned up all debug console.log statements that were added to investigate the duplicate node ID issue. The root cause was identified and fixed in the backend (SHA-256 hash collision prevention). Changes: - Removed API response duplicate detection logging - Removed external links duplicate ID checking - Removed orphan merchant warning logs - Removed link creation summary logs - Cleaned up unused variables (txDataKeys, matchedLinks, unmatchedKeys)
- Remove verbose debug logs from graph rendering - Add lightweight console.assert() for duplicate ID detection - Remove unused simulation variable - Clean up debug logging throughout InvitesGraph component - Add safety nets for duplicate node/external node IDs This reduces console noise in production while maintaining runtime safety checks that only appear when assertions fail. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Add SizeLabel export for node sizing in payment mode - Make node fields optional to support both full and payment modes - Add separate localStorage key for payment graph preferences - Update payment graph to use topNodes=5000 with performanceMode Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
…op-nodes-slider feat(graph): Add payment-graph mode and top-nodes slider for full-graph
…r state on modal open
- Only update fullName if it's missing (not overwrite existing) - Only update email if it's missing (not overwrite existing) - Add proper error handling for fetchUser() call - Trim email field like accountOwnerName - Only reset error when modal opens (not when closing)
hot-fix: update user details if missing during KYC process and reset error state on modal open
- Replace API key auth with password query param for payment mode - Read password from URL (?password=xxx) or show input form - Support direct URL access: /dev/payment-graph?password=xxx - Backend validates password against PAYMENT_GRAPH_PASSWORD env var - API key still required for full-graph mode Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…op-nodes-slider feat: payment graph with password auth and UI improvements
The external nodes API call was missing password param and was still sending API key header for payment mode. This caused 401 errors when loading external nodes in payment-graph. - Add password option to getExternalNodes - Pass password in query params for payment mode - Skip api-key header for payment mode (uses password auth) - Pass password from InvitesGraph component to API call Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Username was 'konrad' but should be 'kkonrad'. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
feat: disable squid withdrawals
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
WalkthroughThis PR introduces a multi-mode graph visualization system (full, payment, user modes) with corresponding dev pages, routing infrastructure, and enhanced data models. Key additions include a new Payment Graph feature with password gating, renaming Invite Graph to Full Graph, replacing showAllNodes with topNodes-based filtering, restructuring data types to support anonymized qualitative labels (frequency/volume/size), implementing dev route access control via layout middleware, and adding maintenance-based withdrawal gating. Changes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~65 minutes Possibly related PRs
🚥 Pre-merge checks | ❌ 3❌ Failed checks (1 warning, 2 inconclusive)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 7
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
src/components/Global/TokenSelector/TokenSelector.tsx (1)
62-70: Ensure maintenance mode actually enforces the USDC/Arbitrum selection.Right now, if a non‑USDC token/chain is already selected in context,
isSquidWithdrawDisabledonly hides UI but doesn’t override the selection. That can let disallowed selections persist. Consider coercing the selection (and clearing UI state) when maintenance is active.🛠️ Suggested fix
-import React, { type ReactNode, useCallback, useContext, useMemo, useRef, useState } from 'react' +import React, { type ReactNode, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react' @@ } = useContext(tokenSelectorContext) + + useEffect(() => { + if (!isSquidWithdrawDisabled) return + setSelectedChainID(PEANUT_WALLET_CHAIN.id.toString()) + setSelectedTokenAddress(PEANUT_WALLET_TOKEN) + setSearchValue('') + setNetworkSearchValue('') + setShowNetworkList(false) + }, [ + isSquidWithdrawDisabled, + setSelectedChainID, + setSelectedTokenAddress, + setSearchValue, + setNetworkSearchValue, + setShowNetworkList, + ])src/components/Global/InvitesGraph/index.tsx (1)
953-1031: Add missing dependencies to both useEffects and update external nodes cache keyThe second useEffect fetches external nodes using
topNodesandpassword, but neither is included in the dependency array. This causes stale external nodes whentopNodeschanges (e.g., full graph slider) or whenpasswordis updated. Additionally, the cache key (externalNodesFetchedLimitRef) only tracks the limit, nottopNodes, preventing cache invalidation.The first useEffect also fails to include
props.passwordin its dependencies, though it's conditionally passed to the API.Add
topNodesandprops.passwordto both useEffect dependency arrays, and update the cache ref to track bothlimitandtopNodes:Proposed fix
-const externalNodesFetchedLimitRef = useRef<number | null>(null) +const externalNodesFetchedRef = useRef<{ limit: number; topNodes?: number } | null>(null) useEffect(() => { if (isMinimal) return -}, [isMinimal, !isMinimal && props.apiKey, mode, topNodes]) +}, [isMinimal, props.apiKey, mode, topNodes, props.password]) useEffect(() => { if (isMinimal) return if (!externalNodesConfig.enabled) return - const lastLimit = externalNodesFetchedLimitRef.current - if (lastLimit !== null && lastLimit >= externalNodesConfig.limit) return + const effectiveTopNodes = topNodes > 0 ? topNodes : undefined + const lastFetch = externalNodesFetchedRef.current + if (lastFetch && lastFetch.limit >= externalNodesConfig.limit && lastFetch.topNodes === effectiveTopNodes) return if (result.success && result.data) { setExternalNodesData(result.data.nodes) - externalNodesFetchedLimitRef.current = externalNodesConfig.limit + externalNodesFetchedRef.current = { limit: externalNodesConfig.limit, topNodes: effectiveTopNodes } -}, [isMinimal, !isMinimal && props.apiKey, externalNodesConfig.enabled, mode, externalNodesConfig.limit]) +}, [isMinimal, props.apiKey, externalNodesConfig.enabled, mode, externalNodesConfig.limit, topNodes, props.password])
🤖 Fix all issues with AI agents
In @.cursorrules:
- Line 10: Typo: change "explicity" to "explicitly" in the .cursorrules text
line that reads "**Do not generate .md files** unless explicity told to do so."
— update that phrase to "**Do not generate .md files** unless explicitly told to
do so." to correct the spelling.
In `@src/app/`(mobile-ui)/dev/layout.tsx:
- Around line 1-19: The DevLayout client component currently calls notFound(),
which throws in client-side code; either move the guard out of the client module
(e.g., implement the check in a Server Component or middleware using IS_DEV and
PRODUCTION_ALLOWED_ROUTES) or replace the client-side behavior: remove
notFound(), import useRouter from 'next/navigation', call const router =
useRouter(), and inside a useEffect check the pathname against
PRODUCTION_ALLOWED_ROUTES and call router.replace('/404') (or another safe
route) when not allowed, returning null while redirecting; reference DevLayout,
PRODUCTION_ALLOWED_ROUTES, IS_DEV, usePathname, and notFound to locate the
change.
In `@src/app/`(mobile-ui)/dev/page.tsx:
- Line 81: The text in the dev page ("• These tools are only available in
development mode") is inconsistent with route access rules defined in
src/constants/routes.ts (notably the /dev/payment-graph public API-key route);
update the copy in src/app/(mobile-ui)/dev/page.tsx to accurately reflect access
control (either change the line to note that some tools require development mode
while /dev/payment-graph is public via API key, or indicate per-tool access like
"Development-only; some tools (e.g. /dev/payment-graph) are public via API
key"). Locate the string in the Dev page component and adjust the wording to
match the actual policies or add a short per-tool note linking to the
routes/constants for clarity.
In `@src/app/`(mobile-ui)/dev/payment-graph/page.tsx:
- Around line 16-23: The useEffect that reads the password from searchParams
(the block using useEffect, searchParams.get('password'), setPassword and
setPasswordSubmitted) must remove/scrub the password query param immediately
after reading it to avoid leaking credentials; after calling setPassword and
setPasswordSubmitted, programmatically replace the URL (e.g., via Next.js
router.replace or history.replaceState) with the same path but without the
password param so the credential is not retained in browser history or
referrers, and ensure this scrub happens only once (guarded by the same effect)
to preserve current behavior.
In `@src/config/underMaintenance.config.ts`:
- Around line 40-45: The config underMaintenanceConfig currently sets
disableSquidWithdraw: true which globally disables cross-chain withdrawals;
change the default to false in underMaintenanceConfig (MaintenanceConfig) so
x‑chain withdrawals remain enabled by default, and if desired read an
environment flag or feature flag to toggle disableSquidWithdraw per-deployment
(e.g., pull from process.env or a runtime feature manager) so ops can disable it
without shipping a global default of true.
In `@src/services/points.ts`:
- Around line 316-318: The code currently adds sensitive passwords to the URL
querystring via params.set('password', options.password), which leaks secrets
into logs and Sentry; change the request to send the password in a secure place
instead: remove any params.set('password', ...) usage and instead attach the
secret in a request header (e.g., 'Authorization' or 'X-Password') or in the
POST body when calling fetchWithSentry; update all occurrences that build params
(the params variable and any code paths referenced around options?.password) to
stop including password in the URL and ensure fetchWithSentry is passed the
header/body value; if the backend cannot accept headers/body, change
fetchWithSentry to redact the password from logged URLs before sending to Sentry
(redact in fetchWithSentry by replacing the password query value with
'[REDACTED]') and update callers accordingly.
- Around line 308-311: Add an early guard that returns/throws a local error when
payment mode is requested but no password is provided: check the computed
isPaymentMode (options?.mode === 'payment') and if true and options?.password is
falsy, immediately return or throw a clear validation error instead of
proceeding (before creating URLSearchParams or making any network calls). Apply
the same short-circuit in the other payment-related branch referenced by the
code around where isPaymentMode/params are used (the second flow at the other
occurrence).
🧹 Nitpick comments (3)
src/components/AddWithdraw/AddWithdrawCountriesList.tsx (1)
148-154: Consider validating trimmed values before adding to payload.The truthiness check happens before
.trim(), so whitespace-only strings like" "would pass the check but result in empty values being sent to the API.Suggested defensive approach
- if (!hasNameOnLoad && rawData.accountOwnerName) { - updatePayload.fullName = rawData.accountOwnerName.trim() + const trimmedName = rawData.accountOwnerName?.trim() + if (!hasNameOnLoad && trimmedName) { + updatePayload.fullName = trimmedName } - if (!hasEmailOnLoad && rawData.email) { - updatePayload.email = rawData.email.trim() + const trimmedEmail = rawData.email?.trim() + if (!hasEmailOnLoad && trimmedEmail) { + updatePayload.email = trimmedEmail }src/constants/routes.ts (1)
66-66: Ensure PUBLIC_ROUTES array and regex stay in sync.The regex pattern now includes
dev\/payment-graph, which correctly matches thePUBLIC_ROUTESarray entry. This is good practice. Consider adding a comment or test to ensure these stay synchronized when routes are added/removed.src/services/points.ts (1)
6-23: CentralizeP2PEdgeto avoid type drift.This duplicates
src/components/Global/InvitesGraph/index.tsx(Line 127-139). Consider moving the type to a shared module (e.g.,src/services/services.typesor agraph.typesfile) and importing it in both places to prevent divergence.
| - **Never open SVG files** - it crashes you. Only read jpeg, png, gif, or webp. | ||
| - **Never run jq command** - it crashes you. | ||
| - **Never run sleep** from command line - it hibernates pc. | ||
| - **Do not generate .md files** unless explicity told to do so. |
There was a problem hiding this comment.
Typo: "explicity" should be "explicitly".
📝 Proposed fix
-- **Do not generate .md files** unless explicity told to do so.
+- **Do not generate .md files** unless explicitly told to do so.📝 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.
| - **Do not generate .md files** unless explicity told to do so. | |
| - **Do not generate .md files** unless explicitly told to do so. |
🤖 Prompt for AI Agents
In @.cursorrules at line 10, Typo: change "explicity" to "explicitly" in the
.cursorrules text line that reads "**Do not generate .md files** unless
explicity told to do so." — update that phrase to "**Do not generate .md files**
unless explicitly told to do so." to correct the spelling.
| 'use client' | ||
|
|
||
| import { usePathname } from 'next/navigation' | ||
| import { notFound } from 'next/navigation' | ||
| import { IS_DEV } from '@/constants/general.consts' | ||
|
|
||
| // Routes that are allowed in production (protected by API key / user check) | ||
| const PRODUCTION_ALLOWED_ROUTES = ['/dev/full-graph', '/dev/payment-graph'] | ||
|
|
||
| export default function DevLayout({ children }: { children: React.ReactNode }) { | ||
| const pathname = usePathname() | ||
|
|
||
| // In production, only allow specific routes (full-graph, payment-graph) | ||
| // Other dev tools (leaderboard, shake-test, dev index) are dev-only | ||
| if (!IS_DEV) { | ||
| const isAllowedInProd = PRODUCTION_ALLOWED_ROUTES.some((route) => pathname?.startsWith(route)) | ||
| if (!isAllowedInProd) { | ||
| notFound() | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
Next.js notFound() client component 'use client' error behavior
💡 Result:
notFound() is not a “client-side navigation” API in the App Router. It’s implemented by throwing a special error (NEXT_HTTP_ERROR_FALLBACK;404) to terminate rendering of the current route segment, so Next.js can render that segment’s not-found.js UI. This mechanism is designed for server-side rendering paths (e.g., Server Components during render, and other server-controlled flows), not for Client Component event handlers. [1]
Because "use client" makes the file part of the client module graph (rendered on the client after navigation/hydration), calling notFound() there typically results in a runtime error / red error overlay, rather than showing not-found.js, since there’s no server render to “bail out of” and swap in the segment’s Not Found boundary. [1], [2]
What to do in a Client Component instead
- Render your own “not found” UI conditionally, or
- Navigate with the client router (e.g.,
router.replace(...)) to a dedicated route, rather than callingnotFound(). (Thenot-found.jsconvention is specifically for whennotFound()is thrown during segment rendering.) [3]
Note: Tutorials showing notFound() inside a "use client" component are misleading for real App Router behavior. [1], [4]
Sources:
[1] Next.js API Reference: notFound() (throws NEXT_HTTP_ERROR_FALLBACK;404)
[2] Next.js Docs: Server vs Client Components ("use client" boundary; Client Components render on the client)
[3] Next.js API Reference: not-found.js convention (rendered when notFound() is thrown in a segment)
[4] Example article demonstrating notFound() inside a Client Component (non-official; conflicts with intended mechanism)
🏁 Script executed:
# First, let's examine the actual file to understand the context
cat -n src/app/\(mobile-ui\)/dev/layout.tsx 2>/dev/null || find . -name "layout.tsx" -path "*dev*" | head -5Repository: peanutprotocol/peanut-ui
Length of output: 1028
notFound() throws a runtime error in client components and won't render the not-found page
notFound() is implemented by throwing a special error during server-side rendering to bail out of a route segment. Since this file is marked with 'use client', the component renders on the client after navigation, so calling notFound() will result in a runtime error instead of showing not-found.js. Either move this guard to a Server Component or middleware, or replace with a client-side redirect using the router.
🤖 Prompt for AI Agents
In `@src/app/`(mobile-ui)/dev/layout.tsx around lines 1 - 19, The DevLayout client
component currently calls notFound(), which throws in client-side code; either
move the guard out of the client module (e.g., implement the check in a Server
Component or middleware using IS_DEV and PRODUCTION_ALLOWED_ROUTES) or replace
the client-side behavior: remove notFound(), import useRouter from
'next/navigation', call const router = useRouter(), and inside a useEffect check
the pathname against PRODUCTION_ALLOWED_ROUTES and call router.replace('/404')
(or another safe route) when not allowed, returning null while redirecting;
reference DevLayout, PRODUCTION_ALLOWED_ROUTES, IS_DEV, usePathname, and
notFound to locate the change.
| <h3 className="font-bold text-blue-900">ℹ️ Info</h3> | ||
| <ul className="space-y-1 text-sm text-blue-800"> | ||
| <li>• These tools are publicly accessible (no login required)</li> | ||
| <li>• These tools are only available in development mode</li> |
There was a problem hiding this comment.
Inconsistent info text with actual access control.
This text states tools are "only available in development mode", but according to src/constants/routes.ts, /dev/payment-graph is a public route accessible via API key. Consider updating the text to accurately reflect access policies, or clarifying which tools have different access levels.
🤖 Prompt for AI Agents
In `@src/app/`(mobile-ui)/dev/page.tsx at line 81, The text in the dev page ("•
These tools are only available in development mode") is inconsistent with route
access rules defined in src/constants/routes.ts (notably the /dev/payment-graph
public API-key route); update the copy in src/app/(mobile-ui)/dev/page.tsx to
accurately reflect access control (either change the line to note that some
tools require development mode while /dev/payment-graph is public via API key,
or indicate per-tool access like "Development-only; some tools (e.g.
/dev/payment-graph) are public via API key"). Locate the string in the Dev page
component and adjust the wording to match the actual policies or add a short
per-tool note linking to the routes/constants for clarity.
| // Check for password in URL on mount | ||
| useEffect(() => { | ||
| const urlPassword = searchParams.get('password') | ||
| if (urlPassword) { | ||
| setPassword(urlPassword) | ||
| setPasswordSubmitted(true) | ||
| } | ||
| }, [searchParams]) |
There was a problem hiding this comment.
Avoid keeping the payment-graph password in the URL
Query params can leak via browser history, referrers, and logs. Since this is a password gate, scrub the password param after reading it (or avoid query params entirely).
🔒 Proposed fix (scrub query param after reading)
useEffect(() => {
const urlPassword = searchParams.get('password')
if (urlPassword) {
setPassword(urlPassword)
setPasswordSubmitted(true)
+ // Scrub password from the URL to avoid leaks via history/referrer/logs
+ if (typeof window !== 'undefined') {
+ const url = new URL(window.location.href)
+ url.searchParams.delete('password')
+ window.history.replaceState({}, '', url.toString())
+ }
}
}, [searchParams])📝 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.
| // Check for password in URL on mount | |
| useEffect(() => { | |
| const urlPassword = searchParams.get('password') | |
| if (urlPassword) { | |
| setPassword(urlPassword) | |
| setPasswordSubmitted(true) | |
| } | |
| }, [searchParams]) | |
| // Check for password in URL on mount | |
| useEffect(() => { | |
| const urlPassword = searchParams.get('password') | |
| if (urlPassword) { | |
| setPassword(urlPassword) | |
| setPasswordSubmitted(true) | |
| // Scrub password from the URL to avoid leaks via history/referrer/logs | |
| if (typeof window !== 'undefined') { | |
| const url = new URL(window.location.href) | |
| url.searchParams.delete('password') | |
| window.history.replaceState({}, '', url.toString()) | |
| } | |
| } | |
| }, [searchParams]) |
🤖 Prompt for AI Agents
In `@src/app/`(mobile-ui)/dev/payment-graph/page.tsx around lines 16 - 23, The
useEffect that reads the password from searchParams (the block using useEffect,
searchParams.get('password'), setPassword and setPasswordSubmitted) must
remove/scrub the password query param immediately after reading it to avoid
leaking credentials; after calling setPassword and setPasswordSubmitted,
programmatically replace the URL (e.g., via Next.js router.replace or
history.replaceState) with the same path but without the password param so the
credential is not retained in browser history or referrers, and ensure this
scrub happens only once (guarded by the same effect) to preserve current
behavior.
| const underMaintenanceConfig: MaintenanceConfig = { | ||
| enableFullMaintenance: false, // set to true to redirect all pages to /maintenance | ||
| enableMaintenanceBanner: false, // set to true to show maintenance banner on all pages | ||
| disabledPaymentProviders: [], // set to ['MANTECA'] to disable Manteca QR payments | ||
| disableSquidWithdraw: true, // set to true to disable cross-chain withdrawals (only allows USDC on Arbitrum) | ||
| } |
There was a problem hiding this comment.
Defaulting disableSquidWithdraw to true disables x‑chain withdrawals globally.
Unless this is intended for all environments, this will ship with cross‑chain withdrawals disabled. Consider defaulting to false and toggling via env/ops when needed.
🛠️ Suggested change
const underMaintenanceConfig: MaintenanceConfig = {
enableFullMaintenance: false, // set to true to redirect all pages to /maintenance
enableMaintenanceBanner: false, // set to true to show maintenance banner on all pages
disabledPaymentProviders: [], // set to ['MANTECA'] to disable Manteca QR payments
- disableSquidWithdraw: true, // set to true to disable cross-chain withdrawals (only allows USDC on Arbitrum)
+ disableSquidWithdraw: false, // set to true to disable cross-chain withdrawals (only allows USDC on Arbitrum)
}📝 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.
| const underMaintenanceConfig: MaintenanceConfig = { | |
| enableFullMaintenance: false, // set to true to redirect all pages to /maintenance | |
| enableMaintenanceBanner: false, // set to true to show maintenance banner on all pages | |
| disabledPaymentProviders: [], // set to ['MANTECA'] to disable Manteca QR payments | |
| disableSquidWithdraw: true, // set to true to disable cross-chain withdrawals (only allows USDC on Arbitrum) | |
| } | |
| const underMaintenanceConfig: MaintenanceConfig = { | |
| enableFullMaintenance: false, // set to true to redirect all pages to /maintenance | |
| enableMaintenanceBanner: false, // set to true to show maintenance banner on all pages | |
| disabledPaymentProviders: [], // set to ['MANTECA'] to disable Manteca QR payments | |
| disableSquidWithdraw: false, // set to true to disable cross-chain withdrawals (only allows USDC on Arbitrum) | |
| } |
🤖 Prompt for AI Agents
In `@src/config/underMaintenance.config.ts` around lines 40 - 45, The config
underMaintenanceConfig currently sets disableSquidWithdraw: true which globally
disables cross-chain withdrawals; change the default to false in
underMaintenanceConfig (MaintenanceConfig) so x‑chain withdrawals remain enabled
by default, and if desired read an environment flag or feature flag to toggle
disableSquidWithdraw per-deployment (e.g., pull from process.env or a runtime
feature manager) so ops can disable it without shipping a global default of
true.
| const isPaymentMode = options?.mode === 'payment' | ||
| const params = new URLSearchParams() | ||
| if (isPaymentMode) { | ||
| params.set('mode', 'payment') |
There was a problem hiding this comment.
Short-circuit when payment mode is missing a password.
Both flows allow payment mode without a password, which results in avoidable network calls and less clear errors. Consider returning a local error immediately.
💡 Suggested guard
const isPaymentMode = options?.mode === 'payment'
+ if (isPaymentMode && !options?.password) {
+ return { success: false, data: null, error: 'Invalid or missing password.' }
+ } // Payment mode uses password auth, full mode requires JWT
const isPaymentMode = options?.mode === 'payment'
+ if (isPaymentMode && !options?.password) {
+ return { success: false, data: null, error: 'Invalid or missing password.' }
+ }
if (!isPaymentMode && !jwtToken) {
return { success: false, data: null, error: 'Not authenticated. Please log in.' }
}Also applies to: 355-357
🤖 Prompt for AI Agents
In `@src/services/points.ts` around lines 308 - 311, Add an early guard that
returns/throws a local error when payment mode is requested but no password is
provided: check the computed isPaymentMode (options?.mode === 'payment') and if
true and options?.password is falsy, immediately return or throw a clear
validation error instead of proceeding (before creating URLSearchParams or
making any network calls). Apply the same short-circuit in the other
payment-related branch referenced by the code around where isPaymentMode/params
are used (the second flow at the other occurrence).
| if (options?.password) { | ||
| params.set('password', options.password) | ||
| } |
There was a problem hiding this comment.
Avoid sending payment passwords via query string (secret leakage risk).
Query params are logged in fetchWithSentry (URL is captured in Sentry and console logs), so the password can leak via logs, browser history, proxies, and referrers. Prefer sending it via a header or POST body. If the backend only accepts query params, consider redacting password in fetchWithSentry before logging.
🔒 Proposed fix (header-based password)
- if (options?.password) {
- params.set('password', options.password)
- }
const endpoint = `/invites/graph${params.toString() ? `?${params}` : ''}`
// Payment mode uses password auth (no API key needed), full mode requires API key + JWT
- const headers: Record<string, string> = isPaymentMode ? {} : { 'api-key': apiKey }
+ const headers: Record<string, string> = isPaymentMode ? {} : { 'api-key': apiKey }
+ if (options?.password) {
+ headers['x-payment-password'] = options.password
+ }- // Password is required for payment mode
- if (options?.password) {
- params.set('password', options.password)
- }
const url = `${PEANUT_API_URL}/invites/graph/external${params.toString() ? `?${params}` : ''}`
@@
const headers: Record<string, string> = {
'Content-Type': 'application/json',
}
+ if (options?.password) {
+ headers['x-payment-password'] = options.password
+ }Also applies to: 321-322, 374-380, 387-395
🤖 Prompt for AI Agents
In `@src/services/points.ts` around lines 316 - 318, The code currently adds
sensitive passwords to the URL querystring via params.set('password',
options.password), which leaks secrets into logs and Sentry; change the request
to send the password in a secure place instead: remove any
params.set('password', ...) usage and instead attach the secret in a request
header (e.g., 'Authorization' or 'X-Password') or in the POST body when calling
fetchWithSentry; update all occurrences that build params (the params variable
and any code paths referenced around options?.password) to stop including
password in the URL and ensure fetchWithSentry is passed the header/body value;
if the backend cannot accept headers/body, change fetchWithSentry to redact the
password from logged URLs before sending to Sentry (redact in fetchWithSentry by
replacing the password query value with '[REDACTED]') and update callers
accordingly.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 2 potential issues.
Bugbot Autofix is OFF. To automatically fix reported issues with Cloud Agents, enable Autofix in the Cursor dashboard.
This PR is being reviewed by Cursor Bugbot
Details
You are on the Bugbot Free tier. On this plan, Bugbot will review limited PRs each billing cycle.
To receive Bugbot reviews on all of your PRs, visit the Cursor dashboard to activate Pro and start your 14-day free trial.
| > | ||
| <Icon name="logout" size={32} className="size-8" /> | ||
| </Button> | ||
| /> |
There was a problem hiding this comment.
Logout icon hidden during loading state
Low Severity
The refactor from using Icon as a child to using the icon prop changes the loading behavior. The Button component's renderIcon() function returns null when loading is true, meaning the logout icon now disappears entirely while isLoggingOut is true. Previously, the icon was passed as children and remained visible alongside the loading spinner. Users will now see only a spinner during logout instead of both the spinner and the logout icon.
| fetchExternalNodes() | ||
| }, [isMinimal, props.apiKey, externalNodesConfig.enabled]) | ||
| // eslint-disable-next-line react-hooks/exhaustive-deps | ||
| }, [isMinimal, !isMinimal && props.apiKey, externalNodesConfig.enabled, mode, externalNodesConfig.limit]) |
There was a problem hiding this comment.
External nodes fetch missing topNodes dependency
Medium Severity
The external nodes fetch useEffect uses topNodes in the API call (line 1007: topNodes: topNodes > 0 ? topNodes : undefined) but topNodes is not included in the dependency array. When users increase the top-N slider, the main graph refetches correctly, but external nodes don't refetch. This means external nodes (wallets/banks/merchants) connected only to users in the expanded range won't appear, causing incomplete visualization of the network.
Additional Locations (1)
8d0e1bc
Note
Introduces a new payment graph and refactors the invites graph to support multiple modes and scalable filtering, plus maintenance-based withdraw restrictions.
/dev/payment-graphpage with password access, performance mode, and P2P-focused overlays; marks it public in routes while backend/password-gated/dev/full-graph; adds(mobile-ui)/dev/layoutto restrict dev routes in prod and updates dev tools indexInvitesGraphoverhaul: newmode(full/payment/user), backend-driventopNodeslimit (replacesshowAllNodes), payment-safe anonymized data (size/frequency/volume), improved external node linking/direction, new force defaults, search for external nodes, revised legends/labels, and separate saved preferences per modefullName/emailfrom bank form before KYC; KYC modal now resets errors on openunderMaintenance.disableSquidWithdrawto limit withdraws to USDC on Arbitrum with UI noticeresetErrorinuseBridgeKycFlow; extend public routes regexWritten by Cursor Bugbot for commit 6bb7377. This will update automatically on new commits. Configure here.