Skip to content

Drips wave cleanup#208

Merged
0xDeon merged 41 commits intomainfrom
deon-cleanup
Apr 11, 2026
Merged

Drips wave cleanup#208
0xDeon merged 41 commits intomainfrom
deon-cleanup

Conversation

@0xDeon
Copy link
Copy Markdown
Contributor

@0xDeon 0xDeon commented Apr 1, 2026

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

  • Full redesign with a Jupiter-inspired layout: net worth hero, wallet pills, Positions / Activity tabs, flat position cards with withdraw buttons
  • Monochromatic B/W design with font-mono for all numeric values

Savings page

  • Replaced the single savings vault entry with four distinct vault types: Flexible, Auto-Compound, Stablecoin Yield, and Custom Goal
  • Hover info tooltips replace the old compare table
  • Deposit modal: wider two-column layout, quick-amount chips, hidden number spinners
  • Strategy selector inside deposit modal

Vaults (Markets) page

  • Redesigned as token markets with MarketType (single | pair | index)
  • TVL, utilization, APY range per market
  • Strategies become a sub-layer inside each market
  • Filter tabs + sort (TVL, APY, Utilization)
  • Assets restricted to USDC and XLM only
  • Info tooltips on all metrics

Stocks page (new)

  • Seven tokenized assets: nSPY, nQQQ, nAAPL, nTSLA, nMSFT, nGLD, nDIV
  • Category filters, search, buy modal

Withdraw flow

  • Fixed never-working withdraw — switched to mock simulation flow with local ledger
  • Scrollable single-column modal with confirm button always visible
  • Works from dashboard, portfolio, and vault detail pages

