Conversation
✅ Deploy Preview for nesterdapp canceled.
|
There was a problem hiding this comment.
Pull request overview
Cleanup/restructure across the Nester dapp, website, and intelligence service: fixes broken website routing, promotes /dashboard/* routes to top-level pages with redesigned UI, and introduces a new Prometheus (Gemini) intelligence layer with chat + analysis endpoints.
Changes:
- Website: update “Nester for Web” CTA to point to the deployed dapp URL.
- Dapp frontend: new top-level Vaults/Savings/Portfolio/Offramp pages, new navbar + mobile drawer, new 404 page, and removal of legacy
/dashboard/*subpages and old Prometheus panel. - Intelligence (Python): add Prometheus service logic, in-memory conversation store, and new routers for streaming chat + structured analysis.
Reviewed changes
Copilot reviewed 32 out of 33 changed files in this pull request and generated 18 comments.
Show a summary per file
| File | Description |
|---|---|
| apps/website/src/components/navbar.tsx | Updates external CTA URL for the web dapp. |
| apps/intelligence/app/services/prometheus.py | Adds Gemini-based streaming chat + structured analysis helpers. |
| apps/intelligence/app/services/conversation_store.py | Adds in-memory per-user conversation history with TTL. |
| apps/intelligence/app/routers/chat.py | Adds SSE chat endpoint router. |
| apps/intelligence/app/routers/analyze.py | Adds structured analysis endpoints router. |
| apps/intelligence/app/config.py | Adds gemini_api_key setting. |
| apps/intelligence/.gitignore | Adds local ignore rules for the intelligence app. |
| apps/dapp/frontend/public/xlm.png | Adds XLM icon asset. |
| apps/dapp/frontend/lib/validation.ts | Switches Zod import path for validation helpers. |
| apps/dapp/frontend/lib/token-icons.ts | Adds token icon path constants. |
| apps/dapp/frontend/lib/mock-vaults.ts | Adds a new mock vault entry used by the redesigned Vaults UI. |
| apps/dapp/frontend/components/vault-action-modals.tsx | Updates Zod import path and adjusts RHF resolver wiring. |
| apps/dapp/frontend/components/notification-bell.tsx | Updates notifications route to new top-level path. |
| apps/dapp/frontend/components/network/NetworkSelector.tsx | Adjusts network selector dropdown styling and banner UI. |
| apps/dapp/frontend/components/navbar.tsx | Replaces legacy navbar with new top-level routing + mobile drawer behavior. |
| apps/dapp/frontend/components/dashboard/vault-positions-table.tsx | Updates Vault links to new /vaults/* routes. |
| apps/dapp/frontend/components/dashboard/recent-activity.tsx | Updates “View all history” link to /portfolio. |
| apps/dapp/frontend/components/ai/prometheusPanel.tsx | Removes old Prometheus panel implementation. |
| apps/dapp/frontend/components/ai/prometheusChatbot.tsx | Adds new floating Prometheus chatbot UI component. |
| apps/dapp/frontend/app/vaults/page.tsx | Adds redesigned Vaults list/grid page with filters + deposit modal. |
| apps/dapp/frontend/app/vaults/[id]/page.tsx | Adds vault detail page UI. |
| apps/dapp/frontend/app/vaults/[id]/not-found.tsx | Updates vault not-found link to new route. |
| apps/dapp/frontend/app/savings/page.tsx | Adds redesigned Savings page with multiple plan types + deposit modal. |
| apps/dapp/frontend/app/portfolio/page.tsx | Adds redesigned Portfolio page with positions/activity tabs and asset lookup. |
| apps/dapp/frontend/app/offramp/page.tsx | Renames/updates Settlements → Offramp page and Zod import path. |
| apps/dapp/frontend/app/notifications/page.tsx | Adjusts layout spacing to match new fixed header/banner behavior. |
| apps/dapp/frontend/app/not-found.tsx | Adds custom app-wide 404 page UI. |
| apps/dapp/frontend/app/layout.tsx | Mounts the new Prometheus chatbot and adjusts NetworkBanner placement. |
| apps/dapp/frontend/app/dashboard/vaults/page.tsx | Removes legacy /dashboard/vaults page. |
| apps/dapp/frontend/app/dashboard/vaults/[id]/page.tsx | Removes legacy /dashboard/vaults/[id] page. |
| apps/dapp/frontend/app/dashboard/settings/page.tsx | Removes legacy settings page. |
| apps/dapp/frontend/app/dashboard/page.tsx | Updates dashboard styling and links to new top-level routes; removes old Prometheus panel embed. |
| apps/dapp/frontend/app/dashboard/history/page.tsx | Removes legacy history page. |
Comments suppressed due to low confidence (3)
apps/website/src/components/navbar.tsx:64
- This
Linkopens a new tab but is missingrel="noopener noreferrer", which is recommended to prevent reverse-tabnabbing.
apps/website/src/components/navbar.tsx:94 - This
Linkopens a new tab but is missingrel="noopener noreferrer", which is recommended to prevent reverse-tabnabbing.
apps/dapp/frontend/app/offramp/page.tsx:125 - Casting the Zod schema to
anyforzodResolverdrops type-safety and can mask real validation/type issues. Prefer using compatible Zod types (e.g., import fromzodinstead ofzod/v4if needed) or upgrade/downgrade@hookform/resolverssozodResolver(formSchema)type-checks withoutany.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 31 out of 33 changed files in this pull request and generated 10 comments.
Files not reviewed (1)
- apps/dapp/frontend/package-lock.json: Language not supported
Comments suppressed due to low confidence (3)
apps/dapp/frontend/app/offramp/page.tsx:124
zodResolver(formSchema as any)removes type-safety from validation. This often happens when the schema is built fromzod/v4but the resolver types referencezod. Prefer importing Zod fromzodconsistently and remove theas anycast.
apps/website/src/components/navbar.tsx:64- This external link opens a new tab but is missing
rel="noopener noreferrer", which can enable reverse-tabnabbing. Add the rel attribute when usingtarget="_blank".
apps/website/src/components/navbar.tsx:94 - Same issue here:
target="_blank"should be paired withrel="noopener noreferrer"for external links.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…r modal, no spinners
…framp - Move /dashboard/vaults, /savings, /portfolio, /settlements, /notifications to top-level routes (/vaults, /savings, /portfolio, /offramp, /notifications) - Delete all old /dashboard/* subdirectories - Update all internal links across navbar, components, and pages - Rename SettlementsPage → OfframpPage - Fix max-w-5xl → max-w-7xl on dashboard, vaults, vault detail pages so content aligns with navbar container width
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 34 out of 36 changed files in this pull request and generated 8 comments.
Files not reviewed (1)
- apps/dapp/frontend/package-lock.json: Language not supported
Comments suppressed due to low confidence (1)
apps/dapp/frontend/app/offramp/page.tsx:13
- This page imports Zod from
"zod/v4"and then useszodResolver(formSchema as any). This indicates the same mixed-Zod-entrypoint problem as elsewhere in the dapp and it drops type-safety for form values. Standardize imports (e.g.,import { z } from "zod") and remove theas anysozodResolver(formSchema)type-checks cleanly.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| @@ -1,4 +1,4 @@ | |||
| import { z } from "zod"; | |||
| import { z } from "zod/v4"; | |||
There was a problem hiding this comment.
lib/validation.ts imports Zod via "zod/v4", while other files (e.g. forms using zodResolver) import from "zod". Mixing these entrypoints produces incompatible TypeScript types and forces downstream as any casts. Standardize on a single Zod import path across the dapp (prefer import { z } from "zod";) so schemas/validators compose without any.
| import { z } from "zod/v4"; | |
| import { z } from "zod"; |
| reset: resetForm | ||
| } = useForm<FormValues>({ | ||
| resolver: zodResolver(formSchema), | ||
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
| resolver: zodResolver(formSchema as any), | ||
| mode: "onBlur", | ||
| defaultValues: { amount: "" } |
There was a problem hiding this comment.
The zodResolver(formSchema as any) cast (and lint suppression) is masking a real type incompatibility. In this file the schema is built with z from "zod" but validateAmount() comes from lib/validation.ts, which currently imports from "zod/v4" — that mixes Zod types and breaks inference. Once Zod imports are unified, remove the as any and eslint disables so the form schema stays type-safe.
| <button | ||
| className="flex h-6 w-6 items-center justify-center rounded-full border border-black/12 text-black/35 hover:border-black/25 hover:text-black/60 transition-colors" | ||
| tabIndex={-1} | ||
| aria-label="More info" | ||
| > | ||
| <Info className="h-3 w-3" /> | ||
| </button> |
There was a problem hiding this comment.
The info tooltip trigger is removed from keyboard navigation (tabIndex={-1}) and is hover-only. This prevents keyboard-only and many mobile users from accessing the explanatory copy. Make the trigger focusable and add onFocus/onBlur (and/or click-to-toggle) so the tooltip is accessible.
| def __init__(self, ttl_minutes: int = 60, max_turns: int = 20) -> None: | ||
| self._ttl = timedelta(minutes=ttl_minutes) | ||
| self._max_turns = max_turns # keep last N messages to cap token spend | ||
| self._store: dict[str, list[dict[str, str]]] = {} | ||
| self._touched: dict[str, datetime] = {} | ||
|
|
There was a problem hiding this comment.
ConversationStore can grow without bound in number of user_id keys: TTL only evicts users that have been touched recently, and there’s no max-users cap. An attacker (or buggy client) can send many distinct userIds and cause unbounded memory growth. Consider adding a maximum user count / LRU eviction (or a size-based cap) in addition to max_turns per user.
| @router.get("/chat") | ||
| async def chat( | ||
| userId: str = Query(..., description="Nester user ID or wallet address"), | ||
| message: str = Query(..., description="User message to Prometheus"), | ||
| ) -> StreamingResponse: |
There was a problem hiding this comment.
The chat endpoint sends user messages as a GET query param (message). This leaks user content into URLs (logs, proxies, browser history) and is constrained by URL length limits. Consider offering a POST-based streaming endpoint (e.g., POST /chat with body {userId, message} and fetch + ReadableStream) while keeping the GET endpoint only for short/debug use if needed.
| @router.get("/chat") | ||
| async def chat( | ||
| userId: str = Query(..., description="Nester user ID or wallet address"), | ||
| message: str = Query(..., description="User message to Prometheus"), | ||
| ) -> StreamingResponse: | ||
| """Stream a Prometheus AI response as Server-Sent Events. | ||
|
|
||
| The client should open this with `EventSource` or `fetch` + `ReadableStream`. | ||
| Each event is `data: <text chunk>\\n\\n`. | ||
| The stream terminates with `data: [DONE]\\n\\n`. | ||
| """ | ||
| return StreamingResponse( | ||
| stream_chat(userId, message), |
There was a problem hiding this comment.
New chat/analyze functionality is introduced, but the test suite currently only covers health checks. Add FastAPI tests for these endpoints (e.g., that /intelligence/chat streams SSE and that analyze endpoints return the expected JSON shape / handle upstream failures) to prevent regressions and validate the error fallbacks.
| import anthropic | ||
|
|
||
| from app.config import settings | ||
| from app.services.conversation_store import store as conversation_store | ||
|
|
There was a problem hiding this comment.
The PR description mentions a Gemini-based service, but this implementation uses Anthropic (anthropic.AsyncAnthropic) and Claude models. Either update the PR description or align the implementation to the intended provider to avoid confusion for reviewers/operators configuring credentials.
| full_response = "" | ||
|
|
||
| try: | ||
| async with client.messages.stream( | ||
| model=MODEL, | ||
| max_tokens=CHAT_MAX_TOKENS, | ||
| system=SYSTEM_PROMPT, | ||
| messages=messages, | ||
| ) as stream: | ||
| async for text in stream.text_stream: | ||
| full_response += text | ||
| safe = text.replace("\n", "\\n") | ||
| yield f"data: {safe}\n\n" | ||
|
|
||
| conversation_store.append(user_id, "assistant", full_response) | ||
| yield "data: [DONE]\n\n" |
There was a problem hiding this comment.
full_response += text in a streaming loop repeatedly reallocates strings (O(n²) behavior). Even with token limits this is avoidable and can become costly if limits are raised. Accumulate chunks in a list and "".join(chunks) once at the end before storing in conversation_store.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 36 out of 38 changed files in this pull request and generated 9 comments.
Files not reviewed (1)
- apps/dapp/frontend/package-lock.json: Language not supported
Comments suppressed due to low confidence (3)
apps/dapp/frontend/app/offramp/page.tsx:13
- Importing Zod via
zod/v4here contributes to thezodResolver(... as any)workaround elsewhere because the schema types become module-distinct from@hookform/resolvers/zod’szodtypes. To keep forms type-safe, standardize onimport { z } from "zod"(or otherwise ensure a single Zod module identity across the app).
apps/dapp/frontend/app/offramp/page.tsx:125 zodResolver(formSchema as any)disables schema/type checking for the form. Once Zod imports are unified (avoid mixingzodvszod/v4), this should type-check withoutas any; removing the cast prevents silent mismatches betweenFormValuesand the schema.
apps/website/src/components/navbar.tsx:64- The
NEXT_PUBLIC_DAPP_URLfallback still points tohttp://localhost:3001. If this env var is missing in production, the button will regress to localhost again. Prefer defaulting to the production dapp URL and overriding to localhost only for local development via.env.local/env vars.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| animate={{ opacity: 1, y: 0, scale: 1 }} | ||
| exit={{ opacity: 0, y: 12, scale: 0.95 }} | ||
| transition={{ duration: 0.2 }} | ||
| className="flex w-85 flex-col overflow-hidden rounded-2xl border border-border bg-white shadow-2xl shadow-black/10" |
There was a problem hiding this comment.
Several class names here look like non-standard Tailwind utilities (w-85). If you’re not extending the spacing scale, Tailwind will drop these and the panel width won’t be applied. Prefer an existing width (e.g. w-80) or an arbitrary value (w-[340px]) to ensure the layout is deterministic.
| className="flex w-85 flex-col overflow-hidden rounded-2xl border border-border bg-white shadow-2xl shadow-black/10" | |
| className="flex w-[340px] flex-col overflow-hidden rounded-2xl border border-border bg-white shadow-2xl shadow-black/10" |
| </div> | ||
|
|
||
| {/* Messages */} | ||
| <div className="flex max-h-72 min-h-25 flex-col gap-3 overflow-y-auto p-4 scrollbar-thin"> |
There was a problem hiding this comment.
min-h-25 and scrollbar-thin are not default Tailwind utilities. Unless you’ve explicitly added them, these will be ignored and the chat layout/scroll styling won’t match what the code suggests. Consider using supported classes or arbitrary values (e.g. min-h-[6.25rem]) and define scrollbar styling in globals.css if desired.
| <div className="flex max-h-72 min-h-25 flex-col gap-3 overflow-y-auto p-4 scrollbar-thin"> | |
| <div className="flex max-h-72 min-h-[6.25rem] flex-col gap-3 overflow-y-auto p-4"> |
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
| resolver: zodResolver(formSchema as any), |
There was a problem hiding this comment.
Same concern as above: zodResolver(formSchema as any) drops validation/type guarantees. Align Zod imports so the schema type matches what @hookform/resolvers/zod expects and remove the as any cast.
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | |
| resolver: zodResolver(formSchema as any), | |
| resolver: zodResolver(formSchema), |
| - Price predictions or market speculation for any asset | ||
| - Other DeFi protocols not integrated with Nester | ||
| - General financial advice unrelated to Nester | ||
| - Anything outside personal savings and yield on the Nester platform | ||
|
|
||
| If asked something outside scope, respond with: |
There was a problem hiding this comment.
The system prompt forbids “market speculation”, but get_market_sentiment() asks the model to produce a bull/bear/neutral sentiment assessment. This conflict will likely cause refusals or non-JSON outputs (leading to the fallback response or JSON parsing errors). Either relax the system prompt to explicitly allow this endpoint’s scope or change the endpoint to request only platform-specific, non-speculative operational signals.
| CHAT_MAX_TOKENS = 1024 | ||
| ANALYZE_MAX_TOKENS = 800 | ||
|
|
There was a problem hiding this comment.
The model name is hard-coded (MODEL = "claude-sonnet-4-6"), which makes deployments brittle if the model ID changes or differs between environments. Prefer reading the model from configuration (e.g. settings) with a sensible default and validating it at startup so failures are clearer than generic runtime exceptions.
| </div> | ||
| <div className="mt-auto mb-8"> | ||
| <Link href="https://nesterdapp.netlify.app" target="_blank" className="w-full"> | ||
| <Link href={process.env.NEXT_PUBLIC_DAPP_URL ?? "http://localhost:3001"} target="_blank" className="w-full"> | ||
| <Button className="w-full bg-[#0D0E1C] hover:bg-[#0D0E1C]/90 text-white rounded-full"> | ||
| Nester for Web | ||
| </Button> |
There was a problem hiding this comment.
Same as the desktop link: the mobile CTA falls back to http://localhost:3001 when NEXT_PUBLIC_DAPP_URL isn’t defined, which can reintroduce the broken link in production. Default to the production dapp URL and make localhost an explicit dev-only override.
| const nextConfig: NextConfig = { | ||
| /* config options here */ | ||
| env: { | ||
| NEXT_PUBLIC_DAPP_URL: | ||
| process.env.NEXT_PUBLIC_DAPP_URL ?? | ||
| (process.env.NETLIFY ? "https://nesterdapp.netlify.app" : "http://localhost:3001"), | ||
| }, | ||
| }; |
There was a problem hiding this comment.
This config sets NEXT_PUBLIC_DAPP_URL to localhost whenever NEXTLIFY isn’t set and the env var isn’t provided. That makes the website’s “Nester for Web” button fragile outside Netlify. Safer default is the production URL, with localhost only when NODE_ENV === "development" (or via explicit env).
| [build] | ||
| base = "apps/dapp/frontend" | ||
| command = "npm run build" | ||
| publish = ".next" | ||
|
|
||
| [build.environment] | ||
| NODE_VERSION = "22" |
There was a problem hiding this comment.
Repository tooling pins Node 20 via .nvmrc, but this Netlify build sets NODE_VERSION = "22". That mismatch can cause “works locally but fails on CI/Netlify” issues, especially with lockfile/Next.js tooling. Consider aligning this with the repo’s pinned Node version (or update .nvmrc/CI to match 22 if that’s the new standard).
| {/* Stats */} | ||
| <Suspense> | ||
| <StatsBarWrapper /> | ||
| </Suspense> | ||
|
|
There was a problem hiding this comment.
<Suspense> requires a fallback prop (and omitting it is a TS/React type error). As written, this page is likely to fail type-check/build. Add explicit fallbacks (even null) for each Suspense boundary, e.g. <Suspense fallback={null}>…</Suspense> or a skeleton component.
…riendly scope response
…x Savings vault - Add TransferModal for moving funds between vaults with full sign/submit flow - Wire Transfer + Withdraw buttons into portfolio positions section - Add Flex Savings vault definition (no lock, 4-6% APY, USDC+XLM) - Add supportedAssets to all vault definitions; deposit modal shows asset toggle - Add recordTransfer to PortfolioProvider (burns source shares, creates new position) - Prometheus chatbot: hide scrollbar, render **bold** markdown, typing dots animation, paragraph spacing
…er, UI overhaul - Redesign vaults as token markets (USDC, XLM, XLM/USDC pair, indexes) with TVL, utilization bars, and market-type filtering - Add strategy sub-layer to markets (lending, optimized yield, leveraged, concentrated LP, etc.) with risk badges in deposit modal - Create Stocks page with tokenized equities, ETFs, and commodities - Add AppShell component with persistent sidebar navigation across all pages - Restore notification bell and disconnect wallet dropdown in TopBar - Remove search bar from TopBar - Add Info tooltips explaining TVL, APY, utilization across market pages - Fix withdraw modal: correct contract selection per asset (XLM vs USDC), display position asset instead of hardcoded USDC, refresh balances after withdrawal - Switch dashboard/portfolio to use on-chain withdraw modal - Remove minimum deposit criteria from savings strategies - Fix Inter font loading, add custom dash-border CSS utility - Reorder sidebar: Dashboard > Savings > Markets > Stocks > Offramp > Portfolio - Update vault detail page with utilization breakdown and strategies section - Soroban contracts: fix cross-contract imports with contractimport! macro, update deploy script for stellar-cli v25
- Switch withdraw back to working mock flow (vault-action-modals) - Rewrite portfolio page for cleaner UX (~380 lines vs ~1027) - Override --font-mono to Inter (eliminates Consolas everywhere) - Replace font-bold with font-medium on chart labels and table headers
- Redesign WithdrawModal as single-column scrollable layout with Confirm Withdrawal button always visible - Add shared PositionCards component with withdraw support - Show open positions on Savings, Markets, and Stocks pages - Dashboard already had positions with withdraw
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 55 out of 58 changed files in this pull request and generated 11 comments.
Files not reviewed (1)
- apps/dapp/frontend/package-lock.json: Language not supported
Comments suppressed due to low confidence (2)
apps/dapp/frontend/components/vault/depositModal.tsx:263
recordDepositpersistsapyandlockDaysfrommeta(derived fromvault.currentApy/maturityTerms) even when the user selects a specific strategy with its own APY/lock/penalty. This will make portfolio yield/maturity calculations inconsistent with the UI selection. Persist the selected strategy’s APY/lockDays/penalty values whenselectedStrategyis set.
recordDeposit({
vault: {
id: vault.id,
name: vault.name,
asset: selectedAsset,
apy: meta?.apy || 0,
lockDays: meta?.lockDays || 0,
earlyWithdrawalPenaltyPct: 0.1,
},
apps/dapp/frontend/app/offramp/page.tsx:125
zodResolver(formSchema as any)drops type safety and usually indicates an incompatibility between@hookform/resolvers/zodand the Zod import. Prefer aligning versions sozodResolver(formSchema)type-checks (e.g., ensure the resolver supports Zod v4, or import Zod in a way the resolver expects) instead of casting toany.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| mod vault_token { | ||
| soroban_sdk::contractimport!( | ||
| file = "../../target/wasm32-unknown-unknown/release/vault_token.wasm" | ||
| ); | ||
| } | ||
| use vault_token::Client as VaultTokenContractClient; |
There was a problem hiding this comment.
Using contractimport! with a relative ../../target/.../vault_token.wasm path is very likely incorrect from contracts/vault/src (it resolves under contracts/contracts/target, not the workspace target). It also makes vault-contract builds depend on a prebuilt WASM file that won’t exist in clean/CI builds since vault-token is no longer a Cargo dependency. Prefer restoring the vault-token-contract dependency/client (or add a build step that guarantees the WASM exists) and fix the path if contractimport! is kept.
| pub fn initialize(env: Env, vault: Address, name: String, symbol: String, decimals: u32) { | ||
| if env.storage().instance().has(&DataKey::Vault) { | ||
| panic_with_error!(&env, ContractError::AlreadyInitialized); | ||
| } | ||
| vault.require_auth(); | ||
| // No vault.require_auth() here — vault isn't initialized yet when this is called. | ||
| // Auth for vault-only operations is enforced in mint_for_deposit, burn_for_withdrawal, etc. | ||
| env.storage().instance().set(&DataKey::Vault, &vault); | ||
| env.storage().instance().set(&DataKey::Name, &name); | ||
| env.storage().instance().set(&DataKey::Symbol, &symbol); |
There was a problem hiding this comment.
Removing vault.require_auth() makes initialize() permissionless. On-chain, that allows anyone to front-run initialization after deployment and set Vault to an attacker-controlled address, which would let them pass require_vault() and mint/burn shares. Consider requiring auth from a trusted initializer (e.g., an admin argument that must require_auth(), or env.invoker().require_auth() if appropriate) while still allowing the vault address to be set before the vault itself is initialized.
| # Secret key passed directly — write a temp identity file for stellar-cli v25 | ||
| IDENTITY_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/stellar/identity" | ||
| mkdir -p "$IDENTITY_DIR" | ||
| printf 'secret_key = "%s"\n' "$DEPLOYER_SECRET" > "$IDENTITY_DIR/_nester_deploy.toml" | ||
| SOURCE_ACCOUNT="_nester_deploy" | ||
| DEPLOYER=$(stellar keys address _nester_deploy 2>/dev/null) | ||
| fi |
There was a problem hiding this comment.
The script writes the deployer secret into $IDENTITY_DIR/_nester_deploy.toml but never removes it, even though the comment calls it “temp”. This leaves a plaintext secret key on disk after deployment. Add a trap cleanup (and/or avoid writing the secret by requiring a key alias) so the file is removed on success/failure.
| # Nester Testnet Contract IDs — generated by deploy-testnet.sh | ||
| # Copy these into apps/dapp/frontend/.env.local | ||
|
|
||
| NEXT_PUBLIC_VAULT_CONTRACT_ID=CBYJXQUCJ475OREU4TQGGPYFC4XX2EW7FR5XNNR5X2MH3GQJLOTIT5YL | ||
| NEXT_PUBLIC_VAULT_XLM_CONTRACT_ID=CAQUVMTUGONBIUUXKUP3ANIOXBVLSQNXOEP2P5AWUJIM3XMH3NADZDKR | ||
| NEXT_PUBLIC_VAULT_TOKEN_CONTRACT_ID=CBJ2N5PJXZ47SEZ4WLGI5KINKY3NVIMXOUA62VNRGBHS3QZDFIUIONGM |
There was a problem hiding this comment.
This looks like a generated output file (and is overwritten by deploy-testnet.sh). Committing it will cause noisy diffs and accidental conflicts. Consider committing an example template instead (e.g., deployed-testnet.example.env) and adding scripts/deployed-testnet.env to packages/contracts/.gitignore.
| const [selectedAsset, setSelectedAsset] = useState<"USDC" | "XLM">( | ||
| (vault?.supportedAssets?.[0] as "USDC" | "XLM") ?? "USDC" | ||
| ); | ||
| const [selectedStrategy, setSelectedStrategy] = useState<MarketStrategy | null>( | ||
| vault?.strategies?.[0] ?? null | ||
| ); | ||
|
|
||
| // Keep selectedAsset and strategy in sync when vault changes | ||
| const supportedAssets = (vault?.supportedAssets ?? ["USDC"]) as ("USDC" | "XLM")[]; | ||
| const strategies = vault?.strategies ?? []; | ||
|
|
||
| // Reset strategy when vault changes | ||
| if (vault && selectedStrategy && !strategies.find(s => s.id === selectedStrategy.id)) { | ||
| setSelectedStrategy(strategies[0] ?? null); | ||
| } |
There was a problem hiding this comment.
setSelectedStrategy(...) is called during render when the current strategy isn’t found. Updating state during render can cause React warnings and render loops. Move this “reset on vault change” logic into a useEffect that runs when vault/strategies changes, and also reset selectedAsset when the vault changes to avoid carrying an unsupported selection across vaults.
| <motion.div | ||
| initial={{ opacity: 0, y: 12, scale: 0.95 }} | ||
| animate={{ opacity: 1, y: 0, scale: 1 }} | ||
| exit={{ opacity: 0, y: 12, scale: 0.95 }} | ||
| transition={{ duration: 0.2 }} | ||
| className="flex w-85 flex-col overflow-hidden rounded-2xl border border-border bg-white shadow-2xl shadow-black/10" | ||
| > |
There was a problem hiding this comment.
Classes like w-85, min-h-25, and h-13 w-13 are not default Tailwind utilities. Without extending the theme scale, these will be ignored and the chatbot layout will break. Replace them with arbitrary values (e.g., w-[340px], min-h-[100px], h-[52px]) or with existing scale classes.
| <div className="relative" onMouseEnter={() => setShow(true)} onMouseLeave={() => setShow(false)}> | ||
| <button | ||
| className="flex h-4 w-4 items-center justify-center rounded-full border border-black/12 text-black/30 hover:border-black/25 hover:text-black/55 transition-colors" | ||
| tabIndex={-1} | ||
| > | ||
| <Info className="h-2.5 w-2.5" /> | ||
| </button> |
There was a problem hiding this comment.
The tooltip trigger is tabIndex={-1} and only shows on mouse hover, which makes the info content inaccessible to keyboard and assistive tech users. Make the button focusable and show the tooltip on focus/blur as well (and add an aria-label / aria-describedby relationship).
| if (positions.length === 0) return null; | ||
|
|
There was a problem hiding this comment.
When positions.length === 0 this component returns null, so emptyLabel/emptyHint are never rendered despite being accepted props. Either render an empty state using those props, or remove the props to avoid a misleading API.
| if (positions.length === 0) return null; | |
| if (positions.length === 0) { | |
| return ( | |
| <> | |
| <div className="rounded-2xl border border-black/8 bg-white px-5 py-6 text-center"> | |
| <p className="text-sm text-black">{emptyLabel}</p> | |
| <p className="mt-1 text-xs text-black/35">{emptyHint}</p> | |
| </div> | |
| <WithdrawModal | |
| open={!!withdrawPos} | |
| onClose={() => setWithdrawPos(null)} | |
| position={withdrawPos} | |
| /> | |
| </> | |
| ); | |
| } |
| import Image from "next/image"; | ||
| import { motion } from "framer-motion"; | ||
| import { useState } from "react"; | ||
| import { ArrowLeft, TrendingUp, Layers, BarChart3, Info } from "lucide-react"; |
There was a problem hiding this comment.
Layers and BarChart3 are imported but never used, which will typically fail eslint(no-unused-vars) in Next/TS projects. Remove the unused imports (or use them) to keep CI/lint clean.
| import { ArrowLeft, TrendingUp, Layers, BarChart3, Info } from "lucide-react"; | |
| import { ArrowLeft, TrendingUp, Info } from "lucide-react"; |
| import { useEffect, useState } from "react"; | ||
| import { useRouter } from "next/navigation"; | ||
| import Image from "next/image"; | ||
| import { motion, AnimatePresence } from "framer-motion"; | ||
| import { cn } from "@/lib/utils"; | ||
| import { AppShell } from "@/components/app-shell"; | ||
| import { PositionCards } from "@/components/position-cards"; | ||
| import { useWallet } from "@/components/wallet-provider"; | ||
| import { usePortfolio } from "@/components/portfolio-provider"; | ||
| import { | ||
| ArrowUpRight, | ||
| ArrowDownRight, | ||
| TrendingUp, | ||
| Search, | ||
| X, | ||
| Info, | ||
| } from "lucide-react"; |
There was a problem hiding this comment.
Image, TrendingUp, and Info are imported but not used anywhere in this file. With standard lint rules this will fail CI and also adds noise. Remove unused imports to keep the module clean.
…emove unused token-icons
The vault contract uses contractimport! macro reading ../../target/wasm32-unknown-unknown/release/vault_token.wasm, so the WASM must exist before 'cargo test --lib' runs.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 55 out of 58 changed files in this pull request and generated 3 comments.
Files not reviewed (1)
- apps/dapp/frontend/package-lock.json: Language not supported
Comments suppressed due to low confidence (1)
apps/dapp/frontend/app/offramp/page.tsx:13
- Importing
zfrom"zod/v4"forces azodResolver(formSchema as any)cast, which drops validation type-safety and can hide schema/type mismatches. Since the project already depends onzod@^4, prefer importing from"zod"and removing theas any(or upgrade@hookform/resolvers/types sozodResolver(formSchema)type-checks cleanly).
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| [build] | ||
| base = "apps/dapp/frontend" | ||
| command = "npm run build" | ||
| publish = ".next" |
There was a problem hiding this comment.
Root-level netlify.toml applies to any Netlify site building from this repo. With base = "apps/dapp/frontend", it will force Netlify builds to publish the dapp instead of the marketing website unless the site overrides build settings. If both the website and dapp are deployed from this repo, consider using Netlify contexts (or separate Netlify sites with explicit build settings) to avoid breaking the website deploy.
| </div> | ||
|
|
||
| {/* Messages */} | ||
| <div className="flex max-h-72 min-h-25 flex-col gap-3 overflow-y-auto p-4 [scrollbar-width:none] [&::-webkit-scrollbar]:hidden"> |
There was a problem hiding this comment.
min-h-25 is not a default Tailwind min-height utility (and max-h-72 is fine). If the goal is a 6.25rem min height, use an existing scale value or an arbitrary value (e.g. min-h-[6.25rem]) so the chat area doesn’t collapse unexpectedly.
| <div className="flex max-h-72 min-h-25 flex-col gap-3 overflow-y-auto p-4 [scrollbar-width:none] [&::-webkit-scrollbar]:hidden"> | |
| <div className="flex max-h-72 min-h-[6.25rem] flex-col gap-3 overflow-y-auto p-4 [scrollbar-width:none] [&::-webkit-scrollbar]:hidden"> |
| <p className="flex items-center justify-center gap-2 text-sm font-medium text-amber-700"> | ||
| <AlertTriangle className="h-4 w-4" /> | ||
| ⚠️ You are on Testnet — tokens have no real value | ||
| <div className="fixed top-0 left-0 right-0 z-60 flex h-10 items-center justify-center border-b border-yellow-200 bg-black px-4"> |
There was a problem hiding this comment.
z-60 is not part of Tailwind’s default z-index scale (default tops out at z-50). If you need this banner above other fixed elements, use z-50/z-40 or an arbitrary value like z-[60] so the class is actually generated.
| <div className="fixed top-0 left-0 right-0 z-60 flex h-10 items-center justify-center border-b border-yellow-200 bg-black px-4"> | |
| <div className="fixed top-0 left-0 right-0 z-[60] flex h-10 items-center justify-center border-b border-yellow-200 bg-black px-4"> |
- Add vault-token-contract as dev-dependency so vault tests can import VaultTokenContract directly (E0432 unresolved import in test.rs) - Remove unused 'vec' import from allocation_strategy lib - Make elided lifetimes explicit in vault_token and yield_registry tests
Overview
Comprehensive cleanup of the Nester dapp, website, and intelligence service. This branch addresses design debt, broken routing, dead code, and CI failures accumulated since the last release wave.
Linked Issues
Closes #218 — Redesign Portfolio page with Jupiter-inspired layout
Closes #219 — Expand Savings page with multiple vault types
Closes #220 — Redesign Vaults as Markets with TVL, utilization, and strategy sub-layer
Closes #221 — Flatten /dashboard/* routes to top-level
Closes #222 — Add mobile hamburger navigation drawer
Closes #223 — Build Prometheus AI intelligence service
Closes #224 — Add tokenized stocks page with nSPY, nQQQ, nAAPL etc.
Closes #225 — Fix website "Nester for Web" button pointing to localhost
Closes #226 — Implement withdraw flow with working mock signature + local ledger
Closes #227 — Add custom 404 not-found page
Dapp — Frontend
Portfolio page
Savings page
Vaults (Markets) page
Stocks page (new)
Withdraw flow
Route restructuring
Navbar
Custom 404 page
Fonts
Website
Intelligence service (Python)
CI
All checks passing: Intelligence (Python), Dapp Frontend (Next.js), Dapp Backend (Node.js), API (Go), Internal (Go), Contracts (Rust), Website (Next.js).