-
Notifications
You must be signed in to change notification settings - Fork 3
feat: vault v2 setup page #162
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
461c173
c2c7c2e
de43ca6
00a41f0
d8e0041
a2985a9
929dd3e
f79e759
f8bb418
33b4b20
906f36c
cb99a60
e7e8255
ca87d15
0abba73
9fb2902
e8fca38
949b6c6
9768822
9c43219
d69fe24
5b389bb
2360f22
4730888
97180d1
fc5eb3e
4585612
5565754
ce75908
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| # Repository Guidelines | ||
|
|
||
| ## Project Structure & Module Organization | ||
| Next.js routes live in `app/`. Shared logic sits in `src/` with UI in `src/components/`, hooks in `src/hooks/`. Keep constants inside `src/constants/`, configuration in `src/config/`, and reusable utilities in `src/utils/`. Static assets stay in `public/`; design primitives reside in `src/fonts/` and `src/imgs/`. Scripts that generate on-chain artifacts live under `scripts/`, and longer form references or RFCs belong in `docs/`. | ||
|
|
||
| ## Build, Test, and Development Commands | ||
| - `pnpm install` — install dependencies; stick with pnpm for lockfile parity. | ||
| - `pnpm dev` — start the hot-reloading Next.js dev server. | ||
| - `pnpm build` — create a clean production bundle after wiping `.next`. | ||
| - `pnpm start` — run the production build locally when validating releases. | ||
| - `pnpm check` — run formatting, ESLint, and Stylelint fixers as a bundle. | ||
| - `pnpm lint` / `pnpm stylelint` — target React or CSS changes without the full suite. | ||
|
|
||
| ## Coding Style & Naming Conventions | ||
| Run `pnpm format` to apply the Prettier profile (100-char width, 2-space indent, single quotes, trailing commas, Tailwind-aware ordering). ESLint (Airbnb + Next.js) enforces hook safety and import hygiene; Stylelint keeps CSS utilities consistent. Use PascalCase for React components (`VaultBanner.tsx`), camelCase for helpers (`formatApr`), and SCREAMING_SNAKE_CASE for shared constants. Keep Tailwind classlists purposeful and lean; consolidate patterns with `tailwind-merge` helpers when they repeat. | ||
|
|
||
| ## Styling Discipline | ||
| Consult `docs/Styling.md` before touching UI. Always follow the documented design tokens, Tailwind composition patterns, and variant rules—no exceptions. Mirror the examples in that guide for component structure, prop naming, and class ordering so the design system stays coherent. When using the shared `Spinner` component, pass numeric pixel values (e.g. `size={12}`)—it does not accept semantic strings. | ||
|
|
||
| ## Implementation Mindset | ||
| Default to the simplest viable implementation first. Reach for straightforward data flows, avoid premature abstractions, and only layer on complexity when the trivial approach no longer meets requirements. | ||
|
|
||
| ## Function Organization & Separation of Concerns | ||
| Never define utility functions or business logic inside hooks, components, or classes. Extract them into dedicated utility files in `src/utils/`. This principle—often called **Single Responsibility Principle** or **Separation of Concerns**—keeps code testable, reusable, and maintainable. For example: | ||
| - ❌ Bad: Defining `readAllocation()` inside `useAllocations.ts` | ||
| - ✅ Good: Creating `src/utils/vaultAllocation.ts` with `readAllocation()`, `formatAllocationAmount()`, etc., then importing into the hook | ||
|
|
||
| Hooks should orchestrate effects and state; components should render UI; utilities should handle pure logic. Keep each layer focused on its single responsibility. | ||
|
|
||
| ## Git Ownership | ||
| Never run git commits, pushes, or other history-altering commands—leave all git operations to the maintainers. | ||
|
|
||
| ## Contract Interaction TL;DR | ||
| When writing new on-chain hooks, mirror the structure in `src/hooks/useERC20Approval.ts` and `src/hooks/useTransactionWithToast.tsx`: compute chain/address context up front, reuse `useTransactionWithToast` for consistent toast + confirmation handling, and expose a minimal hook surface (`{ action, isLoading }`) with refetch callbacks for follow-up reads. | ||
|
|
||
| ## Commit & Pull Request Guidelines | ||
| Mirror the Conventional Commits style in history (`feat:`, `fix:`, `chore:`), keeping messages imperative and scoped. Sync with `main`, run `pnpm check`, and capture UI evidence (screenshots or short clips) for anything user-facing. Reference the relevant Linear/Jira ticket with closing keywords, call out risk areas, and flag required follow-ups. Tag reviewers who understand the touched protocol surfaces to speed feedback. | ||
|
|
||
| ## Incident Log | ||
| - Autovault settings refactor: we unintentionally spammed the Morpho API because we passed fresh array literals (`defaultAllocatorAddresses`) into `useVaultV2Data`. That array was part of the hook’s memoised fetch dependencies, so every render produced a new reference, rebuilt the `useCallback`, and re-triggered the fetch effect. **Guardrail:** before handing arrays or objects to hooks that fire network requests, memoize the props (or pass a stable key) so React’s dependency checks only change when the underlying data truly changes. |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,171 @@ | ||||||||||||||||||||||||||||
| import React from 'react'; | ||||||||||||||||||||||||||||
| import { Cross1Icon } from '@radix-ui/react-icons'; | ||||||||||||||||||||||||||||
| import { Address } from 'viem'; | ||||||||||||||||||||||||||||
| import { useAccount } from 'wagmi'; | ||||||||||||||||||||||||||||
| import { Button } from '@/components/common'; | ||||||||||||||||||||||||||||
| import Input from '@/components/Input/Input'; | ||||||||||||||||||||||||||||
| import AccountConnect from '@/components/layout/header/AccountConnect'; | ||||||||||||||||||||||||||||
| import { TokenIcon } from '@/components/TokenIcon'; | ||||||||||||||||||||||||||||
| import { useLocalStorage } from '@/hooks/useLocalStorage'; | ||||||||||||||||||||||||||||
| import { useVaultV2Deposit } from '@/hooks/useVaultV2Deposit'; | ||||||||||||||||||||||||||||
| import { formatBalance } from '@/utils/balance'; | ||||||||||||||||||||||||||||
| import { VaultDepositProcessModal } from './VaultDepositProcessModal'; | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| type DepositToVaultModalProps = { | ||||||||||||||||||||||||||||
| vaultAddress: Address; | ||||||||||||||||||||||||||||
| vaultName: string; | ||||||||||||||||||||||||||||
| assetAddress: Address; | ||||||||||||||||||||||||||||
| assetSymbol: string; | ||||||||||||||||||||||||||||
| assetDecimals: number; | ||||||||||||||||||||||||||||
| chainId: number; | ||||||||||||||||||||||||||||
| onClose: () => void; | ||||||||||||||||||||||||||||
| onSuccess?: () => void; | ||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| export function DepositToVaultModal({ | ||||||||||||||||||||||||||||
| vaultAddress, | ||||||||||||||||||||||||||||
| vaultName, | ||||||||||||||||||||||||||||
| assetAddress, | ||||||||||||||||||||||||||||
| assetSymbol, | ||||||||||||||||||||||||||||
| assetDecimals, | ||||||||||||||||||||||||||||
| chainId, | ||||||||||||||||||||||||||||
| onClose, | ||||||||||||||||||||||||||||
| onSuccess, | ||||||||||||||||||||||||||||
| }: DepositToVaultModalProps): JSX.Element { | ||||||||||||||||||||||||||||
| const { isConnected } = useAccount(); | ||||||||||||||||||||||||||||
| const [usePermit2Setting] = useLocalStorage('usePermit2', true); | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| const { | ||||||||||||||||||||||||||||
| depositAmount, | ||||||||||||||||||||||||||||
| setDepositAmount, | ||||||||||||||||||||||||||||
| inputError, | ||||||||||||||||||||||||||||
| setInputError, | ||||||||||||||||||||||||||||
| tokenBalance, | ||||||||||||||||||||||||||||
| isApproved, | ||||||||||||||||||||||||||||
| permit2Authorized, | ||||||||||||||||||||||||||||
| isLoadingPermit2, | ||||||||||||||||||||||||||||
| depositPending, | ||||||||||||||||||||||||||||
| approveAndDeposit, | ||||||||||||||||||||||||||||
| signAndDeposit, | ||||||||||||||||||||||||||||
| showProcessModal, | ||||||||||||||||||||||||||||
| setShowProcessModal, | ||||||||||||||||||||||||||||
| currentStep, | ||||||||||||||||||||||||||||
| } = useVaultV2Deposit({ | ||||||||||||||||||||||||||||
| vaultAddress, | ||||||||||||||||||||||||||||
| assetAddress, | ||||||||||||||||||||||||||||
| assetSymbol, | ||||||||||||||||||||||||||||
| assetDecimals, | ||||||||||||||||||||||||||||
| chainId, | ||||||||||||||||||||||||||||
| vaultName, | ||||||||||||||||||||||||||||
| onSuccess, | ||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| return ( | ||||||||||||||||||||||||||||
| <> | ||||||||||||||||||||||||||||
| <div | ||||||||||||||||||||||||||||
| className="fixed inset-0 flex items-center justify-center bg-black/50" | ||||||||||||||||||||||||||||
| style={{ zIndex: 50 }} | ||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||
| <div className="bg-surface relative w-full max-w-lg rounded p-6"> | ||||||||||||||||||||||||||||
| <div className="flex flex-col"> | ||||||||||||||||||||||||||||
| <button | ||||||||||||||||||||||||||||
| type="button" | ||||||||||||||||||||||||||||
| className="absolute right-2 top-2 text-secondary opacity-60 transition-opacity hover:opacity-100" | ||||||||||||||||||||||||||||
| onClick={onClose} | ||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||
| <Cross1Icon /> | ||||||||||||||||||||||||||||
| </button> | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| <div className="mb-6 flex items-center justify-between"> | ||||||||||||||||||||||||||||
| <div className="flex flex-col"> | ||||||||||||||||||||||||||||
| <div className="flex items-center gap-2"> | ||||||||||||||||||||||||||||
| <TokenIcon | ||||||||||||||||||||||||||||
| address={assetAddress} | ||||||||||||||||||||||||||||
| chainId={chainId} | ||||||||||||||||||||||||||||
| symbol={assetSymbol} | ||||||||||||||||||||||||||||
| width={20} | ||||||||||||||||||||||||||||
| height={20} | ||||||||||||||||||||||||||||
| /> | ||||||||||||||||||||||||||||
|
Comment on lines
+82
to
+88
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Remove unsupported TokenIcon doesn't accept a <TokenIcon
address={assetAddress}
chainId={chainId}
- symbol={assetSymbol}
width={20}
height={20}
/>📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||
| <span className="text-2xl">Deposit {assetSymbol}</span> | ||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||
| <span className="mt-1 text-sm text-gray-400">Deposit to {vaultName}</span> | ||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| {!isConnected ? ( | ||||||||||||||||||||||||||||
| <div className="flex justify-center py-4"> | ||||||||||||||||||||||||||||
| <AccountConnect /> | ||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||
| ) : ( | ||||||||||||||||||||||||||||
| <> | ||||||||||||||||||||||||||||
| {/* Deposit Input Section */} | ||||||||||||||||||||||||||||
| <div className="mt-12 space-y-4"> | ||||||||||||||||||||||||||||
| <div> | ||||||||||||||||||||||||||||
| <div className="flex items-center justify-between"> | ||||||||||||||||||||||||||||
| <span className="opacity-80">Deposit amount</span> | ||||||||||||||||||||||||||||
| <p className="font-inter text-xs opacity-50"> | ||||||||||||||||||||||||||||
| Balance: {formatBalance(tokenBalance ?? BigInt(0), assetDecimals)}{' '} | ||||||||||||||||||||||||||||
| {assetSymbol} | ||||||||||||||||||||||||||||
| </p> | ||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| <div className="mt-2 flex items-start justify-between"> | ||||||||||||||||||||||||||||
| <div className="relative flex-grow"> | ||||||||||||||||||||||||||||
| <Input | ||||||||||||||||||||||||||||
| decimals={assetDecimals} | ||||||||||||||||||||||||||||
| max={tokenBalance ?? BigInt(0)} | ||||||||||||||||||||||||||||
| setValue={setDepositAmount} | ||||||||||||||||||||||||||||
| setError={setInputError} | ||||||||||||||||||||||||||||
| exceedMaxErrMessage="Insufficient Balance" | ||||||||||||||||||||||||||||
| /> | ||||||||||||||||||||||||||||
| {inputError && ( | ||||||||||||||||||||||||||||
| <p className="p-1 text-sm text-red-500 transition-opacity duration-200 ease-in-out"> | ||||||||||||||||||||||||||||
| {inputError} | ||||||||||||||||||||||||||||
| </p> | ||||||||||||||||||||||||||||
| )} | ||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| {!permit2Authorized || (!usePermit2Setting && !isApproved) ? ( | ||||||||||||||||||||||||||||
| <Button | ||||||||||||||||||||||||||||
| isDisabled={!isConnected || isLoadingPermit2 || depositPending} | ||||||||||||||||||||||||||||
| onPress={() => void approveAndDeposit()} | ||||||||||||||||||||||||||||
| className="ml-2 min-w-32" | ||||||||||||||||||||||||||||
| variant="cta" | ||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||
| Deposit | ||||||||||||||||||||||||||||
| </Button> | ||||||||||||||||||||||||||||
|
antoncoding marked this conversation as resolved.
|
||||||||||||||||||||||||||||
| ) : ( | ||||||||||||||||||||||||||||
| <Button | ||||||||||||||||||||||||||||
| isDisabled={ | ||||||||||||||||||||||||||||
| !isConnected || depositPending || inputError !== null || !depositAmount | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
| onPress={() => void signAndDeposit()} | ||||||||||||||||||||||||||||
| className="ml-2 min-w-32" | ||||||||||||||||||||||||||||
| variant="cta" | ||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||
| Deposit | ||||||||||||||||||||||||||||
| </Button> | ||||||||||||||||||||||||||||
| )} | ||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||
| </> | ||||||||||||||||||||||||||||
| )} | ||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| {showProcessModal && ( | ||||||||||||||||||||||||||||
| <VaultDepositProcessModal | ||||||||||||||||||||||||||||
| currentStep={currentStep} | ||||||||||||||||||||||||||||
| onClose={() => setShowProcessModal(false)} | ||||||||||||||||||||||||||||
| vaultName={vaultName} | ||||||||||||||||||||||||||||
| assetSymbol={assetSymbol} | ||||||||||||||||||||||||||||
| amount={depositAmount} | ||||||||||||||||||||||||||||
| assetDecimals={assetDecimals} | ||||||||||||||||||||||||||||
| usePermit2={usePermit2Setting} | ||||||||||||||||||||||||||||
| /> | ||||||||||||||||||||||||||||
| )} | ||||||||||||||||||||||||||||
| </> | ||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,90 @@ | ||
| import React, { useMemo, useState } from 'react'; | ||
| import { PlusIcon } from '@radix-ui/react-icons'; | ||
| import { Address } from 'viem'; | ||
| import { TokenIcon } from '@/components/TokenIcon'; | ||
| import { formatBalance } from '@/utils/balance'; | ||
| import { DepositToVaultModal } from './DepositToVaultModal'; | ||
|
|
||
| type VaultTotalAssetsCardProps = { | ||
| totalAssets?: bigint | ||
| tokenDecimals?: number; | ||
| tokenSymbol?: string; | ||
| assetAddress?: Address; | ||
| chainId: number; | ||
| vaultAddress: Address; | ||
| vaultName: string; | ||
| onRefresh?: () => void; | ||
| }; | ||
|
|
||
| export function TotalSupplyCard({ | ||
| tokenDecimals, | ||
| tokenSymbol, | ||
| assetAddress, | ||
| chainId, | ||
| vaultAddress, | ||
| vaultName, | ||
| totalAssets, | ||
| onRefresh, | ||
| }: VaultTotalAssetsCardProps): JSX.Element { | ||
| const [showDepositModal, setShowDepositModal] = useState(false); | ||
|
|
||
|
|
||
| const totalAssetsLabel = useMemo(() => { | ||
| if (totalAssets === undefined || tokenDecimals === undefined) return '--'; | ||
|
|
||
| try { | ||
| const numericAssets = formatBalance(totalAssets, tokenDecimals); | ||
| const formattedAssets = new Intl.NumberFormat('en-US', { | ||
| maximumFractionDigits: 2, | ||
| }).format(numericAssets); | ||
|
|
||
| return `${formattedAssets}${tokenSymbol ? ` ${tokenSymbol}` : ''}`.trim(); | ||
| } catch (_error) { | ||
| return '--'; | ||
| } | ||
| }, [tokenDecimals, tokenSymbol, totalAssets]); | ||
|
|
||
| const handleDepositSuccess = () => { | ||
| setShowDepositModal(false); | ||
| onRefresh?.(); | ||
| }; | ||
|
|
||
| return ( | ||
| <> | ||
| <div className="rounded bg-surface p-4 shadow-sm"> | ||
| <div className="flex items-center justify-between"> | ||
| <span className="text-xs uppercase tracking-wide text-secondary">Total Assets</span> | ||
| {assetAddress && tokenSymbol && tokenDecimals !== undefined && ( | ||
| <button | ||
| type="button" | ||
| onClick={() => setShowDepositModal(true)} | ||
| className="flex h-6 w-6 items-center justify-center rounded-full bg-primary/10 text-primary transition-colors hover:bg-primary/20" | ||
| aria-label="Deposit to vault" | ||
| > | ||
| <PlusIcon className="h-4 w-4" /> | ||
| </button> | ||
| )} | ||
| </div> | ||
| <div className="mt-3 flex items-center gap-2 text-base text-primary"> | ||
| <span className='text-base'>{totalAssetsLabel}</span> | ||
| {assetAddress && ( | ||
| <TokenIcon address={assetAddress} chainId={chainId} width={20} height={20} /> | ||
| )} | ||
| </div> | ||
| </div> | ||
|
|
||
| {showDepositModal && assetAddress && tokenSymbol && tokenDecimals !== undefined && ( | ||
| <DepositToVaultModal | ||
| vaultAddress={vaultAddress} | ||
| vaultName={vaultName} | ||
| assetAddress={assetAddress} | ||
| assetSymbol={tokenSymbol} | ||
| assetDecimals={tokenDecimals} | ||
| chainId={chainId} | ||
| onClose={() => setShowDepositModal(false)} | ||
| onSuccess={handleDepositSuccess} | ||
| /> | ||
| )} | ||
| </> | ||
| ); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove duplicate permission entry.
Line 22 duplicates the permission already defined on line 12.
"Bash(pnpm generate:chainlink:*)", - "Bash(pnpm lint:check:*)", "Bash(pnpm exec tsc:*)",📝 Committable suggestion
🤖 Prompt for AI Agents