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
72 changes: 72 additions & 0 deletions src/features/wallet/components/AssetBalanceGrid.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
'use client';

import { Card, CardHeader, CardTitle, CardDescription, CardContent } from '@/components/ui/card';
import { Wallet } from 'lucide-react';
import { formatCurrency, formatRelativeTime } from '@/lib/format';
import { AssetCard } from './AssetCard';
import type { WalletSummary } from '../types';

interface AssetBalanceGridProps {
summary: WalletSummary | null;
isLoading?: boolean;
}

export function AssetBalanceGrid({ summary, isLoading }: AssetBalanceGridProps) {
if (isLoading) {
return (
<Card className="p-8">
<div className="flex flex-col items-center gap-3 text-center">
<div className="h-8 w-8 animate-pulse rounded-full bg-surface-secondary" />
<div className="h-4 w-48 animate-pulse rounded-button bg-surface-secondary" />
<div className="h-3 w-32 animate-pulse rounded-button bg-surface-secondary" />
</div>
</Card>
);
}

if (!summary || summary.assets.length === 0) {
return (
<Card className="p-8">
<div className="flex flex-col items-center gap-3 text-center">
<Wallet className="h-8 w-8 text-foreground-muted" />
<h4 className="font-semibold text-sm text-foreground">No assets found</h4>
<p className="text-xs text-foreground-muted max-w-xs">
Your wallet does not hold any Stellar assets yet. Once you receive tokens, they will appear here.
</p>
</div>
</Card>
);
}

return (
<div className="space-y-4">
<Card>
<CardHeader>
<div className="flex items-center justify-between">
<div>
<CardTitle>Asset Balances</CardTitle>
<CardDescription>
Multi-asset overview across your Stellar wallet
</CardDescription>
</div>
<div className="text-right">
<p className="font-display text-2xl font-bold tabular text-foreground">
{formatCurrency(summary.totalUsdValue, 'USD')}
</p>
<p className="text-2xs text-foreground-muted">
Total Value · {formatRelativeTime(summary.lastUpdated)}
</p>
</div>
</div>
</CardHeader>
<CardContent>
<div className="grid gap-3 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
{summary.assets.map((asset) => (
<AssetCard key={asset.id} asset={asset} />
))}
</div>
</CardContent>
</Card>
</div>
);
}
56 changes: 56 additions & 0 deletions src/features/wallet/components/AssetCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
'use client';

import { Card, CardContent } from '@/components/ui/card';
import { Badge } from '@/components/ui/badge';
import { formatCurrency } from '@/lib/format';
import type { AssetBalance } from '../types';

interface AssetCardProps {
asset: AssetBalance;
}

const ASSET_COLORS: Record<string, string> = {
XLM: 'bg-info-soft text-info',
USDC: 'bg-success-soft text-success',
EURC: 'bg-gold-soft text-gold-strong',
ASTRO: 'bg-warning-soft text-warning',
};

export function AssetCard({ asset }: AssetCardProps) {
const usdValue = asset.balance * asset.usdPrice;
const colorClass = ASSET_COLORS[asset.code] ?? 'bg-surface-secondary text-foreground-secondary';

return (
<Card interactive className="p-4">
<CardContent className="p-0">
<div className="flex items-start justify-between gap-3">
<div className="flex items-center gap-3">
<div
className={`flex h-10 w-10 items-center justify-center rounded-button text-xs font-bold ${colorClass}`}
>
{asset.code}
</div>
<div>
<h4 className="font-semibold text-sm text-foreground">{asset.name}</h4>
<p className="font-mono text-2xs text-foreground-muted">{asset.code}</p>
</div>
</div>
<Badge variant="outline" size="sm" className="font-mono">
${asset.usdPrice.toFixed(2)}
</Badge>
</div>

<div className="mt-4 space-y-1">
<div className="flex items-baseline justify-between">
<span className="font-display text-xl font-bold tabular text-foreground">
{formatCurrency(asset.balance, asset.code)}
</span>
</div>
<p className="text-xs text-foreground-muted">
{formatCurrency(usdValue, 'USD')} equivalent
</p>
</div>
</CardContent>
</Card>
);
}
4 changes: 4 additions & 0 deletions src/features/wallet/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export { AssetBalanceGrid } from './components/AssetBalanceGrid';
export { AssetCard } from './components/AssetCard';
export type { AssetBalance, AssetCode, WalletSummary } from './types';
export { MOCK_WALLET_SUMMARY, MOCK_ASSET_BALANCES } from './mock-data';
80 changes: 35 additions & 45 deletions src/features/wallet/mock-data.ts
Original file line number Diff line number Diff line change
@@ -1,57 +1,47 @@
import type { StellarAsset } from './types';
import type { AssetBalance, WalletSummary } from './types';

