@luxfi/exchange 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (154) hide show
  1. package/dist/bridge/__tests__/use-private-teleport.test.d.ts +2 -0
  2. package/dist/bridge/__tests__/use-private-teleport.test.d.ts.map +1 -0
  3. package/dist/bridge/__tests__/use-private-teleport.test.js +272 -0
  4. package/dist/bridge/cross-chain-store.d.ts +57 -0
  5. package/dist/bridge/cross-chain-store.d.ts.map +1 -0
  6. package/dist/bridge/cross-chain-store.js +158 -0
  7. package/dist/bridge/index.d.ts +78 -0
  8. package/dist/bridge/index.d.ts.map +1 -0
  9. package/dist/bridge/index.js +79 -0
  10. package/dist/bridge/private-teleport-types.d.ts +634 -0
  11. package/dist/bridge/private-teleport-types.d.ts.map +1 -0
  12. package/dist/bridge/private-teleport-types.js +308 -0
  13. package/dist/bridge/types.d.ts +84 -0
  14. package/dist/bridge/types.d.ts.map +1 -0
  15. package/dist/bridge/types.js +37 -0
  16. package/dist/bridge/use-cross-chain-mint.d.ts +34 -0
  17. package/dist/bridge/use-cross-chain-mint.d.ts.map +1 -0
  18. package/dist/bridge/use-cross-chain-mint.js +228 -0
  19. package/dist/bridge/use-private-teleport.d.ts +69 -0
  20. package/dist/bridge/use-private-teleport.d.ts.map +1 -0
  21. package/dist/bridge/use-private-teleport.js +666 -0
  22. package/dist/chains/index.d.ts +6 -0
  23. package/dist/chains/index.d.ts.map +1 -0
  24. package/dist/chains/index.js +6 -0
  25. package/dist/chains/lux.d.ts +508 -0
  26. package/dist/chains/lux.d.ts.map +1 -0
  27. package/dist/chains/lux.js +131 -0
  28. package/dist/contracts/abis/dex-swap-router.d.ts +137 -0
  29. package/dist/contracts/abis/dex-swap-router.d.ts.map +1 -0
  30. package/dist/contracts/abis/dex-swap-router.js +95 -0
  31. package/dist/contracts/abis/erc20.d.ts +136 -0
  32. package/dist/contracts/abis/erc20.d.ts.map +1 -0
  33. package/dist/contracts/abis/erc20.js +96 -0
  34. package/dist/contracts/abis/index.d.ts +15 -0
  35. package/dist/contracts/abis/index.d.ts.map +1 -0
  36. package/dist/contracts/abis/index.js +15 -0
  37. package/dist/contracts/abis/nft-position-manager.d.ts +235 -0
  38. package/dist/contracts/abis/nft-position-manager.d.ts.map +1 -0
  39. package/dist/contracts/abis/nft-position-manager.js +146 -0
  40. package/dist/contracts/abis/pool-manager.d.ts +315 -0
  41. package/dist/contracts/abis/pool-manager.d.ts.map +1 -0
  42. package/dist/contracts/abis/pool-manager.js +191 -0
  43. package/dist/contracts/abis/quoter-v2.d.ts +103 -0
  44. package/dist/contracts/abis/quoter-v2.d.ts.map +1 -0
  45. package/dist/contracts/abis/quoter-v2.js +68 -0
  46. package/dist/contracts/abis/swap-router.d.ts +119 -0
  47. package/dist/contracts/abis/swap-router.d.ts.map +1 -0
  48. package/dist/contracts/abis/swap-router.js +75 -0
  49. package/dist/contracts/abis/uniswap-v2-factory.d.ts +75 -0
  50. package/dist/contracts/abis/uniswap-v2-factory.d.ts.map +1 -0
  51. package/dist/contracts/abis/uniswap-v2-factory.js +49 -0
  52. package/dist/contracts/abis/uniswap-v2-pair.d.ts +119 -0
  53. package/dist/contracts/abis/uniswap-v2-pair.d.ts.map +1 -0
  54. package/dist/contracts/abis/uniswap-v2-pair.js +85 -0
  55. package/dist/contracts/abis/uniswap-v2-router.d.ts +249 -0
  56. package/dist/contracts/abis/uniswap-v2-router.d.ts.map +1 -0
  57. package/dist/contracts/abis/uniswap-v2-router.js +146 -0
  58. package/dist/contracts/abis/uniswap-v3-factory.d.ts +77 -0
  59. package/dist/contracts/abis/uniswap-v3-factory.d.ts.map +1 -0
  60. package/dist/contracts/abis/uniswap-v3-factory.js +45 -0
  61. package/dist/contracts/abis/uniswap-v3-pool.d.ts +128 -0
  62. package/dist/contracts/abis/uniswap-v3-pool.d.ts.map +1 -0
  63. package/dist/contracts/abis/uniswap-v3-pool.js +81 -0
  64. package/dist/contracts/addresses.d.ts +141 -0
  65. package/dist/contracts/addresses.d.ts.map +1 -0
  66. package/dist/contracts/addresses.js +108 -0
  67. package/dist/contracts/index.d.ts +6 -0
  68. package/dist/contracts/index.d.ts.map +1 -0
  69. package/dist/contracts/index.js +5 -0
  70. package/dist/dex/balance-delta.d.ts +27 -0
  71. package/dist/dex/balance-delta.d.ts.map +1 -0
  72. package/dist/dex/balance-delta.js +45 -0
  73. package/dist/dex/index.d.ts +7 -0
  74. package/dist/dex/index.d.ts.map +1 -0
  75. package/dist/dex/index.js +6 -0
  76. package/dist/dex/pool-key.d.ts +19 -0
  77. package/dist/dex/pool-key.d.ts.map +1 -0
  78. package/dist/dex/pool-key.js +44 -0
  79. package/dist/dex/types.d.ts +71 -0
  80. package/dist/dex/types.d.ts.map +1 -0
  81. package/dist/dex/types.js +28 -0
  82. package/dist/hooks/index.d.ts +10 -0
  83. package/dist/hooks/index.d.ts.map +1 -0
  84. package/dist/hooks/index.js +9 -0
  85. package/dist/hooks/use-pools.d.ts +24 -0
  86. package/dist/hooks/use-pools.d.ts.map +1 -0
  87. package/dist/hooks/use-pools.js +85 -0
  88. package/dist/hooks/use-positions.d.ts +17 -0
  89. package/dist/hooks/use-positions.d.ts.map +1 -0
  90. package/dist/hooks/use-positions.js +65 -0
  91. package/dist/hooks/use-swap-quote.d.ts +19 -0
  92. package/dist/hooks/use-swap-quote.d.ts.map +1 -0
  93. package/dist/hooks/use-swap-quote.js +54 -0
  94. package/dist/hooks/use-swap.d.ts +22 -0
  95. package/dist/hooks/use-swap.d.ts.map +1 -0
  96. package/dist/hooks/use-swap.js +46 -0
  97. package/dist/hooks/use-token-allowance.d.ts +27 -0
  98. package/dist/hooks/use-token-allowance.d.ts.map +1 -0
  99. package/dist/hooks/use-token-allowance.js +59 -0
  100. package/dist/hooks/use-token-balance.d.ts +17 -0
  101. package/dist/hooks/use-token-balance.d.ts.map +1 -0
  102. package/dist/hooks/use-token-balance.js +58 -0
  103. package/dist/index.d.ts +17 -0
  104. package/dist/index.d.ts.map +1 -0
  105. package/dist/index.js +24 -0
  106. package/dist/stores/index.d.ts +7 -0
  107. package/dist/stores/index.d.ts.map +1 -0
  108. package/dist/stores/index.js +6 -0
  109. package/dist/stores/settings-store.d.ts +25 -0
  110. package/dist/stores/settings-store.d.ts.map +1 -0
  111. package/dist/stores/settings-store.js +16 -0
  112. package/dist/stores/swap-store.d.ts +38 -0
  113. package/dist/stores/swap-store.d.ts.map +1 -0
  114. package/dist/stores/swap-store.js +58 -0
  115. package/dist/stores/token-store.d.ts +21 -0
  116. package/dist/stores/token-store.d.ts.map +1 -0
  117. package/dist/stores/token-store.js +32 -0
  118. package/dist/tokens/index.d.ts +65 -0
  119. package/dist/tokens/index.d.ts.map +1 -0
  120. package/dist/tokens/index.js +185 -0
  121. package/package.json +78 -0
  122. package/src/chains/index.ts +21 -0
  123. package/src/chains/lux.ts +141 -0
  124. package/src/contracts/abis/dex-swap-router.ts +98 -0
  125. package/src/contracts/abis/erc20.ts +96 -0
  126. package/src/contracts/abis/index.ts +17 -0
  127. package/src/contracts/abis/nft-position-manager.ts +146 -0
  128. package/src/contracts/abis/pool-manager.ts +198 -0
  129. package/src/contracts/abis/quoter-v2.ts +68 -0
  130. package/src/contracts/abis/swap-router.ts +75 -0
  131. package/src/contracts/abis/uniswap-v2-factory.ts +49 -0
  132. package/src/contracts/abis/uniswap-v2-pair.ts +85 -0
  133. package/src/contracts/abis/uniswap-v2-router.ts +146 -0
  134. package/src/contracts/abis/uniswap-v3-factory.ts +45 -0
  135. package/src/contracts/abis/uniswap-v3-pool.ts +81 -0
  136. package/src/contracts/addresses.ts +128 -0
  137. package/src/contracts/index.ts +14 -0
  138. package/src/dex/balance-delta.ts +52 -0
  139. package/src/dex/index.ts +7 -0
  140. package/src/dex/pool-key.ts +62 -0
  141. package/src/dex/types.ts +87 -0
  142. package/src/hooks/index.ts +10 -0
  143. package/src/hooks/use-pools.ts +116 -0
  144. package/src/hooks/use-positions.ts +90 -0
  145. package/src/hooks/use-swap-quote.ts +81 -0
  146. package/src/hooks/use-swap.ts +64 -0
  147. package/src/hooks/use-token-allowance.ts +74 -0
  148. package/src/hooks/use-token-balance.ts +71 -0
  149. package/src/index.ts +31 -0
  150. package/src/stores/index.ts +7 -0
  151. package/src/stores/settings-store.ts +54 -0
  152. package/src/stores/swap-store.ts +112 -0
  153. package/src/stores/token-store.ts +62 -0
  154. package/src/tokens/index.ts +220 -0
@@ -0,0 +1,81 @@
1
+ 'use client'
2
+
3
+ import { useQuery } from '@tanstack/react-query'
4
+ import { usePublicClient } from 'wagmi'
5
+ import { getContracts } from '../contracts'
6
+ import { QUOTER_V2_ABI } from '../contracts/abis'
7
+ import type { Token } from '../tokens'
8
+
9
+ export interface SwapQuote {
10
+ amountIn: bigint
11
+ amountOut: bigint
12
+ priceImpact: number
13
+ route: Token[]
14
+ gasEstimate: bigint
15
+ }
16
+
17
+ export interface UseSwapQuoteParams {
18
+ tokenIn: Token | null
19
+ tokenOut: Token | null
20
+ amountIn: bigint
21
+ chainId: number
22
+ }
23
+
24
+ /**
25
+ * Hook to get swap quote from V3 Quoter
26
+ */
27
+ export function useSwapQuote({
28
+ tokenIn,
29
+ tokenOut,
30
+ amountIn,
31
+ chainId,
32
+ }: UseSwapQuoteParams) {
33
+ const publicClient = usePublicClient({ chainId })
34
+ const contracts = getContracts(chainId)
35
+
36
+ return useQuery({
37
+ queryKey: ['swapQuote', tokenIn?.address, tokenOut?.address, amountIn.toString(), chainId],
38
+ queryFn: async (): Promise<SwapQuote | null> => {
39
+ if (!tokenIn || !tokenOut || amountIn === 0n || !publicClient) {
40
+ return null
41
+ }
42
+
43
+ try {
44
+ // Call QuoterV2 for quote
45
+ const result = await publicClient.simulateContract({
46
+ address: contracts.V3_QUOTER_V2,
47
+ abi: QUOTER_V2_ABI,
48
+ functionName: 'quoteExactInputSingle',
49
+ args: [
50
+ {
51
+ tokenIn: tokenIn.address,
52
+ tokenOut: tokenOut.address,
53
+ amountIn,
54
+ fee: 3000, // 0.30% fee tier
55
+ sqrtPriceLimitX96: 0n,
56
+ },
57
+ ],
58
+ })
59
+
60
+ const [amountOut, , , gasEstimate] = result.result as [bigint, bigint, number, bigint]
61
+
62
+ // Calculate price impact (simplified)
63
+ const priceImpact = 0.003 // 0.3% placeholder
64
+
65
+ return {
66
+ amountIn,
67
+ amountOut,
68
+ priceImpact,
69
+ route: [tokenIn, tokenOut],
70
+ gasEstimate,
71
+ }
72
+ } catch (error) {
73
+ console.error('Failed to get swap quote:', error)
74
+ return null
75
+ }
76
+ },
77
+ enabled: !!tokenIn && !!tokenOut && amountIn > 0n && !!publicClient,
78
+ staleTime: 10_000, // 10 seconds
79
+ refetchInterval: 15_000, // Refetch every 15 seconds
80
+ })
81
+ }
@@ -0,0 +1,64 @@
1
+ 'use client'
2
+
3
+ import { useMutation } from '@tanstack/react-query'
4
+ import { useWriteContract, useWaitForTransactionReceipt } from 'wagmi'
5
+ import { parseUnits } from 'viem'
6
+ import { getContracts } from '../contracts'
7
+ import { SWAP_ROUTER_ABI } from '../contracts/abis'
8
+ import type { Token } from '../tokens'
9
+ import { isNativeToken } from '../tokens'
10
+
11
+ export interface SwapRequest {
12
+ tokenIn: Token
13
+ tokenOut: Token
14
+ amountIn: bigint
15
+ amountOutMinimum: bigint
16
+ recipient: `0x${string}`
17
+ deadline: bigint
18
+ chainId: number
19
+ }
20
+
21
+ /**
22
+ * Hook to execute a swap transaction
23
+ */
24
+ export function useSwap() {
25
+ const { writeContractAsync, data: hash, isPending } = useWriteContract()
26
+ const { isLoading: isConfirming, isSuccess } = useWaitForTransactionReceipt({ hash })
27
+
28
+ const swap = useMutation({
29
+ mutationFn: async (params: SwapRequest) => {
30
+ const contracts = getContracts(params.chainId)
31
+ const isNativeIn = isNativeToken(params.tokenIn)
32
+
33
+ const txHash = await writeContractAsync({
34
+ address: contracts.V3_SWAP_ROUTER_02,
35
+ abi: SWAP_ROUTER_ABI,
36
+ functionName: 'exactInputSingle',
37
+ args: [
38
+ {
39
+ tokenIn: isNativeIn ? contracts.WLUX : params.tokenIn.address,
40
+ tokenOut: params.tokenOut.address,
41
+ fee: 3000, // 0.30% fee tier
42
+ recipient: params.recipient,
43
+ deadline: params.deadline,
44
+ amountIn: params.amountIn,
45
+ amountOutMinimum: params.amountOutMinimum,
46
+ sqrtPriceLimitX96: 0n,
47
+ },
48
+ ],
49
+ value: isNativeIn ? params.amountIn : 0n,
50
+ })
51
+
52
+ return txHash
53
+ },
54
+ })
55
+
56
+ return {
57
+ swap: swap.mutateAsync,
58
+ hash,
59
+ isPending: isPending || swap.isPending,
60
+ isConfirming,
61
+ isSuccess,
62
+ error: swap.error,
63
+ }
64
+ }
@@ -0,0 +1,74 @@
1
+ 'use client'
2
+
3
+ import { useReadContract, useWriteContract, useWaitForTransactionReceipt } from 'wagmi'
4
+ import { maxUint256 } from 'viem'
5
+ import { ERC20_ABI } from '../contracts/abis'
6
+ import type { Token } from '../tokens'
7
+ import { isNativeToken } from '../tokens'
8
+ import type { Address } from 'viem'
9
+
10
+ /**
11
+ * Hook to check and manage token allowance
12
+ */
13
+ export function useTokenAllowance(
14
+ token: Token | null,
15
+ owner: Address | undefined,
16
+ spender: Address | undefined,
17
+ chainId: number
18
+ ) {
19
+ const allowance = useReadContract({
20
+ address: token?.address,
21
+ abi: ERC20_ABI,
22
+ functionName: 'allowance',
23
+ args: owner && spender ? [owner, spender] : undefined,
24
+ chainId,
25
+ query: {
26
+ enabled: !!token && !isNativeToken(token) && !!owner && !!spender,
27
+ },
28
+ })
29
+
30
+ const { writeContractAsync, data: hash, isPending } = useWriteContract()
31
+ const { isLoading: isConfirming, isSuccess } = useWaitForTransactionReceipt({ hash })
32
+
33
+ const approve = async (amount: bigint = maxUint256) => {
34
+ if (!token || !spender || isNativeToken(token)) return
35
+
36
+ return writeContractAsync({
37
+ address: token.address,
38
+ abi: ERC20_ABI,
39
+ functionName: 'approve',
40
+ args: [spender, amount],
41
+ })
42
+ }
43
+
44
+ // Native tokens don't need approval
45
+ if (token && isNativeToken(token)) {
46
+ return {
47
+ allowance: maxUint256,
48
+ isApproved: true,
49
+ approve,
50
+ isPending: false,
51
+ isConfirming: false,
52
+ isSuccess: true,
53
+ }
54
+ }
55
+
56
+ const currentAllowance = (allowance.data as bigint) ?? 0n
57
+
58
+ return {
59
+ allowance: currentAllowance,
60
+ isApproved: currentAllowance > 0n,
61
+ approve,
62
+ isPending,
63
+ isConfirming,
64
+ isSuccess,
65
+ refetch: allowance.refetch,
66
+ }
67
+ }
68
+
69
+ /**
70
+ * Check if approval is needed for a specific amount
71
+ */
72
+ export function needsApproval(allowance: bigint, amount: bigint): boolean {
73
+ return allowance < amount
74
+ }
@@ -0,0 +1,71 @@
1
+ 'use client'
2
+
3
+ import { useBalance, useReadContract } from 'wagmi'
4
+ import { ERC20_ABI } from '../contracts/abis'
5
+ import type { Token } from '../tokens'
6
+ import { isNativeToken } from '../tokens'
7
+ import type { Address } from 'viem'
8
+
9
+ /**
10
+ * Hook to get token balance for an address
11
+ */
12
+ export function useTokenBalance(
13
+ token: Token | null,
14
+ address: Address | undefined,
15
+ chainId: number
16
+ ) {
17
+ // For native token
18
+ const nativeBalance = useBalance({
19
+ address,
20
+ chainId,
21
+ query: {
22
+ enabled: !!token && isNativeToken(token) && !!address,
23
+ },
24
+ })
25
+
26
+ // For ERC20 token
27
+ const erc20Balance = useReadContract({
28
+ address: token?.address,
29
+ abi: ERC20_ABI,
30
+ functionName: 'balanceOf',
31
+ args: address ? [address] : undefined,
32
+ chainId,
33
+ query: {
34
+ enabled: !!token && !isNativeToken(token) && !!address,
35
+ },
36
+ })
37
+
38
+ if (!token || !address) {
39
+ return {
40
+ balance: 0n,
41
+ formatted: '0',
42
+ isLoading: false,
43
+ error: null,
44
+ }
45
+ }
46
+
47
+ if (isNativeToken(token)) {
48
+ const value = nativeBalance.data?.value ?? 0n
49
+ return {
50
+ balance: value,
51
+ formatted: formatBalance(value, token.decimals),
52
+ isLoading: nativeBalance.isLoading,
53
+ error: nativeBalance.error,
54
+ }
55
+ }
56
+
57
+ return {
58
+ balance: (erc20Balance.data as bigint) ?? 0n,
59
+ formatted: formatBalance((erc20Balance.data as bigint) ?? 0n, token.decimals),
60
+ isLoading: erc20Balance.isLoading,
61
+ error: erc20Balance.error,
62
+ }
63
+ }
64
+
65
+ function formatBalance(value: bigint, decimals: number): string {
66
+ const divisor = 10n ** BigInt(decimals)
67
+ const whole = value / divisor
68
+ const fraction = value % divisor
69
+ const fractionStr = fraction.toString().padStart(decimals, '0').slice(0, 6)
70
+ return `${whole}.${fractionStr}`.replace(/\.?0+$/, '')
71
+ }
package/src/index.ts ADDED
@@ -0,0 +1,31 @@
1
+ /**
2
+ * @luxfi/exchange - Core DEX logic for Lux Exchange
3
+ *
4
+ * Provides:
5
+ * - Chain and token definitions
6
+ * - AMM V2/V3 contract interfaces
7
+ * - DEX precompile integration
8
+ * - Swap/liquidity hooks
9
+ * - Zustand stores for state management
10
+ */
11
+
12
+ // Chains
13
+ export * from './chains'
14
+
15
+ // Tokens
16
+ export * from './tokens'
17
+
18
+ // Contracts & ABIs
19
+ export * from './contracts'
20
+
21
+ // DEX types
22
+ export * from './dex'
23
+
24
+ // Hooks
25
+ export * from './hooks'
26
+
27
+ // Stores
28
+ export * from './stores'
29
+
30
+ // Note: Bridge module is experimental and will be added in a future release
31
+ // See bridge-wip/ for the work-in-progress implementation
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Zustand stores for state management
3
+ */
4
+
5
+ export * from './swap-store'
6
+ export * from './token-store'
7
+ export * from './settings-store'
@@ -0,0 +1,54 @@
1
+ import { create } from 'zustand'
2
+ import { persist } from 'zustand/middleware'
3
+
4
+ export type RouterPreference = 'auto' | 'v2' | 'v3' | 'precompile'
5
+
6
+ export interface SettingsStore {
7
+ // Router preference
8
+ routerPreference: RouterPreference
9
+
10
+ // Expert mode (skip confirmation modals)
11
+ expertMode: boolean
12
+
13
+ // Auto slippage
14
+ autoSlippage: boolean
15
+
16
+ // Hide small balances
17
+ hideSmallBalances: boolean
18
+
19
+ // Small balance threshold (in USD)
20
+ smallBalanceThreshold: number
21
+
22
+ // Actions
23
+ setRouterPreference: (preference: RouterPreference) => void
24
+ toggleExpertMode: () => void
25
+ toggleAutoSlippage: () => void
26
+ toggleHideSmallBalances: () => void
27
+ setSmallBalanceThreshold: (threshold: number) => void
28
+ }
29
+
30
+ export const useSettingsStore = create<SettingsStore>()(
31
+ persist(
32
+ (set) => ({
33
+ routerPreference: 'auto',
34
+ expertMode: false,
35
+ autoSlippage: true,
36
+ hideSmallBalances: false,
37
+ smallBalanceThreshold: 1,
38
+
39
+ setRouterPreference: (preference) => set({ routerPreference: preference }),
40
+
41
+ toggleExpertMode: () => set((state) => ({ expertMode: !state.expertMode })),
42
+
43
+ toggleAutoSlippage: () => set((state) => ({ autoSlippage: !state.autoSlippage })),
44
+
45
+ toggleHideSmallBalances: () =>
46
+ set((state) => ({ hideSmallBalances: !state.hideSmallBalances })),
47
+
48
+ setSmallBalanceThreshold: (threshold) => set({ smallBalanceThreshold: threshold }),
49
+ }),
50
+ {
51
+ name: 'settings-storage',
52
+ }
53
+ )
54
+ )
@@ -0,0 +1,112 @@
1
+ import { create } from 'zustand'
2
+ import { persist } from 'zustand/middleware'
3
+ import type { Token } from '../tokens'
4
+
5
+ export type SwapField = 'input' | 'output'
6
+
7
+ export interface SwapState {
8
+ // Tokens
9
+ tokenIn: Token | null
10
+ tokenOut: Token | null
11
+
12
+ // Amounts (as strings for input fields)
13
+ amountIn: string
14
+ amountOut: string
15
+
16
+ // Which field is being typed in
17
+ independentField: SwapField
18
+
19
+ // Slippage tolerance (in basis points, e.g., 50 = 0.5%)
20
+ slippageTolerance: number
21
+
22
+ // Transaction deadline (in minutes)
23
+ deadline: number
24
+
25
+ // Actions
26
+ setTokenIn: (token: Token | null) => void
27
+ setTokenOut: (token: Token | null) => void
28
+ setAmountIn: (amount: string) => void
29
+ setAmountOut: (amount: string) => void
30
+ setIndependentField: (field: SwapField) => void
31
+ setSlippageTolerance: (tolerance: number) => void
32
+ setDeadline: (deadline: number) => void
33
+ switchTokens: () => void
34
+ reset: () => void
35
+ }
36
+
37
+ const initialState = {
38
+ tokenIn: null,
39
+ tokenOut: null,
40
+ amountIn: '',
41
+ amountOut: '',
42
+ independentField: 'input' as SwapField,
43
+ slippageTolerance: 50, // 0.5%
44
+ deadline: 30, // 30 minutes
45
+ }
46
+
47
+ export const useSwapStore = create<SwapState>()(
48
+ persist(
49
+ (set) => ({
50
+ ...initialState,
51
+
52
+ setTokenIn: (token) =>
53
+ set((state) => {
54
+ // If selecting same token as output, switch them
55
+ if (token && state.tokenOut && token.address === state.tokenOut.address) {
56
+ return {
57
+ tokenIn: token,
58
+ tokenOut: state.tokenIn,
59
+ }
60
+ }
61
+ return { tokenIn: token }
62
+ }),
63
+
64
+ setTokenOut: (token) =>
65
+ set((state) => {
66
+ // If selecting same token as input, switch them
67
+ if (token && state.tokenIn && token.address === state.tokenIn.address) {
68
+ return {
69
+ tokenOut: token,
70
+ tokenIn: state.tokenOut,
71
+ }
72
+ }
73
+ return { tokenOut: token }
74
+ }),
75
+
76
+ setAmountIn: (amount) =>
77
+ set({
78
+ amountIn: amount,
79
+ independentField: 'input',
80
+ }),
81
+
82
+ setAmountOut: (amount) =>
83
+ set({
84
+ amountOut: amount,
85
+ independentField: 'output',
86
+ }),
87
+
88
+ setIndependentField: (field) => set({ independentField: field }),
89
+
90
+ setSlippageTolerance: (tolerance) => set({ slippageTolerance: tolerance }),
91
+
92
+ setDeadline: (deadline) => set({ deadline: deadline }),
93
+
94
+ switchTokens: () =>
95
+ set((state) => ({
96
+ tokenIn: state.tokenOut,
97
+ tokenOut: state.tokenIn,
98
+ amountIn: state.amountOut,
99
+ amountOut: state.amountIn,
100
+ })),
101
+
102
+ reset: () => set(initialState),
103
+ }),
104
+ {
105
+ name: 'swap-storage',
106
+ partialize: (state) => ({
107
+ slippageTolerance: state.slippageTolerance,
108
+ deadline: state.deadline,
109
+ }),
110
+ }
111
+ )
112
+ )
@@ -0,0 +1,62 @@
1
+ import { create } from 'zustand'
2
+ import { persist } from 'zustand/middleware'
3
+ import type { Token } from '../tokens'
4
+
5
+ export interface TokenStore {
6
+ // Custom tokens added by user
7
+ customTokens: Token[]
8
+
9
+ // Favorite tokens
10
+ favoriteTokens: string[] // addresses
11
+
12
+ // Actions
13
+ addCustomToken: (token: Token) => void
14
+ removeCustomToken: (address: string) => void
15
+ toggleFavorite: (address: string) => void
16
+ isFavorite: (address: string) => boolean
17
+ }
18
+
19
+ export const useTokenStore = create<TokenStore>()(
20
+ persist(
21
+ (set, get) => ({
22
+ customTokens: [],
23
+ favoriteTokens: [],
24
+
25
+ addCustomToken: (token) =>
26
+ set((state) => {
27
+ // Check if token already exists
28
+ if (state.customTokens.some((t) => t.address.toLowerCase() === token.address.toLowerCase())) {
29
+ return state
30
+ }
31
+ return { customTokens: [...state.customTokens, token] }
32
+ }),
33
+
34
+ removeCustomToken: (address) =>
35
+ set((state) => ({
36
+ customTokens: state.customTokens.filter(
37
+ (t) => t.address.toLowerCase() !== address.toLowerCase()
38
+ ),
39
+ })),
40
+
41
+ toggleFavorite: (address) =>
42
+ set((state) => {
43
+ const normalized = address.toLowerCase()
44
+ if (state.favoriteTokens.includes(normalized)) {
45
+ return {
46
+ favoriteTokens: state.favoriteTokens.filter((a) => a !== normalized),
47
+ }
48
+ }
49
+ return {
50
+ favoriteTokens: [...state.favoriteTokens, normalized],
51
+ }
52
+ }),
53
+
54
+ isFavorite: (address) => {
55
+ return get().favoriteTokens.includes(address.toLowerCase())
56
+ },
57
+ }),
58
+ {
59
+ name: 'token-storage',
60
+ }
61
+ )
62
+ )