synor/apps/desktop-wallet/src/store/yield.ts
2026-02-02 15:16:29 +05:30

166 lines
4.9 KiB
TypeScript

import { create } from 'zustand';
import { invoke } from '../lib/tauri';
export interface YieldOpportunity {
id: string;
name: string;
protocol: string;
asset: string;
apy: number;
tvl: number;
riskLevel: 'low' | 'medium' | 'high';
lockupPeriodDays: number;
minDeposit: number;
}
export interface YieldPosition {
id: string;
opportunityId: string;
depositedAmount: number;
currentValue: number;
rewardsEarned: number;
autoCompound: boolean;
createdAt: number;
lastCompoundAt?: number;
}
interface YieldState {
opportunities: YieldOpportunity[];
positions: YieldPosition[];
isLoading: boolean;
error: string | null;
}
interface YieldActions {
loadOpportunities: () => Promise<void>;
deposit: (opportunityId: string, amount: number, autoCompound: boolean) => Promise<YieldPosition>;
withdraw: (positionId: string, amount?: number) => Promise<YieldPosition>;
listPositions: () => Promise<void>;
compound: (positionId: string) => Promise<YieldPosition>;
clearError: () => void;
}
// Transform snake_case to camelCase
const transformOpportunity = (data: Record<string, unknown>): YieldOpportunity => ({
id: data.id as string,
name: data.name as string,
protocol: data.protocol as string,
asset: data.asset as string,
apy: data.apy as number,
tvl: data.tvl as number,
riskLevel: data.risk_level as YieldOpportunity['riskLevel'],
lockupPeriodDays: data.lockup_period_days as number,
minDeposit: data.min_deposit as number,
});
const transformPosition = (data: Record<string, unknown>): YieldPosition => ({
id: data.id as string,
opportunityId: data.opportunity_id as string,
depositedAmount: data.deposited_amount as number,
currentValue: data.current_value as number,
rewardsEarned: data.rewards_earned as number,
autoCompound: data.auto_compound as boolean,
createdAt: data.created_at as number,
lastCompoundAt: data.last_compound_at as number | undefined,
});
export const useYieldStore = create<YieldState & YieldActions>((set) => ({
opportunities: [],
positions: [],
isLoading: false,
error: null,
loadOpportunities: async () => {
try {
set({ isLoading: true, error: null });
const data = await invoke<Record<string, unknown>[]>('yield_get_opportunities');
const opportunities = data.map(transformOpportunity);
set({ opportunities, isLoading: false });
} catch (error) {
set({ error: String(error), isLoading: false });
}
},
deposit: async (opportunityId: string, amount: number, autoCompound: boolean) => {
try {
set({ isLoading: true, error: null });
const data = await invoke<Record<string, unknown>>('yield_deposit', {
opportunityId,
amount,
autoCompound,
});
const position = transformPosition(data);
set((state) => ({
positions: [position, ...state.positions],
isLoading: false,
}));
return position;
} catch (error) {
set({ error: String(error), isLoading: false });
throw error;
}
},
withdraw: async (positionId: string, amount?: number) => {
try {
set({ isLoading: true, error: null });
const data = await invoke<Record<string, unknown>>('yield_withdraw', {
positionId,
amount,
});
const position = transformPosition(data);
set((state) => ({
positions: state.positions.map((p) => (p.id === positionId ? position : p)),
isLoading: false,
}));
return position;
} catch (error) {
set({ error: String(error), isLoading: false });
throw error;
}
},
listPositions: async () => {
try {
set({ isLoading: true, error: null });
const data = await invoke<Record<string, unknown>[]>('yield_list_positions');
const positions = data.map(transformPosition);
set({ positions, isLoading: false });
} catch (error) {
set({ error: String(error), isLoading: false });
}
},
compound: async (positionId: string) => {
try {
const data = await invoke<Record<string, unknown>>('yield_compound', { positionId });
const position = transformPosition(data);
set((state) => ({
positions: state.positions.map((p) => (p.id === positionId ? position : p)),
}));
return position;
} catch (error) {
set({ error: String(error) });
throw error;
}
},
clearError: () => set({ error: null }),
}));
// Helper to format APY
export const formatAPY = (apy: number): string => `${apy.toFixed(2)}%`;
// Helper to format TVL
export const formatTVL = (sompi: number): string => {
const syn = sompi / 100_000_000;
if (syn >= 1_000_000) return `${(syn / 1_000_000).toFixed(2)}M SYN`;
if (syn >= 1_000) return `${(syn / 1_000).toFixed(2)}K SYN`;
return `${syn.toFixed(2)} SYN`;
};
// Helper to format sompi to SYN
export const formatAmount = (sompi: number): string => {
const syn = sompi / 100_000_000;
return `${syn.toFixed(8)} SYN`;
};