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

151 lines
4.1 KiB
TypeScript

import { create } from 'zustand';
import { invoke } from '../lib/tauri';
function logError(context: string, error: unknown): void {
if (import.meta.env.PROD) {
console.error(`[Swap] ${context}: ${error instanceof Error ? error.name : 'Unknown'}`);
} else {
console.error(`[Swap] ${context}:`, error);
}
}
export interface SwapQuote {
tokenIn: string;
tokenOut: string;
amountIn: string;
amountOut: string;
amountOutMin: string;
priceImpactBps: number;
route: string[];
estimatedGas: number;
}
export interface LiquidityPoolInfo {
poolAddress: string;
tokenA: string;
tokenB: string;
symbolA: string;
symbolB: string;
reserveA: string;
reserveB: string;
totalSupply: string;
feeBps: number;
}
interface SwapState {
quote: SwapQuote | null;
pools: LiquidityPoolInfo[];
isLoadingQuote: boolean;
isSwapping: boolean;
isAddingLiquidity: boolean;
isRemovingLiquidity: boolean;
error: string | null;
clearError: () => void;
getQuote: (tokenIn: string, tokenOut: string, amountIn: string, slippageBps: number) => Promise<SwapQuote>;
executeSwap: (tokenIn: string, tokenOut: string, amountIn: string, amountOutMin: string) => Promise<string>;
fetchPools: () => Promise<void>;
addLiquidity: (tokenA: string, tokenB: string, amountA: string, amountB: string) => Promise<string>;
removeLiquidity: (poolAddress: string, lpAmount: string) => Promise<string>;
}
export const useSwapStore = create<SwapState>()((set) => ({
quote: null,
pools: [],
isLoadingQuote: false,
isSwapping: false,
isAddingLiquidity: false,
isRemovingLiquidity: false,
error: null,
clearError: () => set({ error: null }),
getQuote: async (tokenIn, tokenOut, amountIn, slippageBps) => {
set({ isLoadingQuote: true, error: null });
try {
const quote = await invoke<SwapQuote>('swap_get_quote', {
tokenIn,
tokenOut,
amountIn,
slippageBps,
});
set({ quote, isLoadingQuote: false });
return quote;
} catch (error) {
const msg = error instanceof Error ? error.message : 'Quote failed';
logError('getQuote', error);
set({ error: msg, isLoadingQuote: false });
throw error;
}
},
executeSwap: async (tokenIn, tokenOut, amountIn, amountOutMin) => {
set({ isSwapping: true, error: null });
try {
const txHash = await invoke<string>('swap_execute', {
tokenIn,
tokenOut,
amountIn,
amountOutMin,
});
set({ isSwapping: false, quote: null });
return txHash;
} catch (error) {
const msg = error instanceof Error ? error.message : 'Swap failed';
logError('executeSwap', error);
set({ error: msg, isSwapping: false });
throw error;
}
},
fetchPools: async () => {
try {
const pools = await invoke<LiquidityPoolInfo[]>('swap_get_pools');
set({ pools });
} catch (error) {
logError('fetchPools', error);
}
},
addLiquidity: async (tokenA, tokenB, amountA, amountB) => {
set({ isAddingLiquidity: true, error: null });
try {
const txHash = await invoke<string>('swap_add_liquidity', {
tokenA,
tokenB,
amountA,
amountB,
});
set({ isAddingLiquidity: false });
return txHash;
} catch (error) {
const msg = error instanceof Error ? error.message : 'Add liquidity failed';
logError('addLiquidity', error);
set({ error: msg, isAddingLiquidity: false });
throw error;
}
},
removeLiquidity: async (poolAddress, lpAmount) => {
set({ isRemovingLiquidity: true, error: null });
try {
const txHash = await invoke<string>('swap_remove_liquidity', {
poolAddress,
lpAmount,
});
set({ isRemovingLiquidity: false });
return txHash;
} catch (error) {
const msg = error instanceof Error ? error.message : 'Remove liquidity failed';
logError('removeLiquidity', error);
set({ error: msg, isRemovingLiquidity: false });
throw error;
}
},
}));
export function formatPriceImpact(bps: number): string {
const pct = bps / 100;
if (pct < 0.01) return '<0.01%';
return `${pct.toFixed(2)}%`;
}