Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions context/progress-tracker.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,20 @@ pure chore/docs commits). Direct pushes to main must also be logged here.

---

## 2026-07-29

- Added withdrawal validation in Sponsors page (#89): the withdraw form now
fetches `GET /liquidity/my-summary` via `sponsorsService.getMySummary()` and
validates the requested shares against both the sponsor's actual share balance
and the pool's available liquidity. A "Max" button fills the field with the
sponsor's full share balance. The Preview Value block is suppressed when the
amount is invalid. The submit button is disabled while the position query is
loading. Zero-share sponsors see an empty state with an explanatory message.
Added `MySummary` type to `types/index.ts` and a `mySummary` key to
`queryKeys.pool`. Tests cover over-balance, over-liquidity, exact-balance
boundary, and zero-share sponsor states. Also fixed pre-existing
`lucide-react` missing declaration issue with a `.d.ts` shim.

## 2026-07-17

- Fixed intermittent 401 on wallet-connect / role selection: the axios
Expand Down
113 changes: 113 additions & 0 deletions src/lucide-react.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
declare module 'lucide-react' {
import type { FC, SVGProps } from 'react'
export interface LucideProps extends SVGProps<SVGSVGElement> {
size?: number | string
}
export type Icon = FC<LucideProps>
export type LucideIcon = Icon
export const Activity: Icon
export const AlertCircle: Icon
export const AlertTriangle: Icon
export const ArrowDownLeft: Icon
export const ArrowLeft: Icon
export const ArrowRight: Icon
export const ArrowUpRight: Icon
export const Award: Icon
export const Ban: Icon
export const BarChart3: Icon
export const Bell: Icon
export const BellRing: Icon
export const Binary: Icon
export const BookOpen: Icon
export const Bookmark: Icon
export const Building: Icon
export const Building2: Icon
export const Calendar: Icon
export const Check: Icon
export const CheckCircle: Icon
export const CheckCircle2: Icon
export const ChevronDown: Icon
export const ChevronLeft: Icon
export const ChevronRight: Icon
export const ChevronUp: Icon
export const ClipboardList: Icon
export const Clock: Icon
export const Code: Icon
export const Copy: Icon
export const CreditCard: Icon
export const Crown: Icon
export const DollarSign: Icon
export const Download: Icon
export const Edit3: Icon
export const ExternalLink: Icon
export const ExternalLinkIcon: Icon
export const Eye: Icon
export const EyeOff: Icon
export const FileText: Icon
export const Filter: Icon
export const Flag: Icon
export const Gift: Icon
export const Github: Icon
export const Globe: Icon
export const GraduationCap: Icon
export const Grid: Icon
export const Hash: Icon
export const Heart: Icon
export const HelpCircle: Icon
export const Info: Icon
export const Layers: Icon
export const Link: Icon
export const Linkedin: Icon
export const List: Icon
export const Loader2: Icon
export const Lock: Icon
export const LogOut: Icon
export const LucideIcon: Icon
export const Mail: Icon
export const MapPin: Icon
export const Medal: Icon
export const Menu: Icon
export const MessageSquare: Icon
export const Minus: Icon
export const Moon: Icon
export const MoreHorizontal: Icon
export const Package: Icon
export const Percent: Icon
export const Phone: Icon
export const PieChart: Icon
export const Plus: Icon
export const RefreshCw: Icon
export const Rocket: Icon
export const RotateCw: Icon
export const Search: Icon
export const Send: Icon
export const Settings: Icon
export const Share2: Icon
export const Shield: Icon
export const ShieldCheck: Icon
export const SlidersHorizontal: Icon
export const SortAsc: Icon
export const SortDesc: Icon
export const Sparkles: Icon
export const Star: Icon
export const Store: Icon
export const Sun: Icon
export const Tag: Icon
export const Target: Icon
export const Terminal: Icon
export const ThumbsDown: Icon
export const ThumbsUp: Icon
export const Trash2: Icon
export const TrendingDown: Icon
export const TrendingUp: Icon
export const Twitter: Icon
export const Unlock: Icon
export const Upload: Icon
export const User: Icon
export const UserCheck: Icon
export const Users: Icon
export const Wallet: Icon
export const X: Icon
export const XCircle: Icon
export const Zap: Icon
}
79 changes: 68 additions & 11 deletions src/pages/Sponsors.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { Button } from '../components/ui/Button'
import { Card } from '../components/ui/Card'
import { Badge } from '../components/ui/Badge'
import { Spinner } from '../components/ui/Spinner'
import type { PoolInfo } from '../types'
import type { MySummary, PoolInfo } from '../types'
import { colors } from '../constants/colors'
import {
TrendingUp,
Expand Down Expand Up @@ -45,6 +45,16 @@ export function Sponsors() {
enabled: isConnected,
})

const {
data: mySummary,
isLoading: summaryLoading,
isError: summaryError,
} = useQuery<MySummary>({
queryKey: queryKeys.pool.mySummary(),
queryFn: sponsorsService.getMySummary,
enabled: isConnected,
})

const { execute, isLoading: txLoading, error: txError } = useTransaction()

const handleDeposit = async (e: React.FormEvent) => {
Expand Down Expand Up @@ -74,10 +84,18 @@ export function Sponsors() {
}
}

const sharesNum = Number(withdrawShares)
const estimatedPayout = sharesNum * (poolInfo?.sharePrice || 0)
const overBalance = mySummary ? sharesNum > mySummary.shares : false
const overLiquidity = poolInfo ? estimatedPayout > poolInfo.availableLiquidity : false
const hasShares = mySummary ? mySummary.shares > 0 : false
const hasValidationError = overBalance || overLiquidity
const positionQueryLoading = summaryLoading

const handleWithdraw = async (e: React.FormEvent) => {
e.preventDefault()
const shares = Number(withdrawShares)
if (!shares || shares <= 0) return
if (!shares || shares <= 0 || hasValidationError) return

try {
const result = await execute(
Expand Down Expand Up @@ -121,8 +139,6 @@ export function Sponsors() {
)
}

const previewUsdc = Number(withdrawShares) * (poolInfo?.sharePrice || 0)

return (
<div className="max-w-7xl mx-auto px-6 py-8">
<div className="mb-8">
Expand Down Expand Up @@ -302,6 +318,23 @@ export function Sponsors() {
<ArrowDownLeft size={18} />
</Button>
</form>
) : summaryError ? (
<div className="p-4 bg-red-500/10 rounded-xl border border-red-500/20 flex gap-3">
<AlertCircle className="text-red-500 shrink-0" size={20} />
<p className="text-sm text-red-500">Failed to load your position. Please try again later.</p>
</div>
) : mySummary && !hasShares ? (
<div className="space-y-4">
<h3 className="font-display font-bold text-xl text-text-primary mb-4 flex items-center gap-2">
Withdraw Funds
</h3>
<div className="p-4 bg-brand/5 rounded-xl border border-brand/10 text-center">
<p className="text-text-secondary text-sm">You have no shares to withdraw.</p>
<p className="text-text-muted text-xs mt-1">
Deposit USDC to receive pool shares and start earning yield.
</p>
</div>
</div>
) : (
<form onSubmit={handleWithdraw} className="space-y-4" aria-label="Withdraw funds form">
<h3 className="font-display font-bold text-xl text-text-primary mb-4 flex items-center gap-2">
Expand All @@ -311,29 +344,53 @@ export function Sponsors() {
<label htmlFor="shares-amount" className="block text-sm text-text-secondary mb-2">
Amount of Shares
</label>
<p className="text-xs text-text-muted mb-2">
Your balance: {mySummary?.shares.toLocaleString() ?? '—'} shares
</p>
<div className="relative">
<input
id="shares-amount"
type="number"
value={withdrawShares}
onChange={(e) => setWithdrawShares(e.target.value)}
placeholder="0.00"
className="w-full bg-bg border border-border rounded-xl px-4 py-3
className="w-full bg-bg border border-border rounded-xl px-4 py-3 pr-20
text-text-primary focus:outline-none focus:border-brand transition-colors"
aria-describedby="shares-hint"
/>
<div className="absolute right-4 top-1/2 -translate-y-1/2 text-text-muted text-sm" aria-hidden="true">
SHARES
<div className="absolute right-4 top-1/2 -translate-y-1/2 flex gap-1 items-center">
<button
type="button"
onClick={() => setWithdrawShares(String(mySummary?.shares ?? 0))}
className="text-xs font-semibold text-brand hover:text-brand/80 transition-colors px-2 py-1"
>
Max
</button>
<span className="text-text-muted text-sm" aria-hidden="true">SHARES</span>
</div>
</div>
<p id="shares-hint" className="text-text-muted text-xs mt-1">Enter the number of pool shares to withdraw.</p>
<p id="shares-hint" className="text-text-muted text-xs mt-1">
Enter the number of pool shares to withdraw.
</p>
{overBalance && (
<p className="text-red-500 text-xs mt-1 flex items-center gap-1" role="alert">
<AlertCircle size={12} />
You only have {mySummary!.shares.toLocaleString()} shares.
</p>
)}
{overLiquidity && (
<p className="text-red-500 text-xs mt-1 flex items-center gap-1" role="alert">
<AlertCircle size={12} />
Withdrawal exceeds available liquidity ({poolInfo!.availableLiquidity.toLocaleString()} USDC).
</p>
)}
</div>

{Number(withdrawShares) > 0 && (
{sharesNum > 0 && !hasValidationError && (
<div className="p-4 bg-brand/5 rounded-xl border border-brand/10">
<div className="flex justify-between text-sm mb-1">
<span className="text-text-secondary">Preview Value:</span>
<span className="text-brand font-bold">{previewUsdc.toFixed(2)} USDC</span>
<span className="text-brand font-bold">{estimatedPayout.toFixed(2)} USDC</span>
</div>
<p className="text-[10px] text-text-muted leading-tight">
Estimated amount based on current share price. Final amount may vary slightly.
Expand All @@ -352,7 +409,7 @@ export function Sponsors() {
type="submit"
className="w-full"
loading={txLoading}
disabled={!withdrawShares || Number(withdrawShares) <= 0}
disabled={!withdrawShares || Number(withdrawShares) <= 0 || positionQueryLoading || hasValidationError}
>
Withdraw USDC
<ArrowUpRight size={18} />
Expand Down
Loading
Loading