Route restructuring

  • All nested /dashboard/* sub-routes promoted to top-level
  • Settlements renamed to Offramp everywhere

Navbar

  • Mobile hamburger menu with animated slide-down drawer, backdrop, body scroll lock
  • Drawer closes on route change

Custom 404 page

  • New app/not-found.tsx: Nester logo, large faint 404, Home + Dashboard CTAs

Fonts

  • All font-mono overridden to use Inter via --font-mono CSS variable (eliminates Consolas)
  • font-bold replaced with font-medium on numeric values and chart labels

Website

  • Fixed Nester for Web button — now reads from NEXT_PUBLIC_DAPP_URL env var (Netlify-aware)

Intelligence service (Python)

  • New prometheus.py service: streaming Claude chat, portfolio insights, market sentiment, vault recommendations
  • New conversation_store.py: in-memory per-user chat history with TTL eviction
  • New analyze.py router for structured analysis endpoints
  • Migrated from Gemini to Claude API (claude-sonnet-4-6)
  • Fixed all ruff lint errors and mypy type errors

CI

All checks passing: Intelligence (Python), Dapp Frontend (Next.js), Dapp Backend (Node.js), API (Go), Internal (Go), Contracts (Rust), Website (Next.js).

@0xDeon 0xDeon changed the title Deon cleanup Drips wave cleanup Apr 3, 2026
@netlify
Copy link
Copy Markdown

netlify bot commented Apr 3, 2026

Deploy Preview for nesterdapp canceled.

Name Link
🔨 Latest commit 6b1afc4
🔍 Latest deploy log https://app.netlify.com/projects/nesterdapp/deploys/69da434087f7a00007f52135

@0xDeon 0xDeon marked this pull request as ready for review April 3, 2026 08:29
Copilot AI review requested due to automatic review settings April 3, 2026 08:29
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

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 Link opens a new tab but is missing rel="noopener noreferrer", which is recommended to prevent reverse-tabnabbing.
    apps/website/src/components/navbar.tsx:94
  • This Link opens a new tab but is missing rel="noopener noreferrer", which is recommended to prevent reverse-tabnabbing.
    apps/dapp/frontend/app/offramp/page.tsx:125
  • Casting the Zod schema to any for zodResolver drops type-safety and can mask real validation/type issues. Prefer using compatible Zod types (e.g., import from zod instead of zod/v4 if needed) or upgrade/downgrade @hookform/resolvers so zodResolver(formSchema) type-checks without any.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread apps/intelligence/app/services/conversation_store.py
Comment thread apps/intelligence/app/routers/chat.py
Comment thread apps/intelligence/app/routers/chat.py
Comment thread apps/intelligence/app/routers/analyze.py
Comment thread apps/dapp/frontend/components/vault-action-modals.tsx
Comment thread apps/dapp/frontend/app/portfolio/page.tsx Outdated
Comment thread apps/dapp/frontend/components/ai/prometheusChatbot.tsx Outdated
Comment thread apps/dapp/frontend/lib/mock-vaults.ts Outdated
Comment thread apps/dapp/frontend/app/savings/page.tsx
Comment thread apps/dapp/frontend/lib/token-icons.ts Outdated
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

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 from zod/v4 but the resolver types reference zod. Prefer importing Zod from zod consistently and remove the as any cast.
    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 using target="_blank".
    apps/website/src/components/navbar.tsx:94
  • Same issue here: target="_blank" should be paired with rel="noopener noreferrer" for external links.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread apps/intelligence/app/services/conversation_store.py
Comment thread apps/dapp/frontend/components/navbar.tsx
Comment thread apps/dapp/frontend/components/navbar.tsx
Comment thread apps/dapp/frontend/components/network/NetworkSelector.tsx Outdated
Comment thread apps/dapp/frontend/components/ai/prometheusChatbot.tsx
Comment thread apps/dapp/frontend/app/vaults/page.tsx
Comment thread apps/dapp/frontend/app/savings/page.tsx
Comment thread apps/dapp/frontend/app/savings/page.tsx
Comment thread apps/dapp/frontend/lib/validation.ts
Comment thread apps/dapp/frontend/app/vaults/[id]/page.tsx
Copilot AI review requested due to automatic review settings April 8, 2026 12:03
Copilot AI review requested due to automatic review settings April 8, 2026 12:53
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

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 uses zodResolver(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 the as any so zodResolver(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";
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

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

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.

Suggested change
import { z } from "zod/v4";
import { z } from "zod";

Copilot uses AI. Check for mistakes.
Comment on lines 143 to 148
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: "" }
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

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

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.

Copilot uses AI. Check for mistakes.
Comment on lines +133 to +139
<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>
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

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

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.

Copilot uses AI. Check for mistakes.
Comment on lines +17 to +22
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] = {}

Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

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

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.

Copilot uses AI. Check for mistakes.
Comment on lines +11 to +15
@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:
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

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

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.

Copilot uses AI. Check for mistakes.
Comment on lines +11 to +23
@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),
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

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

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.

Copilot uses AI. Check for mistakes.
Comment on lines +8 to +12
import anthropic

from app.config import settings
from app.services.conversation_store import store as conversation_store

Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

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

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.

Copilot uses AI. Check for mistakes.
Comment on lines +113 to +128
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"
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

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

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.

Copilot uses AI. Check for mistakes.
Copilot AI review requested due to automatic review settings April 8, 2026 13:25
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

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/v4 here contributes to the zodResolver(... as any) workaround elsewhere because the schema types become module-distinct from @hookform/resolvers/zod’s zod types. To keep forms type-safe, standardize on import { 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 mixing zod vs zod/v4), this should type-check without as any; removing the cast prevents silent mismatches between FormValues and the schema.
    apps/website/src/components/navbar.tsx:64
  • The NEXT_PUBLIC_DAPP_URL fallback still points to http://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"
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

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

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.

Suggested change
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"

Copilot uses AI. Check for mistakes.
</div>

{/* Messages */}
<div className="flex max-h-72 min-h-25 flex-col gap-3 overflow-y-auto p-4 scrollbar-thin">
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

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

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.

Suggested change
<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">

Copilot uses AI. Check for mistakes.
Comment on lines +537 to +538
// eslint-disable-next-line @typescript-eslint/no-explicit-any
resolver: zodResolver(formSchema as any),
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

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

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.

Suggested change
// eslint-disable-next-line @typescript-eslint/no-explicit-any
resolver: zodResolver(formSchema as any),
resolver: zodResolver(formSchema),

Copilot uses AI. Check for mistakes.
Comment on lines +56 to +61
- 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:
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

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

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.

Copilot uses AI. Check for mistakes.
Comment on lines +15 to +17
CHAT_MAX_TOKENS = 1024
ANALYZE_MAX_TOKENS = 800

Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

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

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.

Copilot uses AI. Check for mistakes.
Comment on lines 88 to 93
</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>
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

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

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.

Copilot uses AI. Check for mistakes.
Comment on lines 3 to 9
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"),
},
};
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

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

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).

Copilot uses AI. Check for mistakes.
Comment thread netlify.toml
Comment on lines +1 to +7
[build]
base = "apps/dapp/frontend"
command = "npm run build"
publish = ".next"

[build.environment]
NODE_VERSION = "22"
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

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

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).

Copilot uses AI. Check for mistakes.
Comment on lines +378 to +382
{/* Stats */}
<Suspense>
<StatsBarWrapper />
</Suspense>

Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

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

<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.

Copilot uses AI. Check for mistakes.
0xDeon added 8 commits April 8, 2026 14:42
…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
Copilot AI review requested due to automatic review settings April 9, 2026 11:58
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

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

  • recordDeposit persists apy and lockDays from meta (derived from vault.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 when selectedStrategy is 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/zod and the Zod import. Prefer aligning versions so zodResolver(formSchema) type-checks (e.g., ensure the resolver supports Zod v4, or import Zod in a way the resolver expects) instead of casting to any.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +7 to +12
mod vault_token {
soroban_sdk::contractimport!(
file = "../../target/wasm32-unknown-unknown/release/vault_token.wasm"
);
}
use vault_token::Client as VaultTokenContractClient;
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

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

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.

Copilot uses AI. Check for mistakes.
Comment on lines 77 to 85
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);
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

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

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.

Copilot uses AI. Check for mistakes.
Comment on lines +64 to +70
# 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
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

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

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.

Copilot uses AI. Check for mistakes.
Comment on lines +1 to +6
# 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
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

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

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.

Copilot uses AI. Check for mistakes.
Comment on lines +186 to +200
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);
}
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

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

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.

Copilot uses AI. Check for mistakes.
Comment on lines +160 to +166
<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"
>
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

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

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.

Copilot uses AI. Check for mistakes.
Comment on lines +28 to +34
<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>
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

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

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).

Copilot uses AI. Check for mistakes.
Comment on lines +23 to +24
if (positions.length === 0) return null;

Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

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

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.

Suggested change
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}
/>
</>
);
}

Copilot uses AI. Check for mistakes.
import Image from "next/image";
import { motion } from "framer-motion";
import { useState } from "react";
import { ArrowLeft, TrendingUp, Layers, BarChart3, Info } from "lucide-react";
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

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

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.

Suggested change
import { ArrowLeft, TrendingUp, Layers, BarChart3, Info } from "lucide-react";
import { ArrowLeft, TrendingUp, Info } from "lucide-react";

Copilot uses AI. Check for mistakes.
Comment on lines +3 to +19
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";
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

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

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.

Copilot uses AI. Check for mistakes.
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.
Copilot AI review requested due to automatic review settings April 10, 2026 14:54
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

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 z from "zod/v4" forces a zodResolver(formSchema as any) cast, which drops validation type-safety and can hide schema/type mismatches. Since the project already depends on zod@^4, prefer importing from "zod" and removing the as any (or upgrade @hookform/resolvers/types so zodResolver(formSchema) type-checks cleanly).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread netlify.toml
Comment on lines +1 to +4
[build]
base = "apps/dapp/frontend"
command = "npm run build"
publish = ".next"
Copy link

Copilot AI Apr 10, 2026

Choose a reason for hiding this comment

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

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.

Copilot uses AI. Check for mistakes.
</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">
Copy link

Copilot AI Apr 10, 2026

Choose a reason for hiding this comment

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

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.

Suggested change
<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">

Copilot uses AI. Check for mistakes.
<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">
Copy link

Copilot AI Apr 10, 2026

Choose a reason for hiding this comment

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

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.

Suggested change
<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">

Copilot uses AI. Check for mistakes.
- 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
@0xDeon 0xDeon merged commit 2b758d1 into main Apr 11, 2026
18 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment