Dynero — a minimal, modern Next.js App Router app that integrates NextAuth (Google), Prisma, and Solana tooling for token balances and swaps.
Prerequisites
- Node.js 18+ (tested with 18/20)
- pnpm (recommended) or npm/yarn
- A PostgreSQL (or supported) database for Prisma (local or remote)
Clone and install
git clone <your-repo> && cd dynero
pnpm install
# or: npm installGenerate Prisma client (required before build)
pnpm prisma generate
# or: npx prisma generateRun development server
pnpm dev
# or: npm run devThe app will be available at http://localhost:3000 by default.
This repository contains Dynero: a minimal Next.js app (App Router) that demonstrates a simple authenticated wallet experience with token balances and an integrated swap flow. It is intentionally compact — the code is organized to show key integration points rather than a full production product.
This README explains what the project is, the high-level architecture, security decisions, and the files you should open when you want to change behavior.
- Users sign in with Google (NextAuth). The app stores a minimal user record and associates wallet data.
- Authenticated users can view token balances for a supported set of Solana tokens and request a swap (quote via a price/quote API and then sign/submit the swap).
- The project demonstrates both client-side and server-side responsibilities: UI and session handling live in React components; signing and other sensitive operations are performed in server routes (or demonstrated as encrypted storage).
- Next.js (App Router) — UI and server routes live together under
app/. - NextAuth — authentication with OAuth providers (
app/api/auth/[...nextauth]/route.ts). Server-side callbacks enrich the session with DB-backed user info. - Prisma — database ORM (schemas and data access live where Prisma client is used). The DB holds user records, encrypted key material (examples), and convenience metadata.
- Solana tooling —
@solana/web3.jsand@solana/spl-tokenare used inlib/and server routes to fetch balances, build and/or sign transactions, and interact with the chain. - Sonner — UI toasts for client-side notifications (mounted in
app/layout.tsx).
Separation of responsibilities
- Client components (React): render UI, collect user intent, request quotes, show balances.
- Server routes (under
app/api/): perform sensitive operations (signing with server-side keys if you choose a custodial model), return structured JSON, and enforce validations.
-
Authentication
- User clicks sign-in in
components/Appbar.tsx. - NextAuth handles the OAuth dance;
route.tsin the auth folder contains callbacks (signIn/jwt/session) that map provider fields into the app's user model.
- User clicks sign-in in
-
Balance display
- The app fetches token balances (server helper or client hook) and renders them in
components/Wallet.tsx. - Token metadata and supported tokens are declared in
lib/constants.ts/lib/token.tsso the UI and price lookups use a single source of truth.
- The app fetches token balances (server helper or client hook) and renders them in
-
Pricing & quoting
- The Swap UI (
components/Swap.tsx+components/SwapHelper.tsx) requests a quote from an external price/quote API (e.g., Jupiter). Quote responses are normalized and stored in component state asquoteResponse.
- The Swap UI (
-
Swap execution
- The client POSTs the
quoteResponse(or the prepared tx) toapp/api/swap/route.ts. - The server validates the request, constructs or signs the transaction (if custodial), submits to Solana, and returns a structured JSON success/failure.
- The client POSTs the
Notes
- The repo includes example code for both client-side and server-side signing patterns; choose one and remove the other for production clarity.
- Users: minimal profile created via NextAuth callbacks; provider identifiers are saved for audit and correlation.
- Wallets / Keys: examples in the code previously saved raw key arrays; the project now demonstrates an encrypted-storage approach (AES-GCM) as a short-term pattern. See
lib/utils.tsfor helper references.
Security note (important)
- Do not store raw private keys in plaintext. Prefer a non-custodial UX (let users keep keys in their browser wallet). If you must store keys, use envelope encryption with a KMS-managed key in production.
-
Authentication
app/api/auth/[...nextauth]/route.ts— provider config and callbacks.
-
UI / Client
app/layout.tsx— root layout, fonts, Toaster placement, head metadata.components/Appbar.tsx— top navigation and authentication controls.components/Wallet.tsx— balance UI, copy-to-clipboard, and loading states.components/Swap.tsx&components/SwapHelper.tsx— inputs, token selection and quote handling.
-
Server / API
app/api/swap/route.ts— validate and execute swap requests. Make sure every error path returns JSON with an HTTP status.app/api/*— other server endpoints used for token list, prices, or helper endpoints.
-
Utilities
lib/utils.ts— Solana helper functions, encryption examples, and wrappers.lib/constants.ts/lib/token.ts— supported token metadata and price lookup keys.
- Client vs server boundaries: calling browser-only APIs (toasts, window, localStorage) from server components will fail — keep toasts and DOM interactions in client components.
- NextAuth callbacks:
signInandjwt/sessioncallbacks receive different payloads; only thesignIncallback receives theaccountobject with provider IDs on the first sign-in. - External API contracts: price APIs often use mint addresses as keys. If your UI expects symbol-named keys you'll get
undefinedprices. - Always return structured JSON from server routes. Empty responses or thrown exceptions cause opaque 500s on the client.
- Clarity: the App Router keeps server routes and UI colocated, making it easier to reason about which code runs where.
- Security-first examples: the repo intentionally surfaces the dangers of storing key material and provides a simple encrypted example so contributors can safely experiment.
- Minimalism: this repo is a focused sandbox for auth + wallet + swap patterns — it avoids framework bloat so developers can iterate quickly.
- Decide on the signing model (non-custodial vs custodial) and consolidate code paths accordingly.
- If you keep custody, migrate encryption to a KMS-backed flow and store only ciphertext + metadata in the DB.
- Add tests for
app/api/swap/route.tsthat mock quote responses and validate error paths return JSON.
If you want, I can now:
- generate a
docs/SECURITY.mdwith a migration plan to KMS, including a suggested DB schema for storing ciphertext + iv + tag + key version, or - add example non-custodial client-side signing code (Phantom + signTransaction) to replace server-side signing examples.