Skip to content
Open
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
13 changes: 13 additions & 0 deletions next.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,19 @@ const API_URL =
const nextConfig: NextConfig = {
output: 'standalone',
reactStrictMode: true,
experimental: {
optimizePackageImports: ['@noble/ed25519', '@noble/hashes', '@noble/curves'],
},
async headers() {
return [
{
source: '/_next/static/:path*',
headers: [
{ key: 'Cache-Control', value: 'public, max-age=31536000, immutable' },
],
},
];
},
async rewrites() {
return [
{
Expand Down
8 changes: 7 additions & 1 deletion src/components/pool-detail/PriceChart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ import { useTwap } from '@/hooks/useTwap';
import { usePriceHistory } from '@/hooks/usePriceHistory';
import { cn } from '@/lib/utils';

// Hoist dynamic import to module level so it's cached across renders
const chartModulePromise = typeof window !== 'undefined'
? import('lightweight-charts')
: null;

interface PriceChartProps {
/** TWAP spot price as string (fallback when no history). */
spotPrice: string | null;
Expand Down Expand Up @@ -100,7 +105,8 @@ export function PriceChart({ spotPrice, poolId, className }: PriceChartProps) {

async function initChart() {
try {
const { createChart, LineStyle } = await import('lightweight-charts');
if (!chartModulePromise) return;
const { createChart, LineStyle } = await chartModulePromise;
if (disposed || !chartRef.current) return;

const chart = createChart(chartRef.current, {
Expand Down
9 changes: 7 additions & 2 deletions src/components/wallet/WalletButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { WalletModal } from '@/components/wallet/WalletModal';
export function WalletButton() {
const [modalOpen, setModalOpen] = useState(false);
const [mounted, setMounted] = useState(false);
const { address, isConnected, isLocked, hydrate } = useWalletStore();
const { address, isConnected, isLocked, source, extensionDetected, hydrate } = useWalletStore();

useEffect(() => {
hydrate();
Expand All @@ -24,17 +24,22 @@ export function WalletButton() {
? 'Unlock Wallet'
: 'Connect Wallet';

const showExtensionDot = mounted && isConnected && source === 'extension';

return (
<>
<button
type="button"
onClick={() => setModalOpen(true)}
className={
isConnected && mounted
? 'px-4 py-2 text-sm font-mono font-medium rounded-lg border border-border bg-surface hover:bg-surface-hover transition-colors text-text-primary'
? 'relative px-4 py-2 text-sm font-mono font-medium rounded-lg border border-border bg-surface hover:bg-surface-hover transition-colors text-text-primary'
: 'px-4 py-2 text-sm font-semibold rounded-lg bg-accent hover:bg-accent-hover transition-colors text-background'
}
>
{showExtensionDot && (
<span className="absolute -top-1 -right-1 w-3 h-3 rounded-full bg-buy border-2 border-background" title="Basalt Wallet Extension" />
)}
{label}
</button>
<WalletModal open={modalOpen} onOpenChange={setModalOpen} />
Expand Down
98 changes: 88 additions & 10 deletions src/components/wallet/WalletModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,23 +13,31 @@ interface WalletModalProps {
}

export function WalletModal({ open, onOpenChange }: WalletModalProps) {
const { isConnected, address } = useWalletStore();
const { isConnected, address, source, extensionDetected } = useWalletStore();

if (isConnected && address) {
return (
<Modal open={open} onOpenChange={onOpenChange} title="Wallet">
<ConnectedView address={address} onClose={() => onOpenChange(false)} />
<ConnectedView address={address} source={source} onClose={() => onOpenChange(false)} />
</Modal>
);
}

const keystoreExists = typeof window !== 'undefined' && hasKeystore();
const defaultTab = keystoreExists ? 'unlock' : 'create';
const defaultTab = extensionDetected ? 'extension' : keystoreExists ? 'unlock' : 'create';

return (
<Modal open={open} onOpenChange={onOpenChange} title="Wallet">
<Tabs.Root defaultValue={defaultTab} className="w-full">
<Tabs.List className="flex border-b border-border mb-4">
{extensionDetected && (
<Tabs.Trigger
value="extension"
className="flex-1 px-4 py-2 text-sm text-text-secondary hover:text-text-primary data-[state=active]:text-accent data-[state=active]:border-b-2 data-[state=active]:border-accent transition-colors"
>
Extension
</Tabs.Trigger>
)}
<Tabs.Trigger
value="create"
className="flex-1 px-4 py-2 text-sm text-text-secondary hover:text-text-primary data-[state=active]:text-accent data-[state=active]:border-b-2 data-[state=active]:border-accent transition-colors"
Expand All @@ -52,6 +60,11 @@ export function WalletModal({ open, onOpenChange }: WalletModalProps) {
)}
</Tabs.List>

{extensionDetected && (
<Tabs.Content value="extension">
<ExtensionTab onSuccess={() => onOpenChange(false)} />
</Tabs.Content>
)}
<Tabs.Content value="create">
<CreateTab onSuccess={() => onOpenChange(false)} />
</Tabs.Content>
Expand All @@ -74,9 +87,11 @@ export function WalletModal({ open, onOpenChange }: WalletModalProps) {

function ConnectedView({
address,
source,
onClose,
}: {
address: string;
source: 'local' | 'extension' | null;
onClose: () => void;
}) {
const { lock, disconnect } = useWalletStore();
Expand All @@ -100,6 +115,14 @@ function ConnectedView({

return (
<div className="flex flex-col gap-5">
{/* Source badge */}
<div className="flex justify-center">
<span className="inline-flex items-center gap-1.5 px-3 py-1 rounded-full text-xs font-medium bg-accent/10 text-accent">
<span className="w-1.5 h-1.5 rounded-full bg-buy" />
{source === 'extension' ? 'Basalt Wallet Extension' : 'Local Wallet'}
</span>
</div>

{/* Address display */}
<div className="flex flex-col items-center gap-3 py-2">
<div className="w-12 h-12 rounded-full bg-accent/15 flex items-center justify-center">
Expand All @@ -123,13 +146,15 @@ function ConnectedView({

{/* Actions */}
<div className="flex flex-col gap-2">
<button
type="button"
onClick={handleLock}
className="w-full py-2.5 rounded-lg border border-border text-text-secondary hover:text-text-primary hover:bg-surface-hover text-sm font-medium transition-colors"
>
Lock Wallet
</button>
{source === 'local' && (
<button
type="button"
onClick={handleLock}
className="w-full py-2.5 rounded-lg border border-border text-text-secondary hover:text-text-primary hover:bg-surface-hover text-sm font-medium transition-colors"
>
Lock Wallet
</button>
)}
<button
type="button"
onClick={handleDisconnect}
Expand All @@ -142,6 +167,59 @@ function ConnectedView({
);
}

// ---------------------------------------------------------------------------
// Extension tab
// ---------------------------------------------------------------------------

function ExtensionTab({ onSuccess }: { onSuccess: () => void }) {
const [error, setError] = useState('');
const [loading, setLoading] = useState(false);
const connectExtension = useWalletStore((s) => s.connectExtension);

async function handleConnect() {
setError('');
setLoading(true);
try {
await connectExtension();
onSuccess();
} catch (e) {
setError(e instanceof Error ? e.message : 'Failed to connect');
} finally {
setLoading(false);
}
}

return (
<div className="flex flex-col gap-4 items-center py-4">
{/* Extension icon */}
<div className="w-16 h-16 rounded-2xl bg-accent/10 flex items-center justify-center">
<svg width="32" height="32" viewBox="0 0 32 32" fill="none">
<rect width="32" height="32" rx="6" fill="currentColor" className="text-accent/20" />
<text x="16" y="22" textAnchor="middle" fill="currentColor" fontSize="16" fontWeight="bold" className="text-accent">B</text>
</svg>
</div>

<div className="text-center">
<p className="text-sm font-semibold text-text-primary">Basalt Wallet Extension</p>
<p className="text-xs text-text-secondary mt-1">
Connect your Basalt Wallet to trade securely. Your private keys never leave the extension.
</p>
</div>

{error && <p className="text-sm text-sell">{error}</p>}

<button
type="button"
onClick={handleConnect}
disabled={loading}
className="w-full py-3 rounded-lg bg-accent hover:bg-accent-hover text-background font-semibold transition-colors disabled:opacity-50"
>
{loading ? 'Connecting...' : 'Connect Extension'}
</button>
</div>
);
}

// ---------------------------------------------------------------------------
// Tab forms
// ---------------------------------------------------------------------------
Expand Down
2 changes: 1 addition & 1 deletion src/hooks/useTokenBalances.ts
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ export function useTokenBalances(

const { data, error, isLoading, mutate } = useSWR<TokenBalance[]>(
userAddress && tokens.length > 0
? `token-balances:${userAddress}:${tokens.sort().join(',')}`
? `token-balances:${userAddress}:${[...tokens].sort().join(',')}`
: null,
() => fetchAllBalances(tokens, userAddress!),
{
Expand Down
Loading
Loading