/**
* Preset list of well-known Stellar assets.
* Issuer keys are real Stellar mainnet addresses for these tokens.
*/
export const presetAssets: StellarAsset[] = [
export const MOCK_ASSET_BALANCES: AssetBalance[] = [
{
id: 'ast-xlm',
code: 'XLM',
name: 'Stellar Lumens',
balance: 12450.75,
usdPrice: 0.12,
decimals: 7,
},
{
id: 'ast-usdc',
code: 'USDC',
issuer: 'GA5ZSEJYB37JTH5UA4GHHQTUWQZ3XMY4Y2S3VHLW7A7I73ZMIFR67LMZ',
name: 'USD Coin',
verification: 'verified',
trustline: 'trusted',
balance: 8420.5,
usdPrice: 1.0,
issuer: 'GA5ZSEJ6B3AE8RGX3C古城L4PBRPSNY7HO6AZGEBTXOZMOI57DPMGBGI',
decimals: 7,
},
{
id: 'ast-eurc',
code: 'EURC',
issuer: 'GDQOE23C4MMKFZ25LZ5SE6Y56NYCJ7NKVP6YSO7HA7KJLJ7T6S5YEUU',
name: 'Euro Coin',
verification: 'verified',
trustline: 'untrusted',
},
{
code: 'yXLM',
issuer: 'GALPCCUINT6MUDRN6PMKG6CLTEL3TE3KDEFSAU5T2RUEETE5Z5EMX25VP',
name: 'yXLM (Superfluid)',
verification: 'verified',
trustline: 'untrusted',
},
{
code: 'ARST',
issuer: 'GBSTZN5OZTUIJ5ZG4N6XZSOPCJ5T765B5N7L4YELLGLO2ZBEDJWN7B1A',
name: 'Arst',
verification: 'verified',
trustline: 'untrusted',
balance: 3200.0,
usdPrice: 1.09,
issuer: 'GAXLKZUT5RRPOOMN6HEB3TTSX5IREHBWMFT76Y2G4Q377E3VZE5BN2EQ',
decimals: 7,
},
{
code: 'BTC',
issuer: 'GAUTUYYRTHMLUOVG7VZ7S5S7N3H3RE7V6Y3S3Z4J5Z2YX4QH4ZXXXX',
name: 'Bitcoin on Stellar',
verification: 'unverified',
trustline: 'untrusted',
},
{
code: 'ETH',
issuer: 'GBVOLZ7F7Y7EVJZXEWZ3F6V3EODV4YU4GIOFAPJFHTJL4R7PKM3KJP7F',
name: 'Ethereum on Stellar',
verification: 'unverified',
trustline: 'untrusted',
},
{
code: 'SRT',
issuer: 'GSSPDVH3SS66RCPPLJDCNLD4PC6S5KSJVYAJI7GPXGCQ7ESKC3STHT4A',
name: 'Soroban token',
verification: 'unverified',
trustline: 'untrusted',
id: 'ast-astro',
code: 'ASTRO',
name: 'Astroid Token',
balance: 45000.0,
usdPrice: 0.05,
decimals: 7,
},
];

export const MOCK_WALLET_SUMMARY: WalletSummary = {
totalUsdValue: MOCK_ASSET_BALANCES.reduce(
(sum, asset) => sum + asset.balance * asset.usdPrice,
0,
),
assets: MOCK_ASSET_BALANCES,
lastUpdated: new Date().toISOString(),
};
31 changes: 13 additions & 18 deletions src/features/wallet/types.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,18 @@
/** An asset entry in the Stellar Asset Directory. */
export type AssetCode = 'XLM' | 'USDC' | 'EURC' | 'ASTRO';

export type AssetVerificationStatus = 'verified' | 'unverified';

export type AssetTrustlineStatus = 'trusted' | 'untrusted';

export interface StellarAsset {
/** Short code, e.g. "USDC", "EURC", "yXLM". Max 12 alphanumeric chars. */
code: string;
/** Stellar issuer public key (starts with 'G', 56 characters). */
issuer: string;
/** Human-readable name, e.g. "USD Coin". */
export interface AssetBalance {
id: string;
code: AssetCode;
name: string;
/** Whether the issuer has been verified by the directory. */
verification: AssetVerificationStatus;
/** Whether the current wallet holds a trustline for this asset. */
trustline: AssetTrustlineStatus;
balance: number;
usdPrice: number;
iconUrl?: string;
issuer?: string;
decimals: number;
}

export interface AddAssetFormValues {
code: string;
issuer: string;
export interface WalletSummary {
totalUsdValue: number;
assets: AssetBalance[];
lastUpdated: string;
}
Loading