From 95234fc4566e1a938612013e8eea080cdc1992db Mon Sep 17 00:00:00 2001 From: James Date: Wed, 19 Nov 2025 21:03:28 +0100 Subject: [PATCH 01/10] maj packages sdk --- .turbo/cache/6a91baf0e64ff848-meta.json | 1 + .turbo/cache/6a91baf0e64ff848.tar.zst | Bin 0 -> 594 bytes .turbo/cache/819d866a1222082a-meta.json | 1 + .turbo/cache/819d866a1222082a.tar.zst | Bin 0 -> 1183 bytes .turbo/cache/f0be77d340149c0e-meta.json | 1 + .turbo/cache/f0be77d340149c0e.tar.zst | Bin 0 -> 7456 bytes .turbo/cache/f8b2adaa0614c467-meta.json | 1 + .turbo/cache/f8b2adaa0614c467.tar.zst | Bin 0 -> 344 bytes .turbo/cookies/10.cookie | 0 .turbo/cookies/11.cookie | 0 .turbo/cookies/12.cookie | 0 .turbo/cookies/2.cookie | 0 .turbo/cookies/3.cookie | 0 .turbo/cookies/4.cookie | 0 .turbo/cookies/5.cookie | 0 .turbo/cookies/6.cookie | 0 .turbo/cookies/7.cookie | 0 .turbo/cookies/8.cookie | 0 .turbo/cookies/9.cookie | 0 .../c9749efa7ac2748e-turbo.log.2025-11-19 | 0 apps/web/src/components/Layout.tsx | 2 + apps/web/src/components/NetworkSelect.tsx | 29 ++++++++++++ apps/web/src/lib/dynamic.tsx | 16 +++---- apps/web/src/lib/wagmi.ts | 42 +++++++++++------- packages/sdk/src/addresses/index.ts | 13 ++++-- .../sdk/src/addresses/intuition-mainnet.json | 10 +++++ packages/sdk/src/chains.ts | 28 +++++++++++- 27 files changed, 115 insertions(+), 29 deletions(-) create mode 100644 .turbo/cache/6a91baf0e64ff848-meta.json create mode 100644 .turbo/cache/6a91baf0e64ff848.tar.zst create mode 100644 .turbo/cache/819d866a1222082a-meta.json create mode 100644 .turbo/cache/819d866a1222082a.tar.zst create mode 100644 .turbo/cache/f0be77d340149c0e-meta.json create mode 100644 .turbo/cache/f0be77d340149c0e.tar.zst create mode 100644 .turbo/cache/f8b2adaa0614c467-meta.json create mode 100644 .turbo/cache/f8b2adaa0614c467.tar.zst create mode 100644 .turbo/cookies/10.cookie create mode 100644 .turbo/cookies/11.cookie create mode 100644 .turbo/cookies/12.cookie create mode 100644 .turbo/cookies/2.cookie create mode 100644 .turbo/cookies/3.cookie create mode 100644 .turbo/cookies/4.cookie create mode 100644 .turbo/cookies/5.cookie create mode 100644 .turbo/cookies/6.cookie create mode 100644 .turbo/cookies/7.cookie create mode 100644 .turbo/cookies/8.cookie create mode 100644 .turbo/cookies/9.cookie create mode 100644 .turbo/daemon/c9749efa7ac2748e-turbo.log.2025-11-19 create mode 100644 apps/web/src/components/NetworkSelect.tsx create mode 100644 packages/sdk/src/addresses/intuition-mainnet.json diff --git a/.turbo/cache/6a91baf0e64ff848-meta.json b/.turbo/cache/6a91baf0e64ff848-meta.json new file mode 100644 index 000000000..6e9134e2a --- /dev/null +++ b/.turbo/cache/6a91baf0e64ff848-meta.json @@ -0,0 +1 @@ +{"hash":"6a91baf0e64ff848","duration":2858} \ No newline at end of file diff --git a/.turbo/cache/6a91baf0e64ff848.tar.zst b/.turbo/cache/6a91baf0e64ff848.tar.zst new file mode 100644 index 0000000000000000000000000000000000000000..0f6b2937638b8e38fc64418f4029f128ea71338b GIT binary patch literal 594 zcmV-Y0qaS7~ z-|y4UG)1hb`*|eR)csJD)jjp2sT<5TdNES07<(T^dZSN`q`;&X5p&gM_=3Coe+{_9 zZ>#rBtGlwQ_JXHR0xpD7R{dXf|I ze(e<>3ML{Xi$mqANL-dmpKBL($)9W7>8Y9`6v}s#n;QZ{!=)hL^Dl`*srweMf-6NCwZ&h8@zeqd3@&4fMSYp|%%jg^bw$6tUkM z`5e{W1P{{)974ev8s5m(Y`L!`b(;*}9Jwg20Hy${02SZpAI_cY?z7fJS=W>>C>@UP zjKKphS=J>{($rg4X*ne9swB(8W;JKDwXMhF2Y4P4NH^Nn zVcwz$1quz}0phYhTb*Ac0?AyPw+5kI@RkwCZ_1E?AKm&t zyP1U}s$m>i&`2e8} z@C4Vs^~Ky1@p35X{!cn2rGz+0O!(%nzqX#;6E6RwH`{t~;a^=-(4hjF#jnK^G9v9-+j9L$gy7tn!i0Dcb$chhKeJ)+{0!d+Ih(`01z zjI~Nvv1#~@20sA10ZK^z!QEkni5&)I2ek7SG6M3~KhFSe`2)b|1FVqmvFo05xLV=n xgF$$;>M4>YkAjS3L&nqCQd9)iNee(R<_%tlDYdbHEjP!30UJo$gyN3^`vGEKGr#}< literal 0 HcmV?d00001 diff --git a/.turbo/cache/f0be77d340149c0e-meta.json b/.turbo/cache/f0be77d340149c0e-meta.json new file mode 100644 index 000000000..661251489 --- /dev/null +++ b/.turbo/cache/f0be77d340149c0e-meta.json @@ -0,0 +1 @@ +{"hash":"f0be77d340149c0e","duration":3880} \ No newline at end of file diff --git a/.turbo/cache/f0be77d340149c0e.tar.zst b/.turbo/cache/f0be77d340149c0e.tar.zst new file mode 100644 index 0000000000000000000000000000000000000000..170850eb1c72ee87971e563069e74ec190621bd3 GIT binary patch literal 7456 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
+
diff --git a/apps/web/src/components/NetworkSelect.tsx b/apps/web/src/components/NetworkSelect.tsx new file mode 100644 index 000000000..3c8a62fd5 --- /dev/null +++ b/apps/web/src/components/NetworkSelect.tsx @@ -0,0 +1,29 @@ +import React from "react"; +import { useChainId, useSwitchChain } from "wagmi"; +import { CHAINS } from "../lib/wagmi"; + +export function NetworkSelect() { + const chainId = useChainId(); + const { switchChain } = useSwitchChain(); + + const handleChange = (event: React.ChangeEvent) => { + const targetId = Number(event.target.value); + const targetChain = CHAINS.find((c) => c.id === targetId); + if (!targetChain) return; + switchChain({ chainId: targetChain.id }); + }; + + return ( + + ); +} diff --git a/apps/web/src/lib/dynamic.tsx b/apps/web/src/lib/dynamic.tsx index b424a5154..e35c88e6c 100644 --- a/apps/web/src/lib/dynamic.tsx +++ b/apps/web/src/lib/dynamic.tsx @@ -5,21 +5,21 @@ import { DynamicWagmiConnector } from "@dynamic-labs/wagmi-connector" import { WagmiProvider } from "wagmi" import { QueryClient, QueryClientProvider } from "@tanstack/react-query" import { wagmiConfig } from "./wagmi" -import { INTUITION } from "@trustswap/sdk" +import { intuitionTestnet } from "@trustswap/sdk" const queryClient = new QueryClient() function toDynamicEvmNetwork() { return { - chainId: INTUITION.id, - networkId: INTUITION.id, - name: INTUITION.name, + chainId: intuitionTestnet.id, + networkId: intuitionTestnet.id, + name: intuitionTestnet.name, vanityName: "Intuition", shortName: "intuition", - chainName: INTUITION.name, - rpcUrls: INTUITION.rpcUrls.default.http.slice(), - blockExplorerUrls: [INTUITION.blockExplorers?.default?.url].filter(Boolean) as string[], - nativeCurrency: INTUITION.nativeCurrency, + chainName: intuitionTestnet.name, + rpcUrls: intuitionTestnet.rpcUrls.default.http.slice(), + blockExplorerUrls: [intuitionTestnet.blockExplorers?.default?.url].filter(Boolean) as string[], + nativeCurrency: intuitionTestnet.nativeCurrency, testnet: true, iconUrls: [], } diff --git a/apps/web/src/lib/wagmi.ts b/apps/web/src/lib/wagmi.ts index 20dea0458..9ccb08121 100644 --- a/apps/web/src/lib/wagmi.ts +++ b/apps/web/src/lib/wagmi.ts @@ -1,22 +1,32 @@ -import { createConfig, http } from "wagmi" -import type { Chain } from "viem" -import { INTUITION } from "@trustswap/sdk" +import { createConfig, http } from "wagmi"; +import type { Chain } from "viem"; +import { intuitionTestnet, intuitionMainnet } from "@trustswap/sdk"; -const MULTICALL3 = import.meta.env.VITE_MULTICALL3 as `0x${string}` | undefined +const MULTICALL3 = import.meta.env.VITE_MULTICALL3 as `0x${string}` | undefined; -const BASE = INTUITION as unknown as Chain -const INTUITION_CHAIN: Chain = { - ...BASE, - contracts: { - ...(BASE.contracts ?? {}), - ...(MULTICALL3 ? { multicall3: { address: MULTICALL3, blockCreated: 0 } } : {}), - }, +function withMulticall(chain: Chain): Chain { + if (!MULTICALL3) return chain; + return { + ...chain, + contracts: { + ...(chain.contracts ?? {}), + multicall3: { + address: MULTICALL3, + blockCreated: 0, + }, + }, + }; } +export const CHAINS: Chain[] = [ + withMulticall(intuitionTestnet as unknown as Chain), + withMulticall(intuitionMainnet as unknown as Chain), +]; + export const wagmiConfig = createConfig({ - chains: [INTUITION_CHAIN], + chains: CHAINS, multiInjectedProviderDiscovery: false, - transports: { - [INTUITION_CHAIN.id]: http(INTUITION_CHAIN.rpcUrls.default.http[0]), - }, -}) + transports: Object.fromEntries( + CHAINS.map((chain) => [chain.id, http(chain.rpcUrls.default.http[0])]), + ), +}); diff --git a/packages/sdk/src/addresses/index.ts b/packages/sdk/src/addresses/index.ts index e9c4a12cf..ef124fa4f 100644 --- a/packages/sdk/src/addresses/index.ts +++ b/packages/sdk/src/addresses/index.ts @@ -1,4 +1,5 @@ import INTUITION_13579 from "./intuition-testnet.json" assert { type: "json" }; +import INTUITION_MAINNET from "./intuition-mainnet.json" assert { type: "json" }; export type HexAddr = `0x${string}`; @@ -13,11 +14,15 @@ export interface Addresses { StakingRewardsFactory: HexAddr; } +export const INTUITION_TESTNET_CHAIN_ID = 13579; +export const INTUITION_MAINNET_CHAIN_ID = 4242; + const BOOK: Record = { - 13579: INTUITION_13579 as Addresses + [INTUITION_TESTNET_CHAIN_ID]: INTUITION_13579 as Addresses, + [INTUITION_MAINNET_CHAIN_ID]: INTUITION_MAINNET as Addresses, }; -export function getAddresses(chainId: number = 13579): Addresses { +export function getAddresses(chainId: number = INTUITION_TESTNET_CHAIN_ID): Addresses { const entry = BOOK[chainId]; if (!entry) { throw new Error(`No addresses for chainId=${chainId}`); @@ -25,5 +30,5 @@ export function getAddresses(chainId: number = 13579): Addresses { return entry; } -// Raccourci par défaut -export const addresses = getAddresses(13579); +// Kept for backward compatibility, but frontend should prefer getAddresses(useChainId()) +export const addresses = getAddresses(INTUITION_TESTNET_CHAIN_ID); diff --git a/packages/sdk/src/addresses/intuition-mainnet.json b/packages/sdk/src/addresses/intuition-mainnet.json new file mode 100644 index 000000000..7d271ce24 --- /dev/null +++ b/packages/sdk/src/addresses/intuition-mainnet.json @@ -0,0 +1,10 @@ +{ + "UniswapV2Factory": "0x0000000000000000000000000000000000000000", + "UniswapV2Router02": "0x0000000000000000000000000000000000000000", + "router": "0x0000000000000000000000000000000000000000", + "deployer": "0x0000000000000000000000000000000000000000", + "NATIVE_PLACEHOLDER": "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE", + "WTTRUST": "0x0000000000000000000000000000000000000000", + "TSWP": "0x0000000000000000000000000000000000000000", + "StakingRewardsFactory": "0x0000000000000000000000000000000000000000" +} diff --git a/packages/sdk/src/chains.ts b/packages/sdk/src/chains.ts index bdc855657..8141e0305 100644 --- a/packages/sdk/src/chains.ts +++ b/packages/sdk/src/chains.ts @@ -1,7 +1,7 @@ // packages/sdk/src/chain.ts import { defineChain } from "viem"; -export const INTUITION = defineChain({ +export const intuitionTestnet = defineChain({ id: 13579, name: "Intuition Testnet", nativeCurrency: { name: "tTRUST", symbol: "tTRUST", decimals: 18 }, @@ -19,3 +19,29 @@ export const INTUITION = defineChain({ } } }); + + +export const intuitionMainnet = defineChain({ + id: 99999, // TODO: replace with real mainnet chain id + name: "Intuition Mainnet", + network: "intuition-mainnet", + nativeCurrency: { + name: "Trust", + symbol: "TRUST", + decimals: 18, + }, + rpcUrls: { + default: { + http: ["https://rpc.intuition.systems/http"], // TODO: mainnet RPC + }, + public: { + http: ["https://rpc.intuition.systems/http"], + }, + }, + blockExplorers: { + default: { + name: "Intuition Mainnet Explorer", + url: "https://explorer.intuition.systems", // TODO: mainnet explorer + }, + }, +}) \ No newline at end of file From 44dd14551f32029ed9f5517ddd59f9c157ac7bbf Mon Sep 17 00:00:00 2001 From: James Date: Wed, 19 Nov 2025 21:47:11 +0100 Subject: [PATCH 02/10] maj files lib --- .../features/swap/hooks/useQuoteDetails.ts | 63 ++-- apps/web/src/hooks/useTokenMeta.ts | 57 ++-- apps/web/src/hooks/useTokenModule.ts | 23 ++ apps/web/src/hooks/useTrustswapAddresses.ts | 10 + apps/web/src/lib/dynamic.tsx | 61 ++-- apps/web/src/lib/erc20Read.ts | 26 +- apps/web/src/lib/tokens.ts | 276 ++++++++++-------- 7 files changed, 314 insertions(+), 202 deletions(-) create mode 100644 apps/web/src/hooks/useTokenModule.ts create mode 100644 apps/web/src/hooks/useTrustswapAddresses.ts diff --git a/apps/web/src/features/swap/hooks/useQuoteDetails.ts b/apps/web/src/features/swap/hooks/useQuoteDetails.ts index e86b912a6..09a573663 100644 --- a/apps/web/src/features/swap/hooks/useQuoteDetails.ts +++ b/apps/web/src/features/swap/hooks/useQuoteDetails.ts @@ -1,23 +1,31 @@ +// web/src/hooks/swap/useQuoteDetails.ts import type { Address } from "viem"; import { parseUnits, formatUnits } from "viem"; import { usePublicClient } from "wagmi"; -import { getTokenByAddress, NATIVE_PLACEHOLDER } from "../../../lib/tokens"; -import { abi, addresses } from "@trustswap/sdk"; +import { abi } from "@trustswap/sdk"; +import { useTokenModule } from "../../../hooks/useTokenModule"; +import { useTrustswapAddresses } from "../../../hooks/useTrustswapAddresses"; -const ROUTER = addresses.UniswapV2Router02 as Address; -const WNATIVE = addresses.WTTRUST as Address; - -const isNative = (a?: Address) => - !!a && a.toLowerCase() === NATIVE_PLACEHOLDER.toLowerCase(); -const toWrapped = (a: Address) => (isNative(a) ? WNATIVE : a); - -function getDecimalsSafe(addr: Address) { +function getDecimalsSafe(addr: Address, isNative: (a?: Address) => boolean, getTokenByAddress: (addr: Address) => { decimals: number }) { if (isNative(addr)) return 18; - try { return getTokenByAddress(addr).decimals ?? 18; } catch { return 18; } + try { + return getTokenByAddress(addr).decimals ?? 18; + } catch { + return 18; + } } export function useQuoteDetails() { const pc = usePublicClient(); + const { UniswapV2Router02, WTTRUST } = useTrustswapAddresses(); + + const { + NATIVE_PLACEHOLDER, + WNATIVE_ADDRESS, + isNative, + toWrapped, + getTokenByAddress, + } = useTokenModule(); return async function getQuoteDetails( tokenIn: Address, @@ -30,31 +38,45 @@ export function useQuoteDetails() { decimalsOut: number; } | null> { if (!pc) return null; + const v = Number(String(amountInStr).replace(",", ".")); if (!isFinite(v) || v <= 0) return null; - const decimalsIn = getDecimalsSafe(tokenIn); - const decimalsOut = getDecimalsSafe(tokenOut); + const decimalsIn = getDecimalsSafe(tokenIn, isNative, getTokenByAddress); + const decimalsOut = getDecimalsSafe(tokenOut, isNative, getTokenByAddress); const amountIn = parseUnits(String(v), decimalsIn); - const direct = [toWrapped(tokenIn), toWrapped(tokenOut)] as Address[]; - const viaW = [toWrapped(tokenIn), WNATIVE, toWrapped(tokenOut)] as Address[]; + const router = UniswapV2Router02 as Address; + const wNativeFromAddrs = WTTRUST as Address; + const wNative = WNATIVE_ADDRESS ?? wNativeFromAddrs; + + const nativeEq = (a?: Address) => + !!a && a.toLowerCase() === NATIVE_PLACEHOLDER.toLowerCase(); + + const wrap = (a: Address) => + nativeEq(a) || isNative(a) ? wNative : a; + + const direct = [wrap(tokenIn), wrap(tokenOut)] as Address[]; + const viaW = [wrap(tokenIn), wNative, wrap(tokenOut)] as Address[]; const candidates: Address[][] = []; - const same = direct.length === viaW.length && direct.every((x, i) => x === viaW[i]); + const same = + direct.length === viaW.length && direct.every((x, i) => x === viaW[i]); candidates.push(direct); if (!same) candidates.push(viaW); const tryPath = async (path: Address[]) => { try { - const amounts = await pc.readContract({ - address: ROUTER, + const amounts = (await pc.readContract({ + address: router, abi: abi.UniswapV2Router02, functionName: "getAmountsOut", args: [amountIn, path], - }) as bigint[]; + })) as bigint[]; return { out: amounts[amounts.length - 1], path }; - } catch { return null; } + } catch { + return null; + } }; const quotes = await Promise.all(candidates.map(tryPath)); @@ -62,6 +84,7 @@ export function useQuoteDetails() { if (!valid.length) return null; const best = valid.sort((a, b) => (a.out < b.out ? 1 : -1))[0]; + return { amountOutFormatted: formatUnits(best.out, decimalsOut), amountOutBn: best.out, diff --git a/apps/web/src/hooks/useTokenMeta.ts b/apps/web/src/hooks/useTokenMeta.ts index 0bc402cc0..9505ee867 100644 --- a/apps/web/src/hooks/useTokenMeta.ts +++ b/apps/web/src/hooks/useTokenMeta.ts @@ -1,51 +1,48 @@ -// features/shared/hooks/useTokenMeta.ts +// web/src/hooks/useTokenMeta.ts import { useEffect, useState } from "react"; import type { Address } from "viem"; -import { getOrFetchToken, isNative, NATIVE_PLACEHOLDER } from "../lib/tokens"; +import { useTokenModule } from "./useTokenModule"; export type UIMeta = { - address: Address; // adresse "UI" (placeholder si natif) + address: Address; symbol: string; name?: string; - decimals: number; // 18 si natif - isNative?: boolean; // true si tTRUST + decimals: number; + isNative?: boolean; }; export function useTokenMeta(addr?: Address) { - const [state, setState] = useState<{ meta?: UIMeta; loading: boolean; error?: unknown }>({ + const { getTokenMetaSafe } = useTokenModule(); + + const [state, setState] = useState<{ + meta?: UIMeta; + loading: boolean; + error?: unknown; + }>({ loading: !!addr, }); useEffect(() => { let alive = true; + (async () => { - if (!addr) { if (alive) setState({ loading: false }); return; } + if (!addr) { + if (alive) setState({ loading: false }); + return; + } try { - if (isNative(addr)) { - if (!alive) return; - setState({ - loading: false, - meta: { - address: NATIVE_PLACEHOLDER, - symbol: "tTRUST", - name: "Native TRUST", - decimals: 18, - isNative: true, - }, - }); - return; - } - - const onchain = await getOrFetchToken(addr); + const info = await getTokenMetaSafe(addr); if (!alive) return; + setState({ loading: false, meta: { - address: addr, - symbol: onchain.symbol, - name: onchain.name, - decimals: Number(onchain.decimals ?? 18), + address: info.address, + symbol: info.symbol, + name: info.name, + decimals: info.decimals, + isNative: info.isNative, }, }); } catch (error) { @@ -54,8 +51,10 @@ export function useTokenMeta(addr?: Address) { } })(); - return () => { alive = false; }; - }, [addr]); + return () => { + alive = false; + }; + }, [addr, getTokenMetaSafe]); return state; } diff --git a/apps/web/src/hooks/useTokenModule.ts b/apps/web/src/hooks/useTokenModule.ts new file mode 100644 index 000000000..fb37fd681 --- /dev/null +++ b/apps/web/src/hooks/useTokenModule.ts @@ -0,0 +1,23 @@ +import { useMemo } from "react"; +import { useChainId } from "wagmi"; +import type { Chain } from "viem"; +import { intuitionTestnet, intuitionMainnet } from "@trustswap/sdk"; +import { getAddresses } from "@trustswap/sdk"; +import { createTokenModule } from "../lib/tokens"; // adapte le chemin si besoin + +const CHAINS_BY_ID: Record = { + [intuitionTestnet.id]: intuitionTestnet as unknown as Chain, + [intuitionMainnet.id]: intuitionMainnet as unknown as Chain, +}; + +export function useTokenModule() { + const chainId = useChainId() ?? intuitionTestnet.id; + + const chain = CHAINS_BY_ID[chainId] ?? CHAINS_BY_ID[intuitionTestnet.id]; + const addrBook = getAddresses(chainId); + + return useMemo( + () => createTokenModule(chain, addrBook), + [chain, addrBook], + ); +} diff --git a/apps/web/src/hooks/useTrustswapAddresses.ts b/apps/web/src/hooks/useTrustswapAddresses.ts new file mode 100644 index 000000000..c05569aa1 --- /dev/null +++ b/apps/web/src/hooks/useTrustswapAddresses.ts @@ -0,0 +1,10 @@ +// src/hooks/useTrustswapAddresses.ts +import { useChainId } from "wagmi"; +import { getAddresses } from "@trustswap/sdk"; + +const FALLBACK_CHAIN_ID = 13579; + +export function useTrustswapAddresses() { + const chainId = useChainId() || FALLBACK_CHAIN_ID; + return getAddresses(chainId); +} diff --git a/apps/web/src/lib/dynamic.tsx b/apps/web/src/lib/dynamic.tsx index e35c88e6c..573a1962c 100644 --- a/apps/web/src/lib/dynamic.tsx +++ b/apps/web/src/lib/dynamic.tsx @@ -1,37 +1,48 @@ -import type { PropsWithChildren } from "react" -import { DynamicContextProvider } from "@dynamic-labs/sdk-react-core" -import { EthereumWalletConnectors } from "@dynamic-labs/ethereum" -import { DynamicWagmiConnector } from "@dynamic-labs/wagmi-connector" -import { WagmiProvider } from "wagmi" -import { QueryClient, QueryClientProvider } from "@tanstack/react-query" -import { wagmiConfig } from "./wagmi" -import { intuitionTestnet } from "@trustswap/sdk" +import type { PropsWithChildren } from "react"; +import { DynamicContextProvider } from "@dynamic-labs/sdk-react-core"; +import { EthereumWalletConnectors } from "@dynamic-labs/ethereum"; +import { DynamicWagmiConnector } from "@dynamic-labs/wagmi-connector"; +import { WagmiProvider } from "wagmi"; +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; -const queryClient = new QueryClient() +import { wagmiConfig } from "./wagmi"; +import { intuitionTestnet, intuitionMainnet } from "@trustswap/sdk"; -function toDynamicEvmNetwork() { +const queryClient = new QueryClient(); + +function toDynamicEvmNetwork(chain: typeof intuitionTestnet | typeof intuitionMainnet, opts: { testnet: boolean }) { return { - chainId: intuitionTestnet.id, - networkId: intuitionTestnet.id, - name: intuitionTestnet.name, + chainId: chain.id, + networkId: chain.id, + name: chain.name, vanityName: "Intuition", shortName: "intuition", - chainName: intuitionTestnet.name, - rpcUrls: intuitionTestnet.rpcUrls.default.http.slice(), - blockExplorerUrls: [intuitionTestnet.blockExplorers?.default?.url].filter(Boolean) as string[], - nativeCurrency: intuitionTestnet.nativeCurrency, - testnet: true, + chainName: chain.name, + rpcUrls: chain.rpcUrls.default.http.slice(), + blockExplorerUrls: [chain.blockExplorers?.default?.url].filter(Boolean) as string[], + nativeCurrency: chain.nativeCurrency, + testnet: opts.testnet, iconUrls: [], - } + }; } +const dynamicEvmNetworks = [ + toDynamicEvmNetwork(intuitionTestnet, { testnet: true }), + toDynamicEvmNetwork(intuitionMainnet, { testnet: false }), +]; + export function RootProviders({ children }: PropsWithChildren) { - // Prefer env var, fallback to hard-coded id - const envId = (import.meta.env.VITE_DYNAMIC_ENV_ID as string | undefined) ?? "78601171-b1f9-42d1-b651-b76f97becab7" + const envId = + (import.meta.env.VITE_DYNAMIC_ENV_ID as string | undefined) ?? + "78601171-b1f9-42d1-b651-b76f97becab7"; if (!envId) { - console.error("Missing Dynamic environmentId") - return
Wallet connect disabled: missing Dynamic environmentId.
+ console.error("Missing Dynamic environmentId"); + return ( +
+ Wallet connect disabled: missing Dynamic environmentId. +
+ ); } return ( @@ -39,7 +50,7 @@ export function RootProviders({ children }: PropsWithChildren) { settings={{ environmentId: envId, walletConnectors: [EthereumWalletConnectors], - overrides: { evmNetworks: [toDynamicEvmNetwork()] }, + overrides: { evmNetworks: dynamicEvmNetworks }, }} > @@ -48,5 +59,5 @@ export function RootProviders({ children }: PropsWithChildren) { - ) + ); } diff --git a/apps/web/src/lib/erc20Read.ts b/apps/web/src/lib/erc20Read.ts index 5096ad328..ddc7a0857 100644 --- a/apps/web/src/lib/erc20Read.ts +++ b/apps/web/src/lib/erc20Read.ts @@ -1,14 +1,20 @@ -// lib/erc20Read.ts +// src/lib/useErc20Read.ts import type { Address } from "viem"; -import { NATIVE_PLACEHOLDER, WNATIVE_ADDRESS } from "./tokens"; // ajuste le chemin +import { useTokenModule } from "../hooks/useTokenModule"; -export function toERC20ForRead(addr?: Address): Address | undefined { - if (!addr) return undefined; - return addr.toLowerCase() === NATIVE_PLACEHOLDER.toLowerCase() - ? (WNATIVE_ADDRESS as Address) - : addr; -} +export function useErc20Read() { + const { NATIVE_PLACEHOLDER, WNATIVE_ADDRESS } = useTokenModule(); + + function toERC20ForRead(addr?: Address): Address | undefined { + if (!addr) return undefined; + return addr.toLowerCase() === NATIVE_PLACEHOLDER.toLowerCase() + ? (WNATIVE_ADDRESS as Address) + : addr; + } + + function isZeroAddress(addr: Address) { + return addr === "0x0000000000000000000000000000000000000000"; + } -export function isZeroAddress(addr: Address) { - return addr === "0x0000000000000000000000000000000000000000"; + return { toERC20ForRead, isZeroAddress }; } diff --git a/apps/web/src/lib/tokens.ts b/apps/web/src/lib/tokens.ts index bc88b6329..5827a8a24 100644 --- a/apps/web/src/lib/tokens.ts +++ b/apps/web/src/lib/tokens.ts @@ -1,150 +1,190 @@ -import type { Address } from "viem"; -import { INTUITION, addresses } from "@trustswap/sdk"; +import type { Address, Chain } from "viem"; +import type { Addresses } from "@trustswap/sdk"; import { createPublicClient, http, erc20Abi } from "viem"; -export const NATIVE_PLACEHOLDER = addresses.NATIVE_PLACEHOLDER as Address; -export const WNATIVE_ADDRESS = addresses.WTTRUST as Address; - - export type TokenInfo = { address: Address; symbol: string; name: string; decimals: number; isNative?: boolean; - hidden?: boolean; // 👈 ajouté + hidden?: boolean; }; -export const TOKENLIST: TokenInfo[] = [ - { - address: NATIVE_PLACEHOLDER, - symbol: INTUITION?.nativeCurrency?.symbol ?? "tTRUST", - name: INTUITION?.nativeCurrency?.name ?? "Native TRUST", - decimals: 18, - isNative: true, - }, - { - address: addresses.TSWP as Address, - symbol: "TSWP", - name: "TrustSwap", - decimals: 18, - }, - { - address: WNATIVE_ADDRESS, - symbol: "WTTRUST", - name: "Wrapped TRUST", - decimals: 18, - hidden: false, - }, -]; - - -const TOKEN_CACHE: Record = {}; -for (const t of TOKENLIST) TOKEN_CACHE[t.address.toLowerCase()] = t; - -const client = createPublicClient({ - chain: INTUITION, - transport: http(INTUITION.rpcUrls?.default?.http?.[0] || ""), -}); - -function findToken(addr: string): TokenInfo | null { - return TOKEN_CACHE[addr.toLowerCase()] || null; -} +type TokenModule = { + NATIVE_PLACEHOLDER: Address; + WNATIVE_ADDRESS: Address; + TOKENLIST: TokenInfo[]; + isNative: (addr?: string) => boolean; + isWrapped: (addr?: string) => boolean; + toWrapped: (addr: Address) => Address; + buildPath: (path: Address[]) => Address[]; + getTokenByAddress: (addr: string | Address) => TokenInfo; + getTokenByAddressOrFallback: (addr: Address) => TokenInfo; + getDefaultPair: () => { tokenIn: TokenInfo; tokenOut: TokenInfo }; + getOrFetchToken: (address: Address) => Promise; + getTokenMetaSafe: (addr: Address) => Promise; + toUIAddress: (addr?: Address) => Address | undefined; + toUIList: (list: TokenInfo[]) => TokenInfo[]; + getTokenForUI: (addr?: Address) => TokenInfo | null; +}; +export function createTokenModule(chain: Chain, addrBook: Addresses): TokenModule { + const NATIVE_PLACEHOLDER = addrBook.NATIVE_PLACEHOLDER as Address; + const WNATIVE_ADDRESS = addrBook.WTTRUST as Address; -export const isNative = (addr?: string) => - !!addr && addr.toLowerCase() === NATIVE_PLACEHOLDER.toLowerCase(); + const TOKENLIST: TokenInfo[] = [ + { + address: NATIVE_PLACEHOLDER, + symbol: chain.nativeCurrency?.symbol ?? "tTRUST", + name: chain.nativeCurrency?.name ?? "Native TRUST", + decimals: 18, + isNative: true, + }, + { + address: addrBook.TSWP as Address, + symbol: "TSWP", + name: "TrustSwap", + decimals: 18, + }, + { + address: WNATIVE_ADDRESS, + symbol: "WTTRUST", + name: "Wrapped TRUST", + decimals: 18, + hidden: false, + }, + ]; -export const toWrapped = (addr: Address): Address => - isNative(addr) ? WNATIVE_ADDRESS : addr; + const TOKEN_CACHE: Record = {}; + for (const t of TOKENLIST) TOKEN_CACHE[t.address.toLowerCase()] = t; -export const buildPath = (path: Address[]): Address[] => - path.map(toWrapped) as Address[]; + const client = createPublicClient({ + chain, + transport: http(chain.rpcUrls.default.http[0]), + }); -export function getTokenByAddress(addr: string | Address): TokenInfo { - const t = TOKENLIST.find( - t => t.address.toLowerCase() === addr.toLowerCase() - ); - if (!t) throw new Error(`Token not in tokenlist: ${addr}`); - return t; -} + function findToken(addr: string): TokenInfo | null { + return TOKEN_CACHE[addr.toLowerCase()] || null; + } -export function getDefaultPair(): { tokenIn: TokenInfo; tokenOut: TokenInfo } { - const native = TOKENLIST.find(t => t.isNative) ?? TOKENLIST[0]; - const other = TOKENLIST.find(t => t.address !== native.address && !t.hidden) ?? native; - return { tokenIn: native, tokenOut: other }; -} + const isNative = (addr?: string) => + !!addr && addr.toLowerCase() === NATIVE_PLACEHOLDER.toLowerCase(); -export async function getOrFetchToken(address: Address): Promise { - const cached = findToken(address); - if (cached) return cached; - - const [symbol, decimals, name] = await Promise.all([ - client.readContract({ address, abi: erc20Abi, functionName: "symbol" }).catch(() => "TKN"), - client.readContract({ address, abi: erc20Abi, functionName: "decimals" }).catch(() => 18), - client.readContract({ address, abi: erc20Abi, functionName: "name" }).catch(() => "Unknown"), - ]); - - const info: TokenInfo = { - address, - symbol: String(symbol), - name: String(name), - decimals: Number(decimals), - }; + const toWrapped = (addr: Address): Address => + isNative(addr) ? WNATIVE_ADDRESS : addr; - TOKEN_CACHE[address.toLowerCase()] = info; - return info; -} + const buildPath = (path: Address[]): Address[] => + path.map(toWrapped) as Address[]; -export const isWrapped = (addr?: string) => - !!addr && addr.toLowerCase() === WNATIVE_ADDRESS.toLowerCase(); + const isWrapped = (addr?: string) => + !!addr && addr.toLowerCase() === WNATIVE_ADDRESS.toLowerCase(); -/** Adresse à afficher en UI: WTTRUST → tTRUST (placeholder) */ -export const toUIAddress = (addr?: Address): Address | undefined => - !addr ? undefined : isWrapped(addr) ? NATIVE_PLACEHOLDER : addr; + const toUIAddress = (addr?: Address): Address | undefined => + !addr ? undefined : isWrapped(addr) ? NATIVE_PLACEHOLDER : addr; -/** Liste de tokens pour l'UI (on masque les hidden = WTTRUST) */ -export function toUIList(list: TokenInfo[]): TokenInfo[] { - return list.filter(t => !t.hidden); -} + function getTokenByAddress(addr: string | Address): TokenInfo { + const t = TOKENLIST.find( + t => t.address.toLowerCase() === addr.toLowerCase(), + ); + if (!t) throw new Error(`Token not in tokenlist: ${addr}`); + return t; + } -/** Récupérer un TokenInfo pour affichage, en tenant compte du mapping UI */ -export function getTokenForUI(addr?: Address): TokenInfo | null { - if (!addr) return null; - const uiAddr = toUIAddress(addr)!; - const t = TOKENLIST.find(x => x.address.toLowerCase() === uiAddr.toLowerCase()); - return t ?? null; -} + function getDefaultPair(): { tokenIn: TokenInfo; tokenOut: TokenInfo } { + const native = TOKENLIST.find(t => t.isNative) ?? TOKENLIST[0]; + const other = + TOKENLIST.find(t => t.address !== native.address && !t.hidden) ?? + native; + return { tokenIn: native, tokenOut: other }; + } + + async function getOrFetchToken(address: Address): Promise { + const cached = findToken(address); + if (cached) return cached; + + const [symbol, decimals, name] = await Promise.all([ + client + .readContract({ address, abi: erc20Abi, functionName: "symbol" }) + .catch(() => "TKN"), + client + .readContract({ address, abi: erc20Abi, functionName: "decimals" }) + .catch(() => 18), + client + .readContract({ address, abi: erc20Abi, functionName: "name" }) + .catch(() => "Unknown"), + ]); + + const info: TokenInfo = { + address, + symbol: String(symbol), + name: String(name), + decimals: Number(decimals), + }; + + TOKEN_CACHE[address.toLowerCase()] = info; + return info; + } + + function toUIList(list: TokenInfo[]): TokenInfo[] { + return list.filter(t => !t.hidden); + } + + function getTokenForUI(addr?: Address): TokenInfo | null { + if (!addr) return null; + const uiAddr = toUIAddress(addr)!; + const t = TOKENLIST.find( + x => x.address.toLowerCase() === uiAddr.toLowerCase(), + ); + return t ?? null; + } + + async function getTokenMetaSafe(addr: Address): Promise { + if (isNative(addr)) { + return { + address: NATIVE_PLACEHOLDER, + symbol: chain.nativeCurrency?.symbol ?? "tTRUST", + name: chain.nativeCurrency?.name ?? "Native TRUST", + decimals: 18, + isNative: true, + }; + } + + const cached = TOKEN_CACHE[addr.toLowerCase()]; + if (cached) return cached; + + return await getOrFetchToken(addr); + } + function getTokenByAddressOrFallback(addr: Address): TokenInfo { + const hit = TOKENLIST.find( + t => t.address.toLowerCase() === addr.toLowerCase(), + ); + if (hit) return hit; -export async function getTokenMetaSafe(addr: Address): Promise { - // natif - if (isNative(addr)) { return { - address: NATIVE_PLACEHOLDER, - symbol: INTUITION?.nativeCurrency?.symbol ?? "tTRUST", - name: INTUITION?.nativeCurrency?.name ?? "Native TRUST", + address: addr, + symbol: "UNK", + name: "Unknown", decimals: 18, - isNative: true, }; } - // cache/local list - const cached = TOKEN_CACHE[addr.toLowerCase()]; - if (cached) return cached; - - // on-chain (ne throw pas — a déjà des catchs) - return await getOrFetchToken(addr); -} - -export function getTokenByAddressOrFallback(addr: Address): TokenInfo { - const hit = TOKENLIST.find(t => t.address.toLowerCase() === addr.toLowerCase()); - if (hit) return hit; - // fallback neutre: ne JAMAIS throw côté UI return { - address: addr, - symbol: "UNK", - name: "Unknown", - decimals: 18, + NATIVE_PLACEHOLDER, + WNATIVE_ADDRESS, + TOKENLIST, + isNative, + isWrapped, + toWrapped, + buildPath, + getTokenByAddress, + getTokenByAddressOrFallback, + getDefaultPair, + getOrFetchToken, + getTokenMetaSafe, + toUIAddress, + toUIList, + getTokenForUI, }; } From 9a244e32dec9e42535d81ca5990d522ec053a454 Mon Sep 17 00:00:00 2001 From: James Date: Wed, 19 Nov 2025 23:39:08 +0100 Subject: [PATCH 03/10] maj import lib/token --- .../src/features/pool/components/PoolRow.tsx | 4 +- .../features/pool/components/PoolsPage.tsx | 3 +- .../liquidity/AddLiquidityDrawer.tsx | 7 ++- .../liquidity/RemoveLiquidityDrawer.tsx | 5 +- .../src/features/pool/hooks/useGlobalStats.ts | 4 +- .../pool/hooks/useLiquidityActions.ts | 3 +- .../src/features/pool/hooks/useLpPosition.ts | 14 +++-- .../src/features/pool/hooks/usePairMetrics.ts | 3 +- .../features/pool/hooks/usePairsVolume1D.ts | 4 +- .../src/features/pool/hooks/usePoolsData.ts | 4 +- .../src/features/pool/hooks/useStakingData.ts | 42 +++++++-------- .../components/PoolPositionsTable.tsx | 4 +- .../components/TokenHoldingsTable.tsx | 4 +- .../features/portfolio/hooks/usePortfolio.ts | 9 +--- .../src/features/swap/components/SwapForm.tsx | 30 +++++++---- .../swap/components/TokenSelector.tsx | 4 +- .../src/features/swap/hooks/useAllowance.ts | 10 ++-- .../swap/hooks/useDynamicTokenList.ts | 10 ++-- .../src/features/swap/hooks/useGasEstimate.ts | 4 +- .../src/features/swap/hooks/usePairData.ts | 8 ++- .../src/features/swap/hooks/usePriceImpact.ts | 51 ++++++++++--------- apps/web/src/features/swap/hooks/useSwap.ts | 3 +- .../features/swap/hooks/useTokenBalance.ts | 16 +++--- apps/web/src/hooks/useTokenModule.ts | 3 +- 24 files changed, 145 insertions(+), 104 deletions(-) diff --git a/apps/web/src/features/pool/components/PoolRow.tsx b/apps/web/src/features/pool/components/PoolRow.tsx index 75052032a..f3da2e9fa 100644 --- a/apps/web/src/features/pool/components/PoolRow.tsx +++ b/apps/web/src/features/pool/components/PoolRow.tsx @@ -8,10 +8,10 @@ import { Volume1DCell } from "./cells/Volume1DCell"; import { PoolAprCell } from "./cells/PoolAprCell"; import { PoolActionsCell } from "./cells/PoolActionsCell"; import styles from "../tableau.module.css"; - -import { WNATIVE_ADDRESS } from "../../../lib/tokens"; +import { useTokenModule } from "../../../hooks/useTokenModule"; function asUIToken(t: T): T { + const { WNATIVE_ADDRESS } = useTokenModule(); const isWNative = t?.address?.toLowerCase() === WNATIVE_ADDRESS.toLowerCase(); if (!isWNative) return t; return { ...t, symbol: "tTRUST" } as T; diff --git a/apps/web/src/features/pool/components/PoolsPage.tsx b/apps/web/src/features/pool/components/PoolsPage.tsx index d847befa8..33757af90 100644 --- a/apps/web/src/features/pool/components/PoolsPage.tsx +++ b/apps/web/src/features/pool/components/PoolsPage.tsx @@ -8,7 +8,7 @@ import { PoolsTable } from "./PoolsTable"; import { PoolsFilters } from "./filters/PoolsFilters"; import { PoolsPagination } from "./filters/PoolsPagination"; import { LiquidityModal } from "./liquidity/LiquidityModal"; -import { toUIAddress } from "../../../lib/tokens"; +import { useTokenModule } from "../../../hooks/useTokenModule"; import styles from "../pools.module.css"; @@ -21,6 +21,7 @@ export default function PoolsPage() { const [isOpen, setIsOpen] = useState(false); const [tokenA, setTokenA] = useState
(); const [tokenB, setTokenB] = useState
(); + const { toUIAddress } = useTokenModule(); const pc = usePublicClient({ chainId: 13579 }); diff --git a/apps/web/src/features/pool/components/liquidity/AddLiquidityDrawer.tsx b/apps/web/src/features/pool/components/liquidity/AddLiquidityDrawer.tsx index b47471bcf..9932df88d 100644 --- a/apps/web/src/features/pool/components/liquidity/AddLiquidityDrawer.tsx +++ b/apps/web/src/features/pool/components/liquidity/AddLiquidityDrawer.tsx @@ -3,13 +3,14 @@ import type { Address } from "viem"; import { parseUnits } from "viem"; import { useAccount, usePublicClient } from "wagmi"; import { useLiquidityActions } from "../../hooks/useLiquidityActions"; -import { toWrapped } from "../../../../lib/tokens"; import { getTokenIcon } from "../../../../lib/getTokenIcon"; import styles from "../../modal.module.css"; import TokenField from "../../../swap/components/TokenField"; import { quoteOutFromReserves } from "../../../../utils/quotes"; import { abi, addresses } from "@trustswap/sdk"; -import { isZeroAddress } from "../../../../lib/erc20Read"; +import { useErc20Read } from "../../../../lib/erc20Read"; +import { useTokenModule } from "../../../../hooks/useTokenModule"; + type PairData = { pair: Address; @@ -34,6 +35,8 @@ export function AddLiquidityDrawer({ }) { const { address: to } = useAccount(); const pc = usePublicClient(); + const { toWrapped } = useTokenModule(); + const { isZeroAddress } = useErc20Read(); const { addLiquidity } = useLiquidityActions(); const [tokenIn, setTokenIn] = useState
(tokenA); diff --git a/apps/web/src/features/pool/components/liquidity/RemoveLiquidityDrawer.tsx b/apps/web/src/features/pool/components/liquidity/RemoveLiquidityDrawer.tsx index 32a7d9be2..777e877ff 100644 --- a/apps/web/src/features/pool/components/liquidity/RemoveLiquidityDrawer.tsx +++ b/apps/web/src/features/pool/components/liquidity/RemoveLiquidityDrawer.tsx @@ -5,7 +5,8 @@ import { formatUnits, parseUnits } from "viem"; import styles from "../../modal.module.css"; import { clampDecimalsForInput, tidyOnBlur } from "../../../../utils/number"; import { useLiquidityActions } from "../../hooks/useLiquidityActions"; -import { toWrapped } from "../../../../lib/tokens"; +import { useTokenModule } from "../../../../hooks/useTokenModule"; + import { getTokenIcon } from "../../../../lib/getTokenIcon"; import { useLpPosition } from "../../hooks/useLpPosition"; import { fmtUnits, formatAmountStr } from "../../utils"; @@ -29,6 +30,8 @@ export function RemoveLiquidityDrawer({ const [lpAmount, setLpAmount] = useState(""); const [lpRawOverride, setLpRawOverride] = useState(null); + const { toWrapped } = useTokenModule(); + // Position LP réelle const { diff --git a/apps/web/src/features/pool/hooks/useGlobalStats.ts b/apps/web/src/features/pool/hooks/useGlobalStats.ts index 46e0db30d..d6db7a2bc 100644 --- a/apps/web/src/features/pool/hooks/useGlobalStats.ts +++ b/apps/web/src/features/pool/hooks/useGlobalStats.ts @@ -4,7 +4,7 @@ import { parseAbiItem } from "viem"; import { usePublicClient } from "wagmi"; import { addresses } from "@trustswap/sdk"; import * as SDKAbi from "@trustswap/sdk/abi"; -import { WNATIVE_ADDRESS } from "../../../lib/tokens"; +import { useTokenModule } from "../../../hooks/useTokenModule"; function toAbi(x: unknown): Abi { return (Array.isArray(x) ? x : (x as any)?.abi) as Abi; @@ -30,6 +30,7 @@ export function useGlobalStats() { const [data, setData] = useState<{ tvlWT: bigint; vol24hWT: bigint; tx24h: number } | null>(null); const [loading, setLoading] = useState(true); const [error, setErr] = useState(null); + const { WNATIVE_ADDRESS } = useTokenModule(); useEffect(() => { (async () => { @@ -80,6 +81,7 @@ export function useGlobalStats() { ])), }); + const w = WNATIVE_ADDRESS.toLowerCase(); const metas: PairMeta[] = []; const pairIndex: Record = {}; diff --git a/apps/web/src/features/pool/hooks/useLiquidityActions.ts b/apps/web/src/features/pool/hooks/useLiquidityActions.ts index 214609023..dcc8d95a8 100644 --- a/apps/web/src/features/pool/hooks/useLiquidityActions.ts +++ b/apps/web/src/features/pool/hooks/useLiquidityActions.ts @@ -3,8 +3,8 @@ import { useWalletClient, usePublicClient, useChainId } from "wagmi"; import type { Address, Abi } from "viem"; import { erc20Abi, maxUint256, parseGwei, zeroAddress } from "viem"; import { addresses } from "@trustswap/sdk"; -import { toWrapped, WNATIVE_ADDRESS } from "../../../lib/tokens"; import { useAlerts } from "../../../features/alerts/Alerts"; +import { useTokenModule } from "../../../hooks/useTokenModule"; // --- Réseau / addresses const ROUTER = addresses.UniswapV2Router02 as Address; @@ -158,6 +158,7 @@ export function useLiquidityActions() { const publicClient = usePublicClient(); const chainId = useChainId(); const alerts = useAlerts(); + const { toWrapped, WNATIVE_ADDRESS } = useTokenModule(); async function estimateOverrides(base: { address: Address; diff --git a/apps/web/src/features/pool/hooks/useLpPosition.ts b/apps/web/src/features/pool/hooks/useLpPosition.ts index 700e41ab0..3445b1e78 100644 --- a/apps/web/src/features/pool/hooks/useLpPosition.ts +++ b/apps/web/src/features/pool/hooks/useLpPosition.ts @@ -4,13 +4,14 @@ import type { Address } from "viem"; import { useAccount, usePublicClient } from "wagmi"; import { erc20Abi, formatUnits, zeroAddress } from "viem"; import { abi, addresses } from "@trustswap/sdk"; -import { - NATIVE_PLACEHOLDER, - WNATIVE_ADDRESS, - getOrFetchToken, -} from "../../../lib/tokens"; + +import { useTokenModule } from "../../../hooks/useTokenModule"; + + function toERC20ForRead(addr?: Address): Address | undefined { + const { NATIVE_PLACEHOLDER, WNATIVE_ADDRESS } = useTokenModule(); + if (!addr) return undefined; return addr.toLowerCase() === NATIVE_PLACEHOLDER.toLowerCase() ? (WNATIVE_ADDRESS as Address) @@ -49,6 +50,9 @@ export function useLpPosition(tokenA?: Address, tokenB?: Address): LpPosition { const pc = usePublicClient(); // ✅ pas de chainId forcé ici const { address: owner } = useAccount(); + const { getOrFetchToken } = useTokenModule(); + + // adresses “lecture” (toujours ERC-20) const readA = toERC20ForRead(tokenA); const readB = toERC20ForRead(tokenB); diff --git a/apps/web/src/features/pool/hooks/usePairMetrics.ts b/apps/web/src/features/pool/hooks/usePairMetrics.ts index b9f97df13..210790297 100644 --- a/apps/web/src/features/pool/hooks/usePairMetrics.ts +++ b/apps/web/src/features/pool/hooks/usePairMetrics.ts @@ -3,7 +3,7 @@ import { useMemo } from "react"; import type { PoolItem } from "../types"; import { aprFromFees } from "../utils"; import { formatUnits } from "viem"; -import { WNATIVE_ADDRESS } from "../../../lib/tokens"; +import { useTokenModule } from "../../../hooks/useTokenModule"; function safeUnits(x: unknown, decimals: number): number { try { @@ -19,6 +19,7 @@ export function usePairMetrics( volMap: Record = {}, priceMap: Record = {} ) { + const { WNATIVE_ADDRESS } = useTokenModule(); const w = WNATIVE_ADDRESS.toLowerCase(); return useMemo(() => { diff --git a/apps/web/src/features/pool/hooks/usePairsVolume1D.ts b/apps/web/src/features/pool/hooks/usePairsVolume1D.ts index 378bf42b1..844ebf473 100644 --- a/apps/web/src/features/pool/hooks/usePairsVolume1D.ts +++ b/apps/web/src/features/pool/hooks/usePairsVolume1D.ts @@ -2,7 +2,8 @@ import { useEffect, useMemo, useRef, useState } from "react"; import { parseAbiItem, formatUnits } from "viem"; import { usePublicClient } from "wagmi"; -import { WNATIVE_ADDRESS } from "../../../lib/tokens"; +import { useTokenModule } from "../../../hooks/useTokenModule"; + import type { PoolItem } from "../types"; // Adjust the path if PoolItem is defined elsewhere const swapEvent = parseAbiItem( @@ -14,6 +15,7 @@ export function usePairsVolume1D(items: PoolItem[]) { const [volMap, setVolMap] = useState>({}); const [priceMap, setPriceMap] = useState>({}); const runIdRef = useRef(0); + const { WNATIVE_ADDRESS } = useTokenModule(); // 🔑 clé stable quand les paires changent const pairsKey = useMemo(() => { diff --git a/apps/web/src/features/pool/hooks/usePoolsData.ts b/apps/web/src/features/pool/hooks/usePoolsData.ts index f096403bc..a2c9c1ddb 100644 --- a/apps/web/src/features/pool/hooks/usePoolsData.ts +++ b/apps/web/src/features/pool/hooks/usePoolsData.ts @@ -3,7 +3,8 @@ import { useEffect, useRef, useState } from "react"; import { type Address, type Abi } from "viem"; import { usePublicClient } from "wagmi"; import { abi, addresses } from "@trustswap/sdk"; -import { getOrFetchToken } from "../../../lib/tokens"; +import { useTokenModule } from "../../../hooks/useTokenModule"; + import type { PoolItem } from "../types"; const chunk = (a: T[], n = 300) => @@ -20,6 +21,7 @@ export function usePoolsData(limit = 50, offset = 0) { const [error, setError] = useState(null); const [items, setItems] = useState([]); const runIdRef = useRef(0); + const { getOrFetchToken } = useTokenModule(); useEffect(() => { if (!pc) { dbg("no public client"); return; } diff --git a/apps/web/src/features/pool/hooks/useStakingData.ts b/apps/web/src/features/pool/hooks/useStakingData.ts index 08d0ae606..631099d5a 100644 --- a/apps/web/src/features/pool/hooks/useStakingData.ts +++ b/apps/web/src/features/pool/hooks/useStakingData.ts @@ -5,13 +5,13 @@ import type { Address, Abi } from "viem"; import { erc20Abi, formatUnits, zeroAddress } from "viem"; import { addresses } from "@trustswap/sdk"; import type { PoolItem } from "../types"; -import { getOrFetchToken, WNATIVE_ADDRESS } from "../../../lib/tokens"; +import { useTokenModule } from "../../../hooks/useTokenModule"; + import { FARMS } from "../../../lib/farms"; import { useLiveRegister } from "../../../live/LiveRefetchProvider"; const SEC_PER_YEAR = 31_536_000; const FEE_TO_LPS = 0.003; // 0.3% fees distributed to LPs - // --- ABIs --- const STAKING_ABI = [ { type: "function", name: "rewardRate", stateMutability: "view", inputs: [], outputs: [{ type: "uint256" }] }, @@ -34,28 +34,30 @@ const PAIR_ABI = [ ] }, ] as const satisfies Abi; -type StakingSlice = { - staking: Address | null; - rewardToken?: Awaited>; - rewardRatePerSec?: bigint; - earned?: bigint; - stakedBalance?: bigint; // user staked LP - walletLpBalance?: bigint; // user wallet LP (outside farm) - periodFinish?: bigint; // timestamp (sec) - periodFinishDate?: Date; - - totalStakedLP?: bigint; // farm total staked LP (all users) - totalSupplyLP?: bigint; // LP token total supply - poolReserves?: { token0: Address; token1: Address; reserve0: bigint; reserve1: bigint }; - - poolAprPct?: number; // fees APR (trading fees -> LPs) - epochAprPct?: number; // farming APR (global) - epochAprUserPct?: number; // OPTIONAL: user-specific APR -}; export function useStakingData(pools: PoolItem[]) { const { address: user } = useAccount(); const client = usePublicClient(); + const {WNATIVE_ADDRESS, getOrFetchToken} = useTokenModule(); + + type StakingSlice = { + staking: Address | null; + rewardToken?: Awaited>; + rewardRatePerSec?: bigint; + earned?: bigint; + stakedBalance?: bigint; // user staked LP + walletLpBalance?: bigint; // user wallet LP (outside farm) + periodFinish?: bigint; // timestamp (sec) + periodFinishDate?: Date; + + totalStakedLP?: bigint; // farm total staked LP (all users) + totalSupplyLP?: bigint; // LP token total supply + poolReserves?: { token0: Address; token1: Address; reserve0: bigint; reserve1: bigint }; + + poolAprPct?: number; // fees APR (trading fees -> LPs) + epochAprPct?: number; // farming APR (global) + epochAprUserPct?: number; // OPTIONAL: user-specific APR + }; const [stakingMap, setStakingMap] = useState>({}); diff --git a/apps/web/src/features/portfolio/components/PoolPositionsTable.tsx b/apps/web/src/features/portfolio/components/PoolPositionsTable.tsx index c667bfb0c..d7236f2ea 100644 --- a/apps/web/src/features/portfolio/components/PoolPositionsTable.tsx +++ b/apps/web/src/features/portfolio/components/PoolPositionsTable.tsx @@ -1,7 +1,8 @@ import React from "react"; import type { PoolPosition } from "../hooks/usePortfolio"; import styles from "../portfolio.module.css"; -import { getTokenForUI } from "../../../lib/tokens"; +import { useTokenModule } from "../../../hooks/useTokenModule"; + import { getTokenIcon } from "../../../lib/getTokenIcon"; function formatSmart(value: string) { @@ -38,6 +39,7 @@ export function PoolPositionsTable({ data }: { data: PoolPosition[] }) { {data.map((p) => { + const { getTokenForUI } = useTokenModule(); const t0 = getTokenForUI(p.token0.address) ?? p.token0; const t1 = getTokenForUI(p.token1.address) ?? p.token1; const icon0 = getTokenIcon(t0.address ?? ""); diff --git a/apps/web/src/features/portfolio/components/TokenHoldingsTable.tsx b/apps/web/src/features/portfolio/components/TokenHoldingsTable.tsx index 6f8eb6bf0..fbe1c7373 100644 --- a/apps/web/src/features/portfolio/components/TokenHoldingsTable.tsx +++ b/apps/web/src/features/portfolio/components/TokenHoldingsTable.tsx @@ -2,7 +2,8 @@ import React from "react"; import type { TokenHolding } from "../hooks/usePortfolio"; import styles from "../portfolio.module.css"; -import { getTokenForUI, NATIVE_PLACEHOLDER } from "../../../lib/tokens"; +import { useTokenModule } from "../../../hooks/useTokenModule"; + import { getTokenIcon } from "../../../lib/getTokenIcon"; function formatSmart(value: string) { @@ -27,6 +28,7 @@ export function TokenHoldingsTable({ data }: { data: TokenHolding[] }) { {data.map((h, i) => { // Map to UI token (WTTRUST -> tTRUST etc.) + const { getTokenForUI, NATIVE_PLACEHOLDER } = useTokenModule(); const uiToken = getTokenForUI(h.token.address) ?? h.token; // Always provide an address for icon: native -> NATIVE_PLACEHOLDER const addrForIcon = (uiToken.address ?? NATIVE_PLACEHOLDER) as string; diff --git a/apps/web/src/features/portfolio/hooks/usePortfolio.ts b/apps/web/src/features/portfolio/hooks/usePortfolio.ts index 746965bc7..fc86439d0 100644 --- a/apps/web/src/features/portfolio/hooks/usePortfolio.ts +++ b/apps/web/src/features/portfolio/hooks/usePortfolio.ts @@ -3,13 +3,7 @@ import type { Address } from "viem"; import { erc20Abi, formatUnits } from "viem"; import { useAccount, usePublicClient } from "wagmi"; import { abi, addresses } from "@trustswap/sdk"; -import { - TOKENLIST, - toUIList, - getOrFetchToken, - NATIVE_PLACEHOLDER, - isNative, -} from "../../../lib/tokens"; // ← adjust path to your tokens file +import { useTokenModule } from "../../../hooks/useTokenModule"; type TokenInfoLite = { address?: Address; // undefined => native @@ -48,6 +42,7 @@ export function usePortfolio() { const [positions, setPositions] = useState(null); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); + const { TOKENLIST, toUIList, getOrFetchToken, NATIVE_PLACEHOLDER, isNative } = useTokenModule(); // Build UI token list: start from TOKENLIST and hide items flagged as hidden const uiTokenList = useMemo(() => toUIList(TOKENLIST), []); diff --git a/apps/web/src/features/swap/components/SwapForm.tsx b/apps/web/src/features/swap/components/SwapForm.tsx index 62bb5e2f4..9fe9b2b37 100644 --- a/apps/web/src/features/swap/components/SwapForm.tsx +++ b/apps/web/src/features/swap/components/SwapForm.tsx @@ -3,11 +3,8 @@ import { useEffect, useMemo, useRef, useState } from "react"; import type { Address } from "viem"; import { getAddress, parseUnits, formatUnits } from "viem"; import { useAccount, usePublicClient } from "wagmi"; -import { - getDefaultPair, - TOKENLIST, - NATIVE_PLACEHOLDER, -} from "../../../lib/tokens"; +import { useTokenModule } from "../../../hooks/useTokenModule"; + import { useImportedTokens } from "../hooks/useImportedTokens"; import { useQuoteDetails } from "../hooks/useQuoteDetails"; import { useAllowance } from "../hooks/useAllowance"; @@ -24,8 +21,6 @@ import ApproveAndSwap from "./ApproveAndSwap"; import DetailsDisclosure from "./DetailsDisclosure"; import { addresses, abi } from "@trustswap/sdk"; -const isNative = (a?: Address) => - !!a && a.toLowerCase() === NATIVE_PLACEHOLDER.toLowerCase(); const norm = (a?: string) => (a ? a.toLowerCase() : ""); @@ -66,6 +61,16 @@ type Meta = { export default function SwapForm() { const { address } = useAccount(); const pc = usePublicClient(); + const { + getDefaultPair, + TOKENLIST, + NATIVE_PLACEHOLDER, + WNATIVE_ADDRESS, + getTokenByAddressOrFallback, +} = useTokenModule(); + + const isNative = (a?: Address) => + !!a && a.toLowerCase() === NATIVE_PLACEHOLDER.toLowerCase(); const defaults = useMemo(() => getDefaultPair(), []); const [tokenIn, setTokenIn] = useState
(defaults.tokenIn.address); @@ -318,9 +323,13 @@ export default function SwapForm() { tokenOut ?? "0x0000000000000000000000000000000000000000", amountIn, amountOut, - pairData + pairData, + { + NATIVE_PLACEHOLDER, + WNATIVE_ADDRESS, + getTokenByAddressOrFallback, + } ); - // deps: tout ce qui influence l'impact }, [ tokenIn, tokenOut, @@ -335,6 +344,9 @@ export default function SwapForm() { (pairData as any)?.decimals1, bestPath?.join(">"), lastOutBn?.toString(), + NATIVE_PLACEHOLDER, + WNATIVE_ADDRESS, + getTokenByAddressOrFallback, ]); async function onApproveAndSwap() { diff --git a/apps/web/src/features/swap/components/TokenSelector.tsx b/apps/web/src/features/swap/components/TokenSelector.tsx index caa9659fc..04e319b94 100644 --- a/apps/web/src/features/swap/components/TokenSelector.tsx +++ b/apps/web/src/features/swap/components/TokenSelector.tsx @@ -3,7 +3,8 @@ import { useState, useRef, useEffect, useMemo } from "react"; import type { Address } from "viem"; import { isAddress, getAddress, erc20Abi } from "viem"; import { usePublicClient } from "wagmi"; -import { TOKENLIST } from "../../../lib/tokens"; +import { useTokenModule } from "../../../hooks/useTokenModule"; + import styles from "@ui/styles/TokenSelector.module.css"; import arrowIcone from "../../../assets/arrow-selector.png"; import deleteIcone from "../../../assets/delete.png"; @@ -51,6 +52,7 @@ export default function TokenSelector({ const pc = usePublicClient(); // Base tokens (props > TOKENLIST) + const { TOKENLIST } = useTokenModule(); const baseTokens: Token[] = useMemo( () => (tokens && tokens.length ? tokens : (TOKENLIST as unknown as Token[])), [tokens] diff --git a/apps/web/src/features/swap/hooks/useAllowance.ts b/apps/web/src/features/swap/hooks/useAllowance.ts index 5da37ddca..bb993d9d7 100644 --- a/apps/web/src/features/swap/hooks/useAllowance.ts +++ b/apps/web/src/features/swap/hooks/useAllowance.ts @@ -1,13 +1,14 @@ import type { Address } from "viem"; import { erc20Abi, maxUint256 } from "viem"; import { usePublicClient } from "wagmi"; -import { NATIVE_PLACEHOLDER } from "../../../lib/tokens"; - -const isNative = (a?: Address) => - !!a && a.toLowerCase() === NATIVE_PLACEHOLDER.toLowerCase(); +import { useTokenModule } from "../../../hooks/useTokenModule"; export function useAllowance() { const pc = usePublicClient(); + const { NATIVE_PLACEHOLDER } = useTokenModule(); + + const isNative = (a?: Address) => + !!a && a.toLowerCase() === NATIVE_PLACEHOLDER.toLowerCase(); return async function allowance( owner: Address, @@ -16,7 +17,6 @@ export function useAllowance() { ): Promise { if (!pc) throw new Error("Public client not available"); - if (isNative(token)) return maxUint256; try { diff --git a/apps/web/src/features/swap/hooks/useDynamicTokenList.ts b/apps/web/src/features/swap/hooks/useDynamicTokenList.ts index 314154c43..13ec9ec22 100644 --- a/apps/web/src/features/swap/hooks/useDynamicTokenList.ts +++ b/apps/web/src/features/swap/hooks/useDynamicTokenList.ts @@ -1,12 +1,9 @@ // apps/web/src/features/tokens/useDynamicTokenList.ts import { useEffect, useMemo, useRef, useState } from "react"; import type { Address } from "viem"; -import { - TOKENLIST, - getOrFetchToken, - type TokenInfo, - WNATIVE_ADDRESS, -} from "../../../lib/tokens"; +import { type TokenInfo } from "../../../lib/tokens"; +import { useTokenModule } from "../../../hooks/useTokenModule"; + const low = (s: string) => s.toLowerCase(); const ZERO = "0x0000000000000000000000000000000000000000"; @@ -38,6 +35,7 @@ function pickTokenAddr(p: any, key: "token0" | "token1"): Address | undefined { } export function useDynamicTokenList(rawPools: any) { + const { getOrFetchToken, TOKENLIST, WNATIVE_ADDRESS } = useTokenModule(); const base = useMemo(() => TOKENLIST, []); const [tokens, setTokens] = useState(base); const inFlight = useRef>(new Set()); diff --git a/apps/web/src/features/swap/hooks/useGasEstimate.ts b/apps/web/src/features/swap/hooks/useGasEstimate.ts index 8123afdd5..7e74e73f8 100644 --- a/apps/web/src/features/swap/hooks/useGasEstimate.ts +++ b/apps/web/src/features/swap/hooks/useGasEstimate.ts @@ -3,7 +3,8 @@ import type { Address } from "viem"; import { usePublicClient } from "wagmi"; import { abi, addresses } from "@trustswap/sdk"; import { formatUnits } from "viem"; -import { buildPath } from "../../../lib/tokens"; +import { useTokenModule } from "../../../hooks/useTokenModule"; + type Args = { account?: Address; @@ -25,6 +26,7 @@ export function useGasEstimate() { if (!args.account) return null; if (!args.path || args.path.length < 2) return null; + const { buildPath } = useTokenModule(); const path = buildPath(args.path); const symbol = args.nativeSymbol ?? "tTRUST"; const nativeIn = !!args.nativeIn; diff --git a/apps/web/src/features/swap/hooks/usePairData.ts b/apps/web/src/features/swap/hooks/usePairData.ts index deae1a8dd..f713bd6cd 100644 --- a/apps/web/src/features/swap/hooks/usePairData.ts +++ b/apps/web/src/features/swap/hooks/usePairData.ts @@ -1,7 +1,8 @@ import type { Address } from "viem"; import { usePublicClient } from "wagmi"; import { abi, addresses } from "@trustswap/sdk"; -import { toWrapped } from "../../../lib/tokens"; +import { useTokenModule } from "../../../hooks/useTokenModule"; + export type PairData = { pair: Address; @@ -15,12 +16,15 @@ const ZERO: Address = "0x0000000000000000000000000000000000000000"; export function usePairData() { const pc = usePublicClient(); - + const { toWrapped } = useTokenModule(); + return async function fetchPair(tokenA: Address, tokenB: Address): Promise { if (!pc) return null; if (!tokenA || !tokenB) return null; if (tokenA.toLowerCase() === tokenB.toLowerCase()) return null; + + // ⚠️ wrap natif -> WNATIVE avant d’interroger la factory const a = toWrapped(tokenA); const b = toWrapped(tokenB); diff --git a/apps/web/src/features/swap/hooks/usePriceImpact.ts b/apps/web/src/features/swap/hooks/usePriceImpact.ts index 89c30faf6..aff678a78 100644 --- a/apps/web/src/features/swap/hooks/usePriceImpact.ts +++ b/apps/web/src/features/swap/hooks/usePriceImpact.ts @@ -1,14 +1,9 @@ // usePriceImpact.ts import type { Address } from "viem"; import type { PairData } from "./usePairData"; -import { NATIVE_PLACEHOLDER, WNATIVE_ADDRESS, getTokenByAddressOrFallback } from "../../../lib/tokens"; +import type { TokenInfo } from "../../../lib/tokens"; import { formatUnits } from "viem"; -const wrap = (a: Address) => - a?.toLowerCase?.() === NATIVE_PLACEHOLDER.toLowerCase() - ? (WNATIVE_ADDRESS as Address) - : a; - const num = (x: string | number | undefined) => { const n = Number(String(x ?? "").replace(",", ".").trim()); return Number.isFinite(n) ? n : 0; @@ -18,15 +13,17 @@ const toBi = (x: unknown): bigint | null => { try { if (typeof x === "bigint") return x; if (typeof x === "number") return BigInt(Math.trunc(x)); - if (typeof x === "string") return BigInt(x); // support "123" / "0x..." + if (typeof x === "string") return BigInt(x); + return null; + } catch { return null; - } catch { return null; } + } }; const safeUnits = (v: bigint, decimals: number): number => { - try { return Number(formatUnits(v, decimals)); } - catch { - // fallback naïf si formatUnits échoue (rare) + try { + return Number(formatUnits(v, decimals)); + } catch { const s = v.toString(); if (decimals <= 0) return Number(s); const len = s.length; @@ -36,34 +33,43 @@ const safeUnits = (v: bigint, decimals: number): number => { } }; +export type PriceImpactHelpers = { + NATIVE_PLACEHOLDER: Address; + WNATIVE_ADDRESS: Address; + getTokenByAddressOrFallback: (addr: Address) => TokenInfo; +}; + export function computePriceImpactPct( tokenIn: Address, tokenOut: Address, amountInStr: string, amountOutStr: string, - pair: PairData | null + pair: PairData | null, + helpers: PriceImpactHelpers ): number | null { - // 0) montants - const ain = num(amountInStr); + const ain = num(amountInStr); const aout = num(amountOutStr); - if (ain <= 0 || aout <= 0) return null; // pas assez d'info + if (ain <= 0 || aout <= 0) return null; - // 1) pair présente if (!pair) return null; - // 2) map adresses (wrap natif) - const inAsPair = wrap(tokenIn)?.toLowerCase?.(); + const { NATIVE_PLACEHOLDER, WNATIVE_ADDRESS, getTokenByAddressOrFallback } = helpers; + + const wrap = (a: Address) => + a?.toLowerCase?.() === NATIVE_PLACEHOLDER.toLowerCase() + ? (WNATIVE_ADDRESS as Address) + : a; + + const inAsPair = wrap(tokenIn)?.toLowerCase?.(); const outAsPair = wrap(tokenOut)?.toLowerCase?.(); const t0 = (pair as any).token0?.toLowerCase?.(); const t1 = (pair as any).token1?.toLowerCase?.(); if (!inAsPair || !outAsPair || !t0 || !t1) return null; - // si la pair ne correspond pas à la sélection courante → null propre if (!((inAsPair === t0 || inAsPair === t1) && (outAsPair === t0 || outAsPair === t1))) { return null; } - // 3) réserves + décimales (tolérant : fallback 18 si manquant) const r0bi = toBi((pair as any).reserve0); const r1bi = toBi((pair as any).reserve1); if (r0bi === null || r1bi === null) return null; @@ -82,20 +88,17 @@ export function computePriceImpactPct( const R1 = safeUnits(r1bi, d1); if (R0 <= 0 || R1 <= 0 || !isFinite(R0) || !isFinite(R1)) return null; - // 4) choisir le bon sens const inIs0 = inAsPair === t0; - const rIn = inIs0 ? R0 : R1; + const rIn = inIs0 ? R0 : R1; const rOut = inIs0 ? R1 : R0; if (rIn <= 0 || rOut <= 0) return null; - // 5) spot vs execution const spot = rOut / rIn; const exec = aout / ain; if (!isFinite(spot) || !isFinite(exec) || spot <= 0 || exec <= 0) return null; let impact = ((spot - exec) / spot) * 100; - // 6) bornes "sanity" (évite les 998%) if (!isFinite(impact)) return null; if (impact < 0) impact = 0; if (impact > 100) impact = 100; diff --git a/apps/web/src/features/swap/hooks/useSwap.ts b/apps/web/src/features/swap/hooks/useSwap.ts index 50335aba7..0be8432d5 100644 --- a/apps/web/src/features/swap/hooks/useSwap.ts +++ b/apps/web/src/features/swap/hooks/useSwap.ts @@ -1,7 +1,7 @@ import type { Address } from "viem"; import { erc20Abi, parseUnits, formatUnits } from "viem"; import { usePublicClient, useWalletClient, useChainId } from "wagmi"; -import { getOrFetchToken } from "../../../lib/tokens"; +import { useTokenModule } from "../../../hooks/useTokenModule"; import { abi, addresses } from "@trustswap/sdk"; import { useAlerts } from "../../../features/alerts/Alerts"; @@ -77,6 +77,7 @@ export function useSwap() { const publicClient = usePublicClient(); const chainId = useChainId(); const alerts = useAlerts(); + const { getOrFetchToken } = useTokenModule(); const approveIfNeeded = async (token: Address, owner: Address, amount: bigint) => { if (!publicClient || !wallet) throw new Error("Wallet not connected"); diff --git a/apps/web/src/features/swap/hooks/useTokenBalance.ts b/apps/web/src/features/swap/hooks/useTokenBalance.ts index ce8da0896..b5e9ee58a 100644 --- a/apps/web/src/features/swap/hooks/useTokenBalance.ts +++ b/apps/web/src/features/swap/hooks/useTokenBalance.ts @@ -3,13 +3,10 @@ import type { Address } from "viem"; import { erc20Abi, formatUnits, zeroAddress } from "viem"; import { useAccount, usePublicClient } from "wagmi"; import { useCallback, useEffect, useMemo, useState } from "react"; -import { - isNative as isNativeAddr, - NATIVE_PLACEHOLDER, - getOrFetchToken, // ✅ utilise ce helper on-chain - WNATIVE_ADDRESS, - type TokenInfo, -} from "../../../lib/tokens"; +import { type TokenInfo } from "../../../lib/tokens"; + +import { useTokenModule } from "../../../hooks/useTokenModule"; + import { useLiveRegister } from "../../../live/LiveRefetchProvider"; type Result = { @@ -26,6 +23,7 @@ export function useTokenBalance(token?: Address, owner?: Address): Result { const { chain } = useAccount(); const pc = usePublicClient(); const isUnsetToken = !token || token.toLowerCase() === zeroAddress; + const { NATIVE_PLACEHOLDER, getOrFetchToken, isNative } = useTokenModule(); const [state, setState] = useState({ isLoading: !!token && !!owner, @@ -62,13 +60,13 @@ export function useTokenBalance(token?: Address, owner?: Address): Result { try { // ✅ récupère meta on-chain si besoin (ne throw pas si hors TOKENLIST) - const meta = isNativeAddr(token!) + const meta = isNative(token!) ? nativeMeta : await getOrFetchToken(token!); let raw: bigint = 0n; - if (meta.isNative || isNativeAddr(token!)) { + if (meta.isNative || isNative(token!)) { raw = await pc.getBalance({ address: owner }); } else { // balanceOf peut revert sur des tokens “non standard” → protège diff --git a/apps/web/src/hooks/useTokenModule.ts b/apps/web/src/hooks/useTokenModule.ts index fb37fd681..c29d9e1ff 100644 --- a/apps/web/src/hooks/useTokenModule.ts +++ b/apps/web/src/hooks/useTokenModule.ts @@ -3,7 +3,8 @@ import { useChainId } from "wagmi"; import type { Chain } from "viem"; import { intuitionTestnet, intuitionMainnet } from "@trustswap/sdk"; import { getAddresses } from "@trustswap/sdk"; -import { createTokenModule } from "../lib/tokens"; // adapte le chemin si besoin +import { createTokenModule } from "../lib/tokens"; + const CHAINS_BY_ID: Record = { [intuitionTestnet.id]: intuitionTestnet as unknown as Chain, From 5831e1ea32bfe10fe5ed22c161ea56b8a9da3a52 Mon Sep 17 00:00:00 2001 From: James Date: Thu, 20 Nov 2025 00:41:35 +0100 Subject: [PATCH 04/10] fix pagination pools table --- .../features/pool/components/PoolsPage.tsx | 12 ++++++- .../features/pool/components/PoolsTable.tsx | 14 +++++++-- .../components/filters/PoolsPagination.tsx | 17 +++++----- apps/web/src/features/pool/tableau.module.css | 31 ++++++++++++++++++- apps/web/src/lib/tokenFilters.ts | 2 +- 5 files changed, 64 insertions(+), 12 deletions(-) diff --git a/apps/web/src/features/pool/components/PoolsPage.tsx b/apps/web/src/features/pool/components/PoolsPage.tsx index 33757af90..d70d8a56c 100644 --- a/apps/web/src/features/pool/components/PoolsPage.tsx +++ b/apps/web/src/features/pool/components/PoolsPage.tsx @@ -15,9 +15,13 @@ import styles from "../pools.module.css"; export default function PoolsPage() { + const PAGE_SIZE = 8; + const [page, setPage] = useState(1); const [query, setQuery] = useState(""); + const [hasNextPage, setHasNextPage] = useState(false); + const [isOpen, setIsOpen] = useState(false); const [tokenA, setTokenA] = useState
(); const [tokenB, setTokenB] = useState
(); @@ -89,8 +93,14 @@ export default function PoolsPage() { page={page} query={query} onOpenLiquidity={openWithPair} + onPageInfoChange={(info) => setHasNextPage(info.hasNextPage)} + /> + + - )} diff --git a/apps/web/src/features/pool/components/PoolsTable.tsx b/apps/web/src/features/pool/components/PoolsTable.tsx index 6b8139b69..a6ae3cf41 100644 --- a/apps/web/src/features/pool/components/PoolsTable.tsx +++ b/apps/web/src/features/pool/components/PoolsTable.tsx @@ -1,5 +1,5 @@ // apps/web/src/features/pools/components/PoolsTable.tsx -import { useMemo } from "react"; +import { useMemo, useEffect } from "react"; import type { Address } from "viem"; import { usePoolsData } from "../hooks/usePoolsData"; @@ -17,14 +17,24 @@ export function PoolsTable({ page, query, onOpenLiquidity, + onPageInfoChange, }: { page: number; query: string; onOpenLiquidity: (a: Address, b: Address) => void; + onPageInfoChange?: (info: { hasNextPage: boolean }) => void; }) { - const pageSize = 10; + const pageSize = 8; const { items, loading, error } = usePoolsData(pageSize, (page - 1) * pageSize); + useEffect(() => { + if (!onPageInfoChange) return; + if (loading || error) return; + + // Si on a une page "pleine", il y a potentiellement une page suivante + onPageInfoChange({ hasNextPage: items.length === pageSize }); + }, [items.length, loading, error, onPageInfoChange]); + const skeletonPool: PoolItem = { pair: "0x0000000000000000000000000000000000000000", token0: { symbol: "", address: "" as `0x${string}`, decimals: 18 }, diff --git a/apps/web/src/features/pool/components/filters/PoolsPagination.tsx b/apps/web/src/features/pool/components/filters/PoolsPagination.tsx index 6d5e0d972..7c7b89447 100644 --- a/apps/web/src/features/pool/components/filters/PoolsPagination.tsx +++ b/apps/web/src/features/pool/components/filters/PoolsPagination.tsx @@ -1,15 +1,16 @@ +// PoolsPagination.tsx import styles from "../../tableau.module.css"; export function PoolsPagination({ page, - totalPages, + hasNextPage, onPage, }: { page: number; - totalPages: number; + hasNextPage: boolean; onPage: (p: number) => void; }) { - if (totalPages <= 1) return null; + if (page === 1 && !hasNextPage) return null; return (
@@ -18,10 +19,12 @@ export function PoolsPagination({ Prev )} - Page {page} - {page < totalPages && ( - + {page} + {hasNextPage && ( + )}
); -} +} \ No newline at end of file diff --git a/apps/web/src/features/pool/tableau.module.css b/apps/web/src/features/pool/tableau.module.css index c39e30f24..be06a7e22 100644 --- a/apps/web/src/features/pool/tableau.module.css +++ b/apps/web/src/features/pool/tableau.module.css @@ -245,9 +245,38 @@ table { bottom: 2vh; right: 2vh; font-size: 1.2vh; - color: grey; + +} + +.pagination button { + background-color: #313030; + color: var(--text-color); + border: 1px solid rgba(255, 255, 255, 0.08); + padding: 8px 10px; + border-radius: 12px; + font-size: 12px; + cursor: pointer; + transition: + background 0.25s ease, + transform 0.15s ease, + box-shadow 0.2s ease; } +.pagination button:hover { + background-color: #313030; + color: var(--text-color); + transform: translateY(-1px); +} + + +.pagination span { + color: #9d9d9e; + font-size: 12px; + letter-spacing: 0.6px; + padding: 0 6px; +} + + .expiredBadge { display: inline-block; padding: 2px 6px; diff --git a/apps/web/src/lib/tokenFilters.ts b/apps/web/src/lib/tokenFilters.ts index 60d1cb695..5c26b4006 100644 --- a/apps/web/src/lib/tokenFilters.ts +++ b/apps/web/src/lib/tokenFilters.ts @@ -19,7 +19,7 @@ const DENY_TOKEN_ADDRESSES: string[] = [ "0x124c4e8470ed201ae896c2df6ee7152ab7438d80", "0x5fdd4edd250b9214d77103881be0f09812d501d6", - "0x51379cc2c942ee2ae2ff0bd67a7b475f0be39dcf", + "0x51379cc2c942ee2ae2ff0bd67a7b475f0be39dcf", ]; const DENY_SYMBOLS: string[] = [ From 05007effaf75d9fa66d19c1086ed5371bb6a4dcf Mon Sep 17 00:00:00 2001 From: James Date: Thu, 20 Nov 2025 10:25:23 +0100 Subject: [PATCH 05/10] fix switch testnet to mainnet --- apps/web/src/components/Layout.tsx | 4 +- apps/web/src/components/NetworkSelect.tsx | 4 +- .../features/pool/components/PoolsPage.tsx | 1 - .../src/features/pool/hooks/useGlobalStats.ts | 15 ++-- .../src/features/pool/hooks/usePoolsData.ts | 90 +++++++++++++------ .../swap/components/TokenSelector.tsx | 2 +- apps/web/src/features/swap/hooks/useSwap.ts | 1 + .../features/swap/hooks/useTokenBalance.ts | 2 +- apps/web/src/styles/Layout.module.css | 59 ++++++++++++ packages/sdk/src/addresses/index.ts | 2 +- packages/sdk/src/chains.ts | 16 ++-- 11 files changed, 151 insertions(+), 45 deletions(-) diff --git a/apps/web/src/components/Layout.tsx b/apps/web/src/components/Layout.tsx index b71d2d862..eff8cd71a 100644 --- a/apps/web/src/components/Layout.tsx +++ b/apps/web/src/components/Layout.tsx @@ -112,9 +112,11 @@ export default function Layout() {
-
+
+ +
diff --git a/apps/web/src/components/NetworkSelect.tsx b/apps/web/src/components/NetworkSelect.tsx index 3c8a62fd5..b220d6f44 100644 --- a/apps/web/src/components/NetworkSelect.tsx +++ b/apps/web/src/components/NetworkSelect.tsx @@ -1,6 +1,8 @@ import React from "react"; import { useChainId, useSwitchChain } from "wagmi"; import { CHAINS } from "../lib/wagmi"; +import styles from "../styles/Layout.module.css"; + export function NetworkSelect() { const chainId = useChainId(); @@ -17,7 +19,7 @@ export function NetworkSelect() {