151 lines
4.1 KiB
TypeScript
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)}%`;
|
|
}
|