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 000000000..0f6b29376
Binary files /dev/null and b/.turbo/cache/6a91baf0e64ff848.tar.zst differ
diff --git a/.turbo/cache/819d866a1222082a-meta.json b/.turbo/cache/819d866a1222082a-meta.json
new file mode 100644
index 000000000..1d360b3ea
--- /dev/null
+++ b/.turbo/cache/819d866a1222082a-meta.json
@@ -0,0 +1 @@
+{"hash":"819d866a1222082a","duration":3036}
\ No newline at end of file
diff --git a/.turbo/cache/819d866a1222082a.tar.zst b/.turbo/cache/819d866a1222082a.tar.zst
new file mode 100644
index 000000000..a8f74aa17
Binary files /dev/null and b/.turbo/cache/819d866a1222082a.tar.zst differ
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 000000000..170850eb1
Binary files /dev/null and b/.turbo/cache/f0be77d340149c0e.tar.zst differ
diff --git a/.turbo/cache/f8b2adaa0614c467-meta.json b/.turbo/cache/f8b2adaa0614c467-meta.json
new file mode 100644
index 000000000..28821448e
--- /dev/null
+++ b/.turbo/cache/f8b2adaa0614c467-meta.json
@@ -0,0 +1 @@
+{"hash":"f8b2adaa0614c467","duration":5375}
\ No newline at end of file
diff --git a/.turbo/cache/f8b2adaa0614c467.tar.zst b/.turbo/cache/f8b2adaa0614c467.tar.zst
new file mode 100644
index 000000000..1bb9ff93c
Binary files /dev/null and b/.turbo/cache/f8b2adaa0614c467.tar.zst differ
diff --git a/.turbo/cookies/10.cookie b/.turbo/cookies/10.cookie
new file mode 100644
index 000000000..e69de29bb
diff --git a/.turbo/cookies/11.cookie b/.turbo/cookies/11.cookie
new file mode 100644
index 000000000..e69de29bb
diff --git a/.turbo/cookies/12.cookie b/.turbo/cookies/12.cookie
new file mode 100644
index 000000000..e69de29bb
diff --git a/.turbo/cookies/2.cookie b/.turbo/cookies/2.cookie
new file mode 100644
index 000000000..e69de29bb
diff --git a/.turbo/cookies/3.cookie b/.turbo/cookies/3.cookie
new file mode 100644
index 000000000..e69de29bb
diff --git a/.turbo/cookies/4.cookie b/.turbo/cookies/4.cookie
new file mode 100644
index 000000000..e69de29bb
diff --git a/.turbo/cookies/5.cookie b/.turbo/cookies/5.cookie
new file mode 100644
index 000000000..e69de29bb
diff --git a/.turbo/cookies/6.cookie b/.turbo/cookies/6.cookie
new file mode 100644
index 000000000..e69de29bb
diff --git a/.turbo/cookies/7.cookie b/.turbo/cookies/7.cookie
new file mode 100644
index 000000000..e69de29bb
diff --git a/.turbo/cookies/8.cookie b/.turbo/cookies/8.cookie
new file mode 100644
index 000000000..e69de29bb
diff --git a/.turbo/cookies/9.cookie b/.turbo/cookies/9.cookie
new file mode 100644
index 000000000..e69de29bb
diff --git a/.turbo/daemon/c9749efa7ac2748e-turbo.log.2025-11-19 b/.turbo/daemon/c9749efa7ac2748e-turbo.log.2025-11-19
new file mode 100644
index 000000000..e69de29bb
diff --git a/apps/docus/docusaurus.config.ts b/apps/docus/docusaurus.config.ts
index 74f06d1ca..00ad024c0 100644
--- a/apps/docus/docusaurus.config.ts
+++ b/apps/docus/docusaurus.config.ts
@@ -76,7 +76,7 @@ const config: Config = {
// Replace with your project's social card
image: 'img/docusaurus-social-card.jpg',
navbar: {
- title: 'TrusSwap Docs',
+ title: 'TrustSwap Docs',
items: [
{ type: 'doc', docId: 'litepaper/introduction', position: 'left', label: 'Litepaper' },
diff --git a/apps/web/src/components/Layout.tsx b/apps/web/src/components/Layout.tsx
index b33dcf075..eff8cd71a 100644
--- a/apps/web/src/components/Layout.tsx
+++ b/apps/web/src/components/Layout.tsx
@@ -2,6 +2,7 @@ import { NavLink, Outlet, useLocation } from "react-router-dom";
import { useEffect, useRef, useState } from "react";
import styles from "../styles/Layout.module.css";
import { ConnectButton } from "./ConnectButton";
+import { NetworkSelect } from "./NetworkSelect";
import logo from "../assets/logo.png";
export default function Layout() {
@@ -113,6 +114,9 @@ export default function Layout() {
+
+
+
diff --git a/apps/web/src/components/NetworkSelect.tsx b/apps/web/src/components/NetworkSelect.tsx
new file mode 100644
index 000000000..de3d9ef60
--- /dev/null
+++ b/apps/web/src/components/NetworkSelect.tsx
@@ -0,0 +1,39 @@
+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();
+ const { switchChainAsync } = useSwitchChain();
+
+ const handleChange = async (event: React.ChangeEvent) => {
+ const targetId = Number(event.target.value);
+ const targetChain = CHAINS.find((c) => c.id === targetId);
+ if (!targetChain) return;
+
+ try {
+ // Wait for chain switch to complete
+ await switchChainAsync({ chainId: targetChain.id });
+
+ // Hard reload to reset all React state / caches
+ window.location.reload();
+ } catch (err) {
+ console.error("Failed to switch chain", err);
+ }
+ };
+
+ return (
+
+ );
+}
diff --git a/apps/web/src/config/sdk.ts b/apps/web/src/config/sdk.ts
index ac96fd641..28bd93e9c 100644
--- a/apps/web/src/config/sdk.ts
+++ b/apps/web/src/config/sdk.ts
@@ -1,16 +1,19 @@
// apps/web/src/config/sdk.ts
import type { Address } from "viem";
-// Source de vérité: SDK (pas le wallet)
-import { INTUITION, addresses as SDK_ADDRESSES } from "@trustswap/sdk";
+import {
+ INTUITION,
+ INTUITION_MAINNET,
+ INTUITION_TESTNET,
+ getAddresses,
+} from "@trustswap/sdk";
// --- Types "côté app" (normalisés) ---
export type ProtocolAddresses = {
ROUTER02: Address;
FACTORY: Address;
WNATIVE: Address;
- // (optionnel) expose aussi ce dont tu as besoin dans l'app:
TSWP?: Address;
- SRF?: Address; // StakingRewardsFactory si tu l'as
+ SRF?: Address;
};
export type ProtocolSymbols = {
@@ -19,51 +22,66 @@ export type ProtocolSymbols = {
};
// --- Choix du chainId app ---
-// Priorité à l'ENV si tu veux override, sinon SDK
-const APP_CHAIN_ID = Number(import.meta.env.VITE_CHAIN_ID || INTUITION.id);
+// ENV > sinon on prend le chain du SDK (INTUITION = "courant")
+const APP_CHAIN_ID =
+ Number(import.meta.env.VITE_CHAIN_ID || INTUITION.id);
// --- Adaptateur: mappe les noms du SDK vers tes noms normalisés ---
function normalizeSdkAddresses(a: any): ProtocolAddresses {
- // Adapte ces clés selon ce que ton SDK expose exactement
return {
ROUTER02: (a.UniswapV2Router02 ?? a.ROUTER02) as Address,
- FACTORY: (a.UniswapV2Factory ?? a.FACTORY) as Address,
- WNATIVE: (a.WTTRUST ?? a.WNATIVE ?? a.WETH9) as Address,
- TSWP: a.TSWP as Address,
- SRF: (a.SRF ?? a.StakingRewardsFactory ?? a.StakingRewardsFactoryV2) as Address,
+ FACTORY: (a.UniswapV2Factory ?? a.FACTORY) as Address,
+ // wrapped: WTRUST sur mainnet, WTTRUST sur testnet, WNATIVE/WETH9 en fallback
+ WNATIVE: (a.WTRUST ?? a.WTTRUST ?? a.WNATIVE ?? a.WETH9) as Address,
+ TSWP: a.TSWP as Address,
+ SRF: (a.SRF ?? a.StakingRewardsFactory ?? a.StakingRewardsFactoryV2) as Address,
};
}
-// Si ton SDK n'est pas multi-chain, on normalise directement l'objet plat:
-const FROM_SDK: ProtocolAddresses = normalizeSdkAddresses(SDK_ADDRESSES);
-
-// Fallbacks (utile si le SDK n'est pas dispo / dev offline)
+// Fallbacks explicites si jamais getAddresses ne connaît pas encore la chain
const FALLBACK_ADDRESSES: Record = {
- 13579: {
+ // 🔹 Intuition Testnet
+ [INTUITION_TESTNET.id]: {
ROUTER02: "0xAc1218b429E2BB26f5FFe635F04F7412ac40979c" as Address,
FACTORY: "0xd103E057242881214793d5A1A7c2A5B84731c75c" as Address,
WNATIVE: "0x51379Cc2C942EE2AE2fF0BD67a7b475F0be39Dcf" as Address,
TSWP: "0x7da120065e104C085fAc6f800d257a6296549cF3" as Address,
- // SRF: "0x819030e047cB49E9F68599433FeC5A7C32B41565" as Address, // si besoin
+ },
+
+ // 🔹 Intuition Mainnet
+ [INTUITION_MAINNET.id]: {
+ ROUTER02: "0x5123208Aa3C6A37615327a8c479a5e1654c0200E" as Address,
+ FACTORY: "0x83E9f4E539eb343F7F67d130a484c8a1b6555458" as Address,
+ WNATIVE: "0x81cFb09cb44f7184Ad934C09F82000701A4bF672" as Address, // WTRUST
+ // TSWP: "0x...." as Address, // quand tu le lances
},
};
-// Symboles (depuis le SDK + override)
+// Symboles (depuis le SDK + override par chain)
const FALLBACK_SYMBOLS: Record = {
- 13579: {
- NATIVE_SYMBOL: INTUITION.nativeCurrency.symbol || "tTRUST",
+ [INTUITION_TESTNET.id]: {
+ NATIVE_SYMBOL: INTUITION_TESTNET.nativeCurrency.symbol || "tTRUST",
WRAPPED_SYMBOL: "WTTRUST",
},
+ [INTUITION_MAINNET.id]: {
+ NATIVE_SYMBOL: INTUITION_MAINNET.nativeCurrency.symbol || "TRUST",
+ WRAPPED_SYMBOL: "WTRUST",
+ },
};
// --- API exportée par le module ---
export function getProtocolConfig() {
- let addrs: ProtocolAddresses | undefined = FROM_SDK;
+ let addrs: ProtocolAddresses | undefined;
- if (!addrs?.ROUTER02 || !addrs?.FACTORY || !addrs?.WNATIVE) {
+ try {
+ const sdkAddrs = getAddresses(APP_CHAIN_ID);
+ addrs = normalizeSdkAddresses(sdkAddrs);
+ } catch {
+ // si le SDK ne connaît pas encore la chain -> fallback local
addrs = FALLBACK_ADDRESSES[APP_CHAIN_ID];
}
- if (!addrs) {
+
+ if (!addrs?.ROUTER02 || !addrs?.FACTORY || !addrs?.WNATIVE) {
throw new Error(`Aucune config protocole pour chainId=${APP_CHAIN_ID}`);
}
diff --git a/apps/web/src/features/pool/components/PoolRow.tsx b/apps/web/src/features/pool/components/PoolRow.tsx
index 75052032a..853a65820 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;
@@ -49,7 +49,7 @@ export function PoolRow({
>
- {/* Display the tokens UI (symbol tTRUST if WTTRUST) */}
+ {/* Display the tokens UI (symbol tTRUST if WTRUST) */}
{/* Pass also the UI tokens if needed (if TvlCell displays the symbols somewhere) */}
diff --git a/apps/web/src/features/pool/components/PoolsPage.tsx b/apps/web/src/features/pool/components/PoolsPage.tsx
index d847befa8..c4ca308aa 100644
--- a/apps/web/src/features/pool/components/PoolsPage.tsx
+++ b/apps/web/src/features/pool/components/PoolsPage.tsx
@@ -8,19 +8,23 @@ 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";
export default function PoolsPage() {
+
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();
+ const { toUIAddress } = useTokenModule();
const pc = usePublicClient({ chainId: 13579 });
@@ -88,8 +92,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..edc1970a6 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 },
@@ -72,7 +82,9 @@ export function PoolsTable({
);
}
- if (!items.length) return Aucune pool
;
+ if (!items.length)
+ return No pools available
;
+
return (
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/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..3fa53ef4d 100644
--- a/apps/web/src/features/pool/hooks/useGlobalStats.ts
+++ b/apps/web/src/features/pool/hooks/useGlobalStats.ts
@@ -1,10 +1,10 @@
import { useEffect, useState } from "react";
import type { Abi, Address } from "viem";
import { parseAbiItem } from "viem";
-import { usePublicClient } from "wagmi";
-import { addresses } from "@trustswap/sdk";
+import { usePublicClient, useChainId } from "wagmi";
+import { getAddresses } 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;
@@ -22,14 +22,18 @@ type PairMeta = {
t1: Address;
r0: bigint;
r1: bigint;
- wIs0?: boolean; // true si t0==WNATIVE, false si t1==WNATIVE, undefined sinon
+ wIs0?: boolean;
};
export function useGlobalStats() {
- const pc = usePublicClient();
+ const wagmiChainId = useChainId();
+ const fallbackChainId = wagmiChainId;
+ const pc = usePublicClient({ chainId: fallbackChainId });
+
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 () => {
@@ -42,7 +46,9 @@ export function useGlobalStats() {
return;
}
- const factory = addresses.UniswapV2Factory as Address;
+ const activeChainId = pc.chain?.id ?? fallbackChainId;
+ const { UniswapV2Factory } = getAddresses(Number(activeChainId));
+ const factory = UniswapV2Factory as Address;
// 1) pairs
const len = await pc.readContract({
@@ -80,6 +86,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..099cce011 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;
@@ -242,7 +243,7 @@ export function useLiquidityActions() {
}
}
- // WTTRUST = vrai wrapped natif (avec alertes)
+ // WTRUST = vrai wrapped natif (avec alertes)
async function wrapNative(amount: bigint) {
try {
const base = {
diff --git a/apps/web/src/features/pool/hooks/useLpPosition.ts b/apps/web/src/features/pool/hooks/useLpPosition.ts
index 700e41ab0..0757862fa 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);
@@ -131,7 +135,7 @@ export function useLpPosition(tokenA?: Address, tokenB?: Address): LpPosition {
// 5) Décimales pour format (safe on-chain).
// Ici on prend celles des ERC-20 de la pair (readA/readB).
- // WTTRUST a 18, et les ERC-20 importés seront lus on-chain via getOrFetchToken.
+ // WTRUST a 18, et les ERC-20 importés seront lus on-chain via getOrFetchToken.
const [metaA, metaB] = await Promise.all([
getOrFetchToken(readA),
getOrFetchToken(readB),
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..901b1863b 100644
--- a/apps/web/src/features/pool/hooks/usePoolsData.ts
+++ b/apps/web/src/features/pool/hooks/usePoolsData.ts
@@ -1,9 +1,10 @@
// apps/web/src/features/pools/hooks/usePoolsData.ts
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 { usePublicClient, useChainId } from "wagmi";
+import { abi, getAddresses } from "@trustswap/sdk";
+import { useTokenModule } from "../../../hooks/useTokenModule";
+
import type { PoolItem } from "../types";
const chunk = (a: T[], n = 300) =>
@@ -15,28 +16,49 @@ const PAIR_ABI = toAbi(abi.UniswapV2Pair);
const dbg = (...args: any[]) => console.log("[usePoolsData]", ...args);
export function usePoolsData(limit = 50, offset = 0) {
- const pc = usePublicClient({ chainId: 13579 });
+ const wagmiChainId = useChainId();
+ const fallbackChainId = wagmiChainId;
+
+ // Let wagmi give us the right client for the current chain
+ const pc = usePublicClient({ chainId: fallbackChainId });
+
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const [items, setItems] = useState([]);
const runIdRef = useRef(0);
+ const { getOrFetchToken } = useTokenModule();
useEffect(() => {
- if (!pc) { dbg("no public client"); return; }
+ if (!pc) {
+ dbg("no public client");
+ return;
+ }
+
+ const activeChainId = pc.chain?.id ?? fallbackChainId;
+ const { UniswapV2Factory, StakingRewardsFactory } = getAddresses(Number(activeChainId));
- dbg("start", { limit, offset, chainId: pc?.chain?.id, hasMulticall3: !!pc.chain?.contracts?.multicall3 });
+ dbg("start", {
+ limit,
+ offset,
+ chainId: activeChainId,
+ hasMulticall3: !!pc.chain?.contracts?.multicall3,
+ });
const runId = ++runIdRef.current;
+
(async () => {
try {
- setLoading(true); setError(null);
+ setLoading(true);
+ setError(null);
- const factory = addresses.UniswapV2Factory as Address;
+ const factory = UniswapV2Factory as Address;
const canMulticall = !!pc.chain?.contracts?.multicall3;
dbg("factory", factory);
const total = await pc.readContract({
- address: factory, abi: FACTORY_ABI, functionName: "allPairsLength",
+ address: factory,
+ abi: FACTORY_ABI,
+ functionName: "allPairsLength",
}) as bigint;
dbg("total pairs", total?.toString());
@@ -45,7 +67,6 @@ export function usePoolsData(limit = 50, offset = 0) {
const idxs = Array.from({ length: end - start }, (_, i) => BigInt(start + i));
dbg("range", { start, end, count: idxs.length });
- // 1) Pairs
const pairs: Address[] = [];
for (const ids of chunk(idxs, 900)) {
dbg("fetch pairs chunk", { size: ids.length, mode: canMulticall ? "multicall" : "loop" });
@@ -53,18 +74,29 @@ export function usePoolsData(limit = 50, offset = 0) {
? await pc.multicall({
allowFailure: false,
contracts: ids.map((i) => ({
- address: factory, abi: FACTORY_ABI, functionName: "allPairs", args: [i],
+ address: factory,
+ abi: FACTORY_ABI,
+ functionName: "allPairs",
+ args: [i],
})),
})
: await Promise.all(ids.map((i) =>
- pc.readContract({ address: factory, abi: FACTORY_ABI, functionName: "allPairs", args: [i] })
+ pc.readContract({
+ address: factory,
+ abi: FACTORY_ABI,
+ functionName: "allPairs",
+ args: [i],
+ }),
));
pairs.push(...(res as Address[]));
dbg("pairs so far", pairs.length);
}
- if (!pairs.length) { if (runId === runIdRef.current) setItems([]); return; }
- // 2) token0 / token1 / reserves
+ if (!pairs.length) {
+ if (runId === runIdRef.current) setItems([]);
+ return;
+ }
+
type Meta = { pair: Address; t0: Address; t1: Address; r0: bigint; r1: bigint };
const metas: Meta[] = [];
@@ -102,22 +134,24 @@ export function usePoolsData(limit = 50, offset = 0) {
dbg("metas so far", metas.length);
}
- // 3) Enrichissement metadata tokens
dbg("enrich tokens", { count: metas.length });
- const rows: PoolItem[] = await Promise.all(metas.map(async (m) => {
- const [t0Info, t1Info] = await Promise.all([
- getOrFetchToken(m.t0), getOrFetchToken(m.t1),
- ]);
- return {
- pair: m.pair,
- token0: t0Info,
- token1: t1Info,
- reserve0: m.r0,
- reserve1: m.r1,
- srf: addresses.StakingRewardsFactory as Address,
- staking: null,
- } satisfies PoolItem;
- }));
+ const rows: PoolItem[] = await Promise.all(
+ metas.map(async (m) => {
+ const [t0Info, t1Info] = await Promise.all([
+ getOrFetchToken(m.t0),
+ getOrFetchToken(m.t1),
+ ]);
+ return {
+ pair: m.pair,
+ token0: t0Info,
+ token1: t1Info,
+ reserve0: m.r0,
+ reserve1: m.r1,
+ srf: StakingRewardsFactory as Address,
+ staking: null,
+ } satisfies PoolItem;
+ }),
+ );
dbg("rows ready", rows.length);
if (runId === runIdRef.current) setItems(rows);
@@ -129,7 +163,7 @@ export function usePoolsData(limit = 50, offset = 0) {
dbg("done");
}
})();
- }, [pc?.chain?.id, limit, offset]);
+ }, [pc, fallbackChainId, limit, offset, getOrFetchToken]);
return { loading, error, items };
}
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/pool/tableau.module.css b/apps/web/src/features/pool/tableau.module.css
index c39e30f24..c7e6ecb96 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;
@@ -368,4 +397,15 @@ table {
width: 90%;
margin-left: auto;
margin-right: auto;
+}
+
+.centerMessage {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ padding: 40px 0;
+ width: 100%;
+ height: 100%;
+ text-align: center;
+ opacity: 0.7;
}
\ No newline at end of file
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..fcd6c69b8 100644
--- a/apps/web/src/features/portfolio/components/TokenHoldingsTable.tsx
+++ b/apps/web/src/features/portfolio/components/TokenHoldingsTable.tsx
@@ -1,8 +1,7 @@
-// features/portfolio/components/TokenHoldingsTable.tsx
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) {
@@ -14,6 +13,8 @@ function formatSmart(value: string) {
}
export function TokenHoldingsTable({ data }: { data: TokenHolding[] }) {
+ const { getTokenForUI, NATIVE_PLACEHOLDER } = useTokenModule();
+
return (
Tokens
@@ -26,17 +27,22 @@ export function TokenHoldingsTable({ data }: { data: TokenHolding[] }) {
{data.map((h, i) => {
- // Map to UI token (WTTRUST -> tTRUST etc.)
- 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;
+ const isNative = !h.token.address;
+
+ // For native token, map to UI token (TRUST / tTRUST)
+ // For all ERC20 (including wTRUST), keep the raw token from the portfolio hook
+ const displayToken = isNative
+ ? getTokenForUI(NATIVE_PLACEHOLDER) ?? h.token
+ : h.token;
+
+ const addrForIcon = (displayToken.address ?? NATIVE_PLACEHOLDER) as string;
const icon = getTokenIcon(addrForIcon);
return (
-
- {uiToken.symbol}
+
+ {displayToken.symbol}
|
{formatSmart(h.balanceFormatted)} |
diff --git a/apps/web/src/features/portfolio/hooks/usePortfolio.ts b/apps/web/src/features/portfolio/hooks/usePortfolio.ts
index 746965bc7..a4f6a8a99 100644
--- a/apps/web/src/features/portfolio/hooks/usePortfolio.ts
+++ b/apps/web/src/features/portfolio/hooks/usePortfolio.ts
@@ -1,18 +1,12 @@
import { useEffect, useMemo, useState } from "react";
-import type { Address } from "viem";
+import type { Address, Abi } 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 { useAccount, usePublicClient, useChainId } from "wagmi";
+import { abi, getAddresses } from "@trustswap/sdk";
+import { useTokenModule } from "../../../hooks/useTokenModule";
type TokenInfoLite = {
- address?: Address; // undefined => native
+ address?: Address; // undefined => native
symbol: string;
decimals: number;
name?: string;
@@ -37,101 +31,159 @@ export type PoolPosition = {
amount1Formatted: string;
};
-const toAbi = (x: unknown) => (Array.isArray(x) ? x : (x as any)?.abi) as any;
+const toAbi = (x: unknown): Abi =>
+ (Array.isArray(x) ? x : (x as any)?.abi) as Abi;
+
const FACTORY_ABI = toAbi(abi.UniswapV2Factory);
const PAIR_ABI = toAbi(abi.UniswapV2Pair);
export function usePortfolio() {
const { address: account } = useAccount();
- const pc = usePublicClient();
- const [holdings, setHoldings] = useState(null);
+ const wagmiChainId = useChainId();
+ const fallbackChainId = wagmiChainId;
+
+ // Let wagmi give us the right client for the current chain
+ const pc = usePublicClient({ chainId: fallbackChainId });
+
+ const [holdings, setHoldings] = useState(null);
const [positions, setPositions] = useState(null);
- const [loading, setLoading] = useState(false);
- const [error, setError] = useState(null);
+ const [loading, setLoading] = useState(false);
+ const [error, setError] = useState(null);
- // Build UI token list: start from TOKENLIST and hide items flagged as hidden
- const uiTokenList = useMemo(() => toUIList(TOKENLIST), []);
+ const {
+ TOKENLIST,
+ toUIList,
+ getOrFetchToken,
+ NATIVE_PLACEHOLDER,
+ isNative,
+ } = useTokenModule();
+
+ const uiTokenList = useMemo(
+ () => toUIList(TOKENLIST),
+ [TOKENLIST, toUIList],
+ );
useEffect(() => {
if (!pc || !account) return;
+
+ const activeChainId = pc.chain?.id ?? fallbackChainId;
+ const { UniswapV2Factory } = getAddresses(Number(activeChainId));
+
+ if (!UniswapV2Factory) {
+ setError(`No UniswapV2Factory address configured for chainId ${activeChainId}`);
+ setLoading(false);
+ return;
+ }
+
+ const factory = UniswapV2Factory as Address;
let cancelled = false;
async function run() {
try {
- if (!pc) return;
setLoading(true);
setError(null);
- // --- 1) Token holdings (native + ERC20 in TOKENLIST) ---
- // Native (placeholder in TOKENLIST has isNative=true)
- const nativeEntry = TOKENLIST.find(t => t.isNative);
- if (!account) throw new Error("Account address is undefined");
+ // --- 1) Native balance ---
+ const nativeEntry = TOKENLIST.find((t) => t.isNative);
const nativeBal = await pc.getBalance({ address: account });
const baseHoldings: TokenHolding[] = [
{
token: {
- // no on-chain address for native; keep undefined
symbol: nativeEntry?.symbol ?? "tTRUST",
decimals: nativeEntry?.decimals ?? 18,
name: nativeEntry?.name,
},
balance: nativeBal,
- balanceFormatted: formatUnits(nativeBal, nativeEntry?.decimals ?? 18),
+ balanceFormatted: formatUnits(
+ nativeBal,
+ nativeEntry?.decimals ?? 18,
+ ),
},
];
- // ERC20 balances for UI tokens (exclude native placeholder)
- const erc20List = uiTokenList.filter(t => t.address.toLowerCase() !== NATIVE_PLACEHOLDER.toLowerCase());
+ // --- 2) ERC20 balances from token list ---
+ const erc20List = uiTokenList.filter(
+ (t) => t.address.toLowerCase() !== NATIVE_PLACEHOLDER.toLowerCase(),
+ );
+
const erc20Calls = erc20List.map((t) => ({
address: t.address as Address,
abi: erc20Abi,
functionName: "balanceOf" as const,
args: [account],
}));
- const erc20Res = erc20Calls.length ? await pc.multicall({ contracts: erc20Calls }) : [];
+
+ const erc20Res = erc20Calls.length
+ ? await pc.multicall({ contracts: erc20Calls })
+ : [];
erc20Res.forEach((r, i) => {
const meta = erc20List[i];
const bal = r.status === "success" ? (r.result as bigint) : 0n;
baseHoldings.push({
- token: { address: meta.address as Address, symbol: meta.symbol, decimals: meta.decimals, name: meta.name },
+ token: {
+ address: meta.address as Address,
+ symbol: meta.symbol,
+ decimals: meta.decimals,
+ name: meta.name,
+ },
balance: bal,
balanceFormatted: formatUnits(bal, meta.decimals),
});
});
// Keep native even if zero; filter out zero ERC20s
- const nonZero = baseHoldings.filter(h => h.balance > 0n || !h.token.address);
+ const nonZero = baseHoldings.filter(
+ (h) => h.balance > 0n || !h.token.address,
+ );
- // --- 2) Pool positions (scan recent pairs from factory) ---
- const factory = addresses.UniswapV2Factory as Address;
+ // --- 3) Pool positions: scan recent pairs from factory ---
const len = (await pc.readContract({
address: factory,
abi: FACTORY_ABI,
functionName: "allPairsLength",
- args: [],
})) as bigint;
- const MAX_SCAN = 1000n; // adjust to your DEX size
+ const MAX_SCAN = 1000n; // adjust as needed
const scanLen = len > MAX_SCAN ? MAX_SCAN : len;
+
+ if (scanLen === 0n) {
+ if (!cancelled) {
+ setHoldings(
+ nonZero.sort((a, b) =>
+ a.balance === b.balance ? 0 : a.balance < b.balance ? 1 : -1,
+ ),
+ );
+ setPositions([]);
+ setLoading(false);
+ }
+ return;
+ }
+
const start = len - scanLen;
- const pairIdxCalls = Array.from({ length: Number(scanLen) }, (_, k) => ({
- address: factory,
- abi: FACTORY_ABI,
- functionName: "allPairs" as const,
- args: [start + BigInt(k)],
- }));
+ const pairIdxCalls = Array.from(
+ { length: Number(scanLen) },
+ (_, k) => ({
+ address: factory,
+ abi: FACTORY_ABI,
+ functionName: "allPairs" as const,
+ args: [start + BigInt(k)],
+ }),
+ );
+
const pairIdxRes = await pc.multicall({ contracts: pairIdxCalls });
const pairAddresses = pairIdxRes
- .map(r => (r.status === "success" ? (r.result as Address) : null))
+ .map((r) => (r.status === "success" ? (r.result as Address) : null))
.filter(Boolean) as Address[];
if (pairAddresses.length === 0) {
if (!cancelled) {
setHoldings(
- nonZero.sort((a, b) => (a.balance === b.balance ? 0 : a.balance < b.balance ? 1 : -1))
+ nonZero.sort((a, b) =>
+ a.balance === b.balance ? 0 : a.balance < b.balance ? 1 : -1,
+ ),
);
setPositions([]);
setLoading(false);
@@ -179,6 +231,7 @@ export function usePortfolio() {
]);
const out: PoolPosition[] = [];
+
for (let i = 0; i < pairAddresses.length; i++) {
const ok =
balRes[i]?.status === "success" &&
@@ -186,38 +239,53 @@ export function usePortfolio() {
t0Res[i]?.status === "success" &&
t1Res[i]?.status === "success" &&
rsvRes[i]?.status === "success";
+
if (!ok) continue;
const lpBal = balRes[i].result as bigint;
if (lpBal === 0n) continue;
const totalSupply = tsRes[i].result as bigint;
+ if (totalSupply === 0n) continue;
+
const token0Addr = t0Res[i].result as Address;
const token1Addr = t1Res[i].result as Address;
- // Parse reserves as tuple: [reserve0, reserve1, blockTimestampLast]
- const [reserve0, reserve1] = rsvRes[i].result as readonly [bigint, bigint, number];
+ const [reserve0, reserve1] = rsvRes[i].result as readonly [
+ bigint,
+ bigint,
+ number,
+ ];
- // Guard against zero totalSupply
- if (totalSupply === 0n) continue;
-
- // Map WTTRUST -> native placeholder for UI, fetch meta safely
const [t0Meta, t1Meta] = await Promise.all([
- getOrFetchToken(isNative(token0Addr) ? NATIVE_PLACEHOLDER : token0Addr),
- getOrFetchToken(isNative(token1Addr) ? NATIVE_PLACEHOLDER : token1Addr),
+ getOrFetchToken(
+ isNative(token0Addr) ? NATIVE_PLACEHOLDER : token0Addr,
+ ),
+ getOrFetchToken(
+ isNative(token1Addr) ? NATIVE_PLACEHOLDER : token1Addr,
+ ),
]);
- // All-bigint math first
- const amt0 = (reserve0 * lpBal) / totalSupply; // bigint
- const amt1 = (reserve1 * lpBal) / totalSupply; // bigint
+ const amt0 = (reserve0 * lpBal) / totalSupply;
+ const amt1 = (reserve1 * lpBal) / totalSupply;
- // sharePct as number with 2 decimals (basis points / 100)
- const sharePct = Number((lpBal * 10000n) / totalSupply) / 100;
+ const sharePct =
+ Number((lpBal * 10000n) / totalSupply) / 100;
out.push({
pairAddress: pairAddresses[i],
- token0: { address: t0Meta.address, symbol: t0Meta.symbol, decimals: t0Meta.decimals, name: t0Meta.name },
- token1: { address: t1Meta.address, symbol: t1Meta.symbol, decimals: t1Meta.decimals, name: t1Meta.name },
+ token0: {
+ address: t0Meta.address,
+ symbol: t0Meta.symbol,
+ decimals: t0Meta.decimals,
+ name: t0Meta.name,
+ },
+ token1: {
+ address: t1Meta.address,
+ symbol: t1Meta.symbol,
+ decimals: t1Meta.decimals,
+ name: t1Meta.name,
+ },
lpBalance: lpBal,
lpBalanceFormatted: formatUnits(lpBal, 18),
sharePct,
@@ -229,7 +297,11 @@ export function usePortfolio() {
}
if (!cancelled) {
- setHoldings(nonZero.sort((a, b) => (a.balance === b.balance ? 0 : a.balance < b.balance ? 1 : -1)));
+ setHoldings(
+ nonZero.sort((a, b) =>
+ a.balance === b.balance ? 0 : a.balance < b.balance ? 1 : -1,
+ ),
+ );
setPositions(out);
setLoading(false);
}
@@ -242,10 +314,20 @@ export function usePortfolio() {
}
run();
+
return () => {
cancelled = true;
};
- }, [pc, account, uiTokenList]);
+ }, [
+ pc,
+ account,
+ fallbackChainId,
+ TOKENLIST,
+ uiTokenList,
+ getOrFetchToken,
+ NATIVE_PLACEHOLDER,
+ isNative,
+ ]);
return { holdings, positions, loading, error };
}
diff --git a/apps/web/src/features/swap/components/DetailsDisclosure.tsx b/apps/web/src/features/swap/components/DetailsDisclosure.tsx
index 1830c6e99..9d2379871 100644
--- a/apps/web/src/features/swap/components/DetailsDisclosure.tsx
+++ b/apps/web/src/features/swap/components/DetailsDisclosure.tsx
@@ -11,7 +11,7 @@ export default function DetailsDisclosure({
}: {
slippageBps: number;
onChangeSlippage: (v: number) => void;
- priceText?: string; // ex: "1 WTTRUST ≈ 123.456 TSWP"
+ priceText?: string; // ex: "1 WTRUST ≈ 123.456 TSWP"
priceImpactPct?: number | null; // ex: 0.42 (%)
networkFeeText?: string | null; // ex: "0.00087 tTRUST"
}) {
diff --git a/apps/web/src/features/swap/components/SwapForm.tsx b/apps/web/src/features/swap/components/SwapForm.tsx
index 62bb5e2f4..3f5968c30 100644
--- a/apps/web/src/features/swap/components/SwapForm.tsx
+++ b/apps/web/src/features/swap/components/SwapForm.tsx
@@ -2,12 +2,9 @@
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 { useAccount, usePublicClient, useChainId } from "wagmi";
+import { useTokenModule } from "../../../hooks/useTokenModule";
+
import { useImportedTokens } from "../hooks/useImportedTokens";
import { useQuoteDetails } from "../hooks/useQuoteDetails";
import { useAllowance } from "../hooks/useAllowance";
@@ -22,10 +19,9 @@ import TokenField from "./TokenField";
import FlipButton from "./FlipButton";
import ApproveAndSwap from "./ApproveAndSwap";
import DetailsDisclosure from "./DetailsDisclosure";
-import { addresses, abi } from "@trustswap/sdk";
+import { addresses as TESTNET_ADDRESSES, abi, getAddresses } from "@trustswap/sdk";
+
-const isNative = (a?: Address) =>
- !!a && a.toLowerCase() === NATIVE_PLACEHOLDER.toLowerCase();
const norm = (a?: string) => (a ? a.toLowerCase() : "");
@@ -66,6 +62,26 @@ type Meta = {
export default function SwapForm() {
const { address } = useAccount();
const pc = usePublicClient();
+ const chainId = useChainId();
+
+ const {
+ getDefaultPair,
+ TOKENLIST,
+ NATIVE_PLACEHOLDER,
+ WNATIVE_ADDRESS,
+ getTokenByAddressOrFallback,
+} = useTokenModule();
+
+ const resolvedChainId = chainId;
+ const { WTTRUST, TSWP, UniswapV2Router02 } = getAddresses(resolvedChainId);
+ const WNATIVE = WNATIVE_ADDRESS as Address;
+ const ROUTER = UniswapV2Router02 as Address;
+
+ const isNative = (a?: Address) =>
+ !!a && a.toLowerCase() === NATIVE_PLACEHOLDER.toLowerCase();
+
+ const isWrapped = (a?: Address) =>
+ !!a && a.toLowerCase() === WTTRUST.toLowerCase();
const defaults = useMemo(() => getDefaultPair(), []);
const [tokenIn, setTokenIn] = useState(defaults.tokenIn.address);
@@ -113,7 +129,8 @@ export default function SwapForm() {
});
}
return m;
- }, [imported]);
+ }, [TOKENLIST, imported]);
+
function getMeta(addr?: Address): Meta {
if (!addr) {
@@ -134,18 +151,19 @@ export default function SwapForm() {
}
function buildPaths(tin: Address, tout: Address): Address[][] {
- const WT = addresses.WTTRUST as Address;
- const TSWP = addresses.TSWP as Address;
+ const WT = WNATIVE as Address;
+ const tswpAddr = TSWP as Address;
const A = isNative(tin) ? WT : tin;
const B = isNative(tout) ? WT : tout;
-
- const paths: Address[][] = [
- [A, B],
- [A, WT, B],
- [A, TSWP, B],
- ];
+ const paths: Address[][] = [[A, B], [A, WT, B]];
+
+ // Optional governance token hop if configured and distinct
+ const isZero = (addr: Address) => addr.toLowerCase() === "0x0000000000000000000000000000000000000000";
+ if (!isZero(tswpAddr) && tswpAddr !== A && tswpAddr !== B) {
+ paths.push([A, tswpAddr, B]);
+ }
const seen = new Set();
const uniq: Address[][] = [];
@@ -159,6 +177,7 @@ export default function SwapForm() {
return uniq;
}
+
async function fastRouterQuote(
tin: Address,
@@ -170,7 +189,7 @@ export default function SwapForm() {
const paths = buildPaths(tin, tout);
const calls = paths.map(async (path) => {
const amounts = (await pc.readContract({
- address: addresses.UniswapV2Router02 as Address,
+ address: ROUTER,
abi: abi.UniswapV2Router02,
functionName: "getAmountsOut",
args: [amtIn, path],
@@ -318,9 +337,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 +358,9 @@ export default function SwapForm() {
(pairData as any)?.decimals1,
bestPath?.join(">"),
lastOutBn?.toString(),
+ NATIVE_PLACEHOLDER,
+ WNATIVE_ADDRESS,
+ getTokenByAddressOrFallback,
]);
async function onApproveAndSwap() {
@@ -343,36 +369,53 @@ export default function SwapForm() {
const v = Number(normalizeAmountStr(amountIn));
if (!isFinite(v) || v <= 0) return;
- // Réutilise la dernière quote si disponible, sinon hook
- const outBn =
- lastOutBn ??
- (await (async () => {
- const qd = await quoteDetails(
- tokenIn,
- tokenOut ?? "0x0000000000000000000000000000000000000000",
- String(v)
- );
- if (!qd) throw new Error("No route/liquidity for this pair");
- return qd.amountOutBn;
- })());
- const minOut = outBn - (outBn * BigInt(slippageBps)) / 10_000n;
+ const isWrap =
+ !!tokenOut &&
+ isNative(tokenIn) &&
+ isWrapped(tokenOut as Address);
+
+ const isUnwrap =
+ !!tokenOut &&
+ isWrapped(tokenIn as Address) &&
+ isNative(tokenOut as Address);
+
+ const sameAsset = isWrap || isUnwrap;
+
+ let outBn: bigint;
+
+ if (sameAsset) {
+ // For wrap/unwrap, 1:1 amount, no routing
+ const tiMeta = getMeta(tokenIn);
+ outBn = parseUnits(String(v), tiMeta.decimals);
+ } else {
+ // Reuse last quote if available, otherwise recompute with quoteDetails
+ outBn =
+ lastOutBn ??
+ (await (async () => {
+ const qd = await quoteDetails(
+ tokenIn,
+ tokenOut ?? "0x0000000000000000000000000000000000000000",
+ String(v)
+ );
+ if (!qd) throw new Error("No route/liquidity for this pair");
+ return qd.amountOutBn;
+ })());
+ }
+
+ const minOut = sameAsset
+ ? outBn // wrap/unwrap is 1:1, no slippage
+ : outBn - (outBn * BigInt(slippageBps)) / 10_000n;
+
const deadline = Math.floor(Date.now() / 1000) + 60 * 20;
const ti = getMeta(tokenIn);
const amtIn = parseUnits(String(v), ti.decimals);
- if (!isNative(tokenIn)) {
- const curr = await allowance(
- address,
- tokenIn,
- addresses.UniswapV2Router02 as Address
- );
+ // No allowance/approve for wrap/unwrap, only for router swaps
+ if (!sameAsset && !isNative(tokenIn)) {
+ const curr = await allowance(address, tokenIn, ROUTER);
if (curr < amtIn) {
- await approve(
- tokenIn,
- addresses.UniswapV2Router02 as Address,
- amtIn
- );
+ await approve(tokenIn, ROUTER, amtIn);
}
}
diff --git a/apps/web/src/features/swap/components/TokenSelector.tsx b/apps/web/src/features/swap/components/TokenSelector.tsx
index caa9659fc..91b7f4a78 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,9 +52,10 @@ 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]
+ [tokens, TOKENLIST]
);
// Imported tokens (user)
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..262dc84eb 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());
@@ -91,7 +89,7 @@ export function useDynamicTokenList(rawPools: any) {
for (const f of fetched) {
if (f && !map.has(low(f.address))) {
- const hidden = low(f.address) === low(WNATIVE_ADDRESS); // masque WTTRUST si besoin
+ const hidden = low(f.address) === low(WNATIVE_ADDRESS); // masque WTRUST si besoin
map.set(low(f.address), hidden ? { ...f, hidden: true } : f);
}
}
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/useQuoteDetails.ts b/apps/web/src/features/swap/hooks/useQuoteDetails.ts
index e86b912a6..4479f90cb 100644
--- a/apps/web/src/features/swap/hooks/useQuoteDetails.ts
+++ b/apps/web/src/features/swap/hooks/useQuoteDetails.ts
@@ -1,23 +1,33 @@
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 } = useTrustswapAddresses();
+
+ const {
+ NATIVE_PLACEHOLDER,
+ WNATIVE_ADDRESS,
+ isNative,
+ getTokenByAddress,
+ } = useTokenModule();
return async function getQuoteDetails(
tokenIn: Address,
@@ -30,31 +40,44 @@ 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 wNative = WNATIVE_ADDRESS as Address;
+
+ 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 +85,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/features/swap/hooks/useSwap.ts b/apps/web/src/features/swap/hooks/useSwap.ts
index 50335aba7..a384adfa6 100644
--- a/apps/web/src/features/swap/hooks/useSwap.ts
+++ b/apps/web/src/features/swap/hooks/useSwap.ts
@@ -1,30 +1,15 @@
import type { Address } from "viem";
import { erc20Abi, parseUnits, formatUnits } from "viem";
import { usePublicClient, useWalletClient, useChainId } from "wagmi";
-import { getOrFetchToken } from "../../../lib/tokens";
-import { abi, addresses } from "@trustswap/sdk";
+import { useTokenModule } from "../../../hooks/useTokenModule";
+import { abi, getAddresses } from "@trustswap/sdk";
import { useAlerts } from "../../../features/alerts/Alerts";
-const NATIVE_PLACEHOLDER = addresses.NATIVE_PLACEHOLDER as Address; // tTRUST (pseudo "native")
-const WNATIVE = addresses.WTTRUST as Address; // WTTRUST (wrapped)
-const ROUTER = addresses.UniswapV2Router02 as Address;
-
-const isNative = (addr?: Address) =>
- !!addr && addr.toLowerCase() === NATIVE_PLACEHOLDER.toLowerCase();
-
-const isWrapped = (addr?: Address) =>
- !!addr && addr.toLowerCase() === WNATIVE.toLowerCase();
-
-const eqAddr = (a?: Address, b?: Address) =>
- !!a && !!b && a.toLowerCase() === b.toLowerCase();
-
-const toWrapped = (addr: Address) => (isNative(addr) ? WNATIVE : addr);
-const buildPath = (path: Address[]) => path.map(toWrapped) as Address[];
-
function explorerTx(chainId: number | undefined, hash?: `0x${string}`) {
if (!hash) return undefined;
const map: Record = {
13579: "https://testnet.explorer.intuition.systems/tx/",
+ 1155: "https://explorer.intuition.systems/tx/",
};
const base = map[chainId ?? 0];
return base ? `${base}${hash}` : undefined;
@@ -32,9 +17,12 @@ function explorerTx(chainId: number | undefined, hash?: `0x${string}`) {
function prettifySwapError(err: any): string {
const raw =
- String(err?.shortMessage || "") + " | " +
- String(err?.message || "") + " | " +
- String(err?.cause?.shortMessage || "") + " | " +
+ String(err?.shortMessage || "") +
+ " | " +
+ String(err?.message || "") +
+ " | " +
+ String(err?.cause?.shortMessage || "") +
+ " | " +
String(err?.cause?.message || "");
const msg = raw.toLowerCase();
@@ -44,7 +32,8 @@ function prettifySwapError(err: any): string {
msg.includes("request rejected") ||
msg.includes("action rejected") ||
String(err?.code) === "4001"
- ) return "Transaction rejected by user.";
+ )
+ return "Transaction rejected by user.";
if (raw.includes("TransferHelper::transferFrom: transferFrom failed"))
return "Insufficient allowance or balance for the input token.";
@@ -53,7 +42,8 @@ function prettifySwapError(err: any): string {
raw.includes("INSUFFICIENT_OUTPUT_AMOUNT") ||
raw.includes("ExcessiveInputAmount") ||
msg.includes("insufficient output")
- ) return "Slippage too low (insufficient output).";
+ )
+ return "Slippage too low (insufficient output).";
if (msg.includes("deadline") || msg.includes("expired"))
return "Transaction deadline exceeded.";
@@ -61,11 +51,9 @@ function prettifySwapError(err: any): string {
if (raw.includes("Transfers restricted") || msg.includes("transfer restricted"))
return "Token has transfer restrictions.";
- if (msg.includes("insufficient funds for gas"))
- return "Insufficient funds for gas.";
+ if (msg.includes("insufficient funds for gas")) return "Insufficient funds for gas.";
- if (msg.includes("nonce too low"))
- return "Nonce too low.";
+ if (msg.includes("nonce too low")) return "Nonce too low.";
if (msg.includes("replacement transaction underpriced") || msg.includes("fee too low"))
return "Replacement transaction underpriced.";
@@ -78,17 +66,34 @@ export function useSwap() {
const chainId = useChainId();
const alerts = useAlerts();
+ const {
+ NATIVE_PLACEHOLDER,
+ WNATIVE_ADDRESS,
+ isNative,
+ isWrapped,
+ toWrapped,
+ buildPath,
+ getOrFetchToken,
+ } = useTokenModule();
+
+ const resolvedChainId = chainId;
+ const { UniswapV2Router02 } = getAddresses(resolvedChainId);
+ const router = UniswapV2Router02 as Address;
+ const nativePlaceholder = NATIVE_PLACEHOLDER as Address;
+ const wNative = WNATIVE_ADDRESS as Address;
+
+ const eqAddr = (a?: Address, b?: Address) =>
+ !!a && !!b && a.toLowerCase() === b.toLowerCase();
+
const approveIfNeeded = async (token: Address, owner: Address, amount: bigint) => {
if (!publicClient || !wallet) throw new Error("Wallet not connected");
- if (isNative(token)) return; // no approve for pseudo-native
- if (isWrapped(token)) return; // no approve needed for wrap/unwrap flows
-
+ if (isNative(token)) return; // do not approve native
const allowance = (await publicClient.readContract({
address: token,
abi: erc20Abi,
functionName: "allowance",
- args: [owner, ROUTER],
+ args: [owner, router],
})) as bigint;
if (allowance >= amount) return;
@@ -98,7 +103,7 @@ export function useSwap() {
address: token,
abi: erc20Abi,
functionName: "approve",
- args: [ROUTER, amount],
+ args: [router, amount],
});
alerts.push({
@@ -146,15 +151,14 @@ export function useSwap() {
if (!wallet) throw new Error("Wallet not connected");
if (!publicClient) throw new Error("No public client");
if (!tokenIn || !tokenOut) throw new Error("Missing token addresses");
- // Guard unsupported flows: TOKEN -> WTTRUST without a pool
+
+ // Guard: direct non-native -> wrapped without pool
if (isWrapped(tokenOut) && !isNative(tokenIn)) {
throw new Error(
- "Cannot swap directly to WTTRUST from non-native token. Swap to tTRUST first, then wrap to WTTRUST."
+ "Cannot swap directly to wrapped native from non-native token. Swap to native first, then wrap."
);
}
-
- // Allow native <-> wrapped as a valid "pair" even if addresses are effectively the same asset
const sameAsset =
(isNative(tokenIn) && isWrapped(tokenOut)) ||
(isWrapped(tokenIn) && isNative(tokenOut));
@@ -167,7 +171,6 @@ export function useSwap() {
throw new Error("Native-to-native swap is not supported");
}
- // Resolve decimals for input
const [tIn, tOut] = await Promise.all([
getOrFetchToken(toWrapped(tokenIn)),
getOrFetchToken(toWrapped(tokenOut)),
@@ -181,30 +184,28 @@ export function useSwap() {
try {
let hash: `0x${string}`;
- // ===== Wrap / Unwrap special-cases (no router) =====
+ // Wrap / unwrap (direct calls to wrapped native contract)
if (isNative(tokenIn) && isWrapped(tokenOut)) {
- // Wrap: deposit native into WTTRUST
hash = await wallet.writeContract({
- address: WNATIVE,
- abi: abi.WTTRUST, // must include deposit()/withdraw(uint256)
+ address: wNative,
+ abi: abi.WTTRUST, // must be the wrapper ABI
functionName: "deposit",
args: [],
value: amountIn,
});
} else if (isWrapped(tokenIn) && isNative(tokenOut)) {
- // Unwrap: withdraw WTTRUST back to native
hash = await wallet.writeContract({
- address: WNATIVE,
+ address: wNative,
abi: abi.WTTRUST,
functionName: "withdraw",
args: [amountIn],
});
}
- // ===== Router swaps (classic) =====
+ // Router swaps
else if (isNative(tokenIn) && !isNative(tokenOut)) {
const path = buildPath([tokenIn, tokenOut]);
hash = await wallet.writeContract({
- address: ROUTER,
+ address: router,
abi: abi.UniswapV2Router02,
functionName: "swapExactETHForTokens",
args: [minOut, path, owner, deadline],
@@ -214,7 +215,7 @@ export function useSwap() {
const path = buildPath([tokenIn, tokenOut]);
await approveIfNeeded(tokenIn, owner, amountIn);
hash = await wallet.writeContract({
- address: ROUTER,
+ address: router,
abi: abi.UniswapV2Router02,
functionName: "swapExactTokensForETH",
args: [amountIn, minOut, path, owner, deadline],
@@ -223,7 +224,7 @@ export function useSwap() {
const path = buildPath([tokenIn, tokenOut]);
await approveIfNeeded(tokenIn, owner, amountIn);
hash = await wallet.writeContract({
- address: ROUTER,
+ address: router,
abi: abi.UniswapV2Router02,
functionName: "swapExactTokensForTokens",
args: [amountIn, minOut, path, owner, deadline],
@@ -241,9 +242,9 @@ export function useSwap() {
const receipt = await publicClient.waitForTransactionReceipt({ hash });
- // Friendly success message
- const labelIn = isNative(tokenIn) ? "tTRUST" : (tIn.symbol || "TOKEN");
- const labelOut = isNative(tokenOut) ? "tTRUST" : (tOut.symbol || "TOKEN");
+ const labelIn = isNative(tokenIn) ? "TRUST" : tIn.symbol || "TOKEN";
+ const labelOut =
+ isNative(tokenOut) ? "TRUST" : isWrapped(tokenOut) ? "wTRUST" : tOut.symbol || "TOKEN";
const shownMinOut = formatUnits(minOut, Number(tOut.decimals ?? 18));
alerts.push({
diff --git a/apps/web/src/features/swap/hooks/useTokenBalance.ts b/apps/web/src/features/swap/hooks/useTokenBalance.ts
index ce8da0896..449ff1cf0 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,
@@ -61,14 +59,14 @@ 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!)
+ // récupère meta on-chain si besoin (ne throw pas si hors TOKENLIST)
+ 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/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..c29d9e1ff
--- /dev/null
+++ b/apps/web/src/hooks/useTokenModule.ts
@@ -0,0 +1,24 @@
+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";
+
+
+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 b424a5154..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 { INTUITION } 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: INTUITION.id,
- networkId: INTUITION.id,
- name: INTUITION.name,
+ chainId: chain.id,
+ networkId: chain.id,
+ name: chain.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,
- 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/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[] = [
diff --git a/apps/web/src/lib/tokenIcons.ts b/apps/web/src/lib/tokenIcons.ts
index f7be24278..485378896 100644
--- a/apps/web/src/lib/tokenIcons.ts
+++ b/apps/web/src/lib/tokenIcons.ts
@@ -6,5 +6,6 @@ import { addresses } from "@trustswap/sdk";
export const tokenIcons: Record = {
[addresses.NATIVE_PLACEHOLDER]: tTrustIcon,
"0xc82d6A5e0Da8Ce7B37330C4D44E9f069269546E6": tTrustIcon,
+ "0x81cFb09cb44f7184Ad934C09F82000701A4bF672" : tTrustIcon,
"0x7da120065e104C085fAc6f800d257a6296549cF3": tswp,
};
diff --git a/apps/web/src/lib/tokens.ts b/apps/web/src/lib/tokens.ts
index bc88b6329..8d3ccc5cb 100644
--- a/apps/web/src/lib/tokens.ts
+++ b/apps/web/src/lib/tokens.ts
@@ -1,10 +1,6 @@
-import type { Address } from "viem";
-import { INTUITION, 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;
-
+import type { Address, Chain } from "viem";
+import type { Addresses } from "@trustswap/sdk";
+import { createPublicClient, http, erc20Abi, zeroAddress } from "viem";
export type TokenInfo = {
address: Address;
@@ -12,139 +8,205 @@ export type TokenInfo = {
name: string;
decimals: number;
isNative?: boolean;
- hidden?: boolean; // 👈 ajouté
+ hidden?: boolean;
};
-export const TOKENLIST: TokenInfo[] = [
- {
+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 WT = (addrBook.WTTRUST ?? zeroAddress) as Address;
+ const TSWP_ADDR = (addrBook.TSWP ?? zeroAddress) as Address;
+
+ const isZero = (addr?: string) =>
+ !addr || addr.toLowerCase() === zeroAddress;
+
+ const WNATIVE_ADDRESS = WT;
+
+ const baseTokens: TokenInfo[] = [];
+
+ // Native pseudo-token
+ baseTokens.push({
address: NATIVE_PLACEHOLDER,
- symbol: INTUITION?.nativeCurrency?.symbol ?? "tTRUST",
- name: INTUITION?.nativeCurrency?.name ?? "Native TRUST",
+ symbol: chain.nativeCurrency?.symbol ?? "tTRUST",
+ name: chain.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,
- },
-];
+ });
+ // Choose wrapped symbol per chain
+ const wrappedSymbol = chain.id === 1155 ? "WTRUST" : "WTTRUST";
-const TOKEN_CACHE: Record = {};
-for (const t of TOKENLIST) TOKEN_CACHE[t.address.toLowerCase()] = t;
+ // Wrapped native
+ if (!isZero(WT)) {
+ baseTokens.push({
+ address: WT,
+ symbol: wrappedSymbol,
+ name: "Wrapped TRUST",
+ decimals: 18,
+ hidden: false,
+ });
+ }
-const client = createPublicClient({
- chain: INTUITION,
- transport: http(INTUITION.rpcUrls?.default?.http?.[0] || ""),
-});
+ // Governance token (optional, may be absent on some networks)
+ if (!isZero(TSWP_ADDR)) {
+ baseTokens.push({
+ address: TSWP_ADDR,
+ symbol: "TSWP",
+ name: "TrustSwap",
+ decimals: 18,
+ });
+ }
-function findToken(addr: string): TokenInfo | null {
- return TOKEN_CACHE[addr.toLowerCase()] || null;
-}
+ const TOKENLIST: TokenInfo[] = baseTokens;
+ const TOKEN_CACHE: Record = {};
+ for (const t of TOKENLIST) {
+ if (!t.address) continue;
+ TOKEN_CACHE[t.address.toLowerCase()] = t;
+ }
-export const isNative = (addr?: string) =>
- !!addr && addr.toLowerCase() === NATIVE_PLACEHOLDER.toLowerCase();
+ const client = createPublicClient({
+ chain,
+ transport: http(chain.rpcUrls.default.http[0]),
+ });
-export const toWrapped = (addr: Address): Address =>
- isNative(addr) ? WNATIVE_ADDRESS : addr;
+ function findToken(addr: string): TokenInfo | null {
+ return TOKEN_CACHE[addr.toLowerCase()] || null;
+ }
-export const buildPath = (path: Address[]): Address[] =>
- path.map(toWrapped) as Address[];
+ const isNative = (addr?: string) =>
+ !!addr && addr.toLowerCase() === NATIVE_PLACEHOLDER.toLowerCase();
-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;
-}
+ const toWrapped = (addr: Address): Address =>
+ isNative(addr) ? WNATIVE_ADDRESS : addr;
-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 buildPath = (path: Address[]): Address[] =>
+ path.map(toWrapped) as Address[];
-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 isWrapped = (addr?: string) =>
+ !!addr && addr.toLowerCase() === WNATIVE_ADDRESS.toLowerCase();
- TOKEN_CACHE[address.toLowerCase()] = info;
- return info;
-}
+ const toUIAddress = (addr?: Address): Address | undefined =>
+ !addr ? undefined : isWrapped(addr) ? NATIVE_PLACEHOLDER : addr;
-export const isWrapped = (addr?: string) =>
- !!addr && addr.toLowerCase() === WNATIVE_ADDRESS.toLowerCase();
+ function getTokenByAddress(addr: string | Address): TokenInfo {
+ const t = TOKENLIST.find(
+ t => t.address.toLowerCase() === String(addr).toLowerCase(),
+ );
+ if (!t) throw new Error(`Token not in tokenlist: ${addr}`);
+ return t;
+ }
-/** Adresse à afficher en UI: WTTRUST → tTRUST (placeholder) */
-export const toUIAddress = (addr?: Address): Address | undefined =>
- !addr ? undefined : isWrapped(addr) ? NATIVE_PLACEHOLDER : addr;
+ 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 };
+ }
-/** Liste de tokens pour l'UI (on masque les hidden = WTTRUST) */
-export function toUIList(list: TokenInfo[]): TokenInfo[] {
- return list.filter(t => !t.hidden);
-}
+ 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),
+ };
-/** 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;
-}
+ 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,
};
}
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/apps/web/src/styles/Layout.module.css b/apps/web/src/styles/Layout.module.css
index aafc062da..5a0d49e2e 100644
--- a/apps/web/src/styles/Layout.module.css
+++ b/apps/web/src/styles/Layout.module.css
@@ -256,4 +256,63 @@
}
+.networkSelectContainer {
+ position: fixed;
+ top: 3vh;
+ right: 19vh;
+ padding: 5px;
+ z-index: 10000;
+}
+
+.networkSelect {
+ height: 30px;
+ border-radius: 2vh;
+ background-color: var(--black);
+ color: var(--text-color);
+ border: 1px solid var(--black-border);
+ padding: 0 30px 0 16px; /* espace pour la flèche */
+ font-size: 11px;
+ cursor: pointer;
+
+ box-shadow:
+ inset 0 1px 3px -0.25px rgba(255, 255, 255, 0.12),
+ inset 0 0.5px 0.25px -0.25px rgba(255, 255, 255, 0.16),
+ inset 0 -0.75px 0.5px var(--btn-shadow-black),
+ 0 4px 4px -1px var(--btn-shadow-black),
+ 0 2px 3px -1px var(--btn-shadow-black),
+ 0 0.5px 0.5px var(--btn-shadow-black),
+ 0 0 0 0.75px var(--btn-shadow-black);
+ backdrop-filter: blur(5px);
+ -webkit-backdrop-filter: blur(5px);
+
+ appearance: none;
+ outline: none;
+ transition: all 0.25s ease;
+
+ background-image: url("data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTQiIGhlaWdodD0iOCIgdmlld0JveD0iMCAwIDE0IDgiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgPHBhdGggZD0iTTcgOEwwIDBIMTRMOSAwTDcgOFoiIGZpbGw9IiNmZmYiLz4KPC9zdmc+Cg==");
+ background-repeat: no-repeat;
+ background-position: right 14px center;
+ background-size: 12px;
+}
+
+/* Hover */
+.networkSelect:hover {
+ color: var(--text-color);
+ border-color: rgba(255,255,255,0.18);
+ transform: translateY(-1px);
+}
+
+/* Active (click) */
+.networkSelect:active {
+ transform: scale(0.98);
+}
+
+/* Dropdown options */
+.networkSelect option {
+ background-color: var(--black);
+ color: var(--text-color);
+ border: none;
+ padding: 10px;
+ font-size: 14px;
+}
diff --git a/packages/config/src/multicall3.json b/packages/config/src/multicall3.json
index e7084584d..d7943f63a 100644
--- a/packages/config/src/multicall3.json
+++ b/packages/config/src/multicall3.json
@@ -1,4 +1,8 @@
{
+ "1155": {
+ "address": "0x31E7C4ef16e1c3c149D2F0a62517d621bDa6D037",
+ "blockCreated": 117543
+ },
"13579": {
"address": "0x6E26ea6ab2236a28e3F2B59F532b79273e0Dc575",
"blockCreated": 4252441
diff --git a/packages/contracts/deploy/00_deploy_factory.ts b/packages/contracts/deploy/00_deploy_factory.ts
index 01fbe4858..e7f6181b3 100644
--- a/packages/contracts/deploy/00_deploy_factory.ts
+++ b/packages/contracts/deploy/00_deploy_factory.ts
@@ -1,9 +1,26 @@
import { ethers } from "hardhat";
+
async function main() {
+ // Use first signer as temporary feeToSetter (admin for protocol fees)
+ const [deployer] = await ethers.getSigners();
+ const feeToSetter = await deployer.getAddress();
+
+ const network = await ethers.provider.getNetwork();
+ const chainId = Number(network.chainId);
+
+ console.log("Deploying UniswapV2Factory...");
+ console.log(" chainId:", chainId);
+ console.log(" deployer / feeToSetter:", feeToSetter);
+
const Factory = await ethers.getContractFactory("UniswapV2Factory");
- const feeToSetter = process.env.FEE_TO!;
- const f = await Factory.deploy(feeToSetter);
- await f.waitForDeployment();
- console.log("Factory:", await f.getAddress());
+ const factory = await Factory.deploy(feeToSetter);
+ await factory.waitForDeployment();
+
+ const addr = await factory.getAddress();
+ console.log("UniswapV2Factory deployed at:", addr);
}
-main().catch(e=>{console.error(e);process.exit(1)});
+
+main().catch((e) => {
+ console.error(e);
+ process.exit(1);
+});
diff --git a/packages/contracts/deploy/01_deploy_router.ts b/packages/contracts/deploy/01_deploy_router.ts
index f770173aa..a8ddc4d47 100644
--- a/packages/contracts/deploy/01_deploy_router.ts
+++ b/packages/contracts/deploy/01_deploy_router.ts
@@ -1,9 +1,22 @@
import { ethers } from "hardhat";
+
async function main() {
const FACTORY = process.env.UNIV2_FACTORY!;
const WNATIVE = process.env.WNATIVE!;
- const R = await (await ethers.getContractFactory("UniswapV2Router02")).deploy(FACTORY, WNATIVE);
- await R.waitForDeployment();
- console.log("Router:", await R.getAddress());
+
+ console.log("Deploying UniswapV2Router02...");
+ console.log(" factory:", FACTORY);
+ console.log(" WNATIVE:", WNATIVE);
+
+ const Router = await ethers.getContractFactory("UniswapV2Router02");
+ const router = await Router.deploy(FACTORY, WNATIVE);
+
+ await router.waitForDeployment();
+
+ console.log("UniswapV2Router02 deployed at:", await router.getAddress());
}
-main().catch(e=>{console.error(e);process.exit(1)});
+
+main().catch((e) => {
+ console.error(e);
+ process.exit(1);
+});
diff --git a/packages/contracts/deploy/02_check_router.ts b/packages/contracts/deploy/02_check_router.ts
new file mode 100644
index 000000000..5b1d47e21
--- /dev/null
+++ b/packages/contracts/deploy/02_check_router.ts
@@ -0,0 +1,20 @@
+import { ethers } from "hardhat";
+
+async function main() {
+ const routerAddr = "0x5123208Aa3C6A37615327a8c479a5e1654c0200E";
+
+ const Router = await ethers.getContractFactory("UniswapV2Router02");
+ const router = Router.attach(routerAddr);
+
+ const factory = await router.factory();
+ const wnative = await router.WETH(); // in UniswapV2, function name is WETH
+
+ console.log("Router:", routerAddr);
+ console.log(" factory:", factory);
+ console.log(" WNATIVE:", wnative);
+}
+
+main().catch((e) => {
+ console.error(e);
+ process.exit(1);
+});
diff --git a/packages/contracts/deploy/02_set_fee_to.ts b/packages/contracts/deploy/03_set_fee_to.ts
similarity index 100%
rename from packages/contracts/deploy/02_set_fee_to.ts
rename to packages/contracts/deploy/03_set_fee_to.ts
diff --git a/packages/contracts/hardhat.config.ts b/packages/contracts/hardhat.config.ts
index ed81d87f2..8dc1dbd54 100644
--- a/packages/contracts/hardhat.config.ts
+++ b/packages/contracts/hardhat.config.ts
@@ -24,6 +24,14 @@ const config: HardhatUserConfig = {
.split(",")
.map((s) => s.trim())
.filter(Boolean)
+ },
+ intuitionMainnet: {
+ url: process.env.INTUITION_MAINNET_RPC_URL || "",
+ chainId: Number(process.env.CHAIN_ID_MAINNET || 0),
+ accounts: (process.env.PRIVATE_KEY_MAINNET || "")
+ .split(",")
+ .map((s) => s.trim())
+ .filter(Boolean)
}
}
};
diff --git a/packages/sdk/src/addresses/index.ts b/packages/sdk/src/addresses/index.ts
index e9c4a12cf..97044e0ad 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}`;
@@ -10,14 +11,19 @@ export interface Addresses {
NATIVE_PLACEHOLDER: HexAddr;
TSWP: HexAddr;
WTTRUST: HexAddr;
+ WTRUST: HexAddr;
StakingRewardsFactory: HexAddr;
}
+export const INTUITION_TESTNET_CHAIN_ID = 13579;
+export const INTUITION_MAINNET_CHAIN_ID = 1155;
+
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 +31,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..490fafce8
--- /dev/null
+++ b/packages/sdk/src/addresses/intuition-mainnet.json
@@ -0,0 +1,10 @@
+{
+ "UniswapV2Factory": "0x83E9f4E539eb343F7F67d130a484c8a1b6555458",
+ "UniswapV2Router02": "0x5123208Aa3C6A37615327a8c479a5e1654c0200E",
+ "deployer": "0xAd34d98F6D041cb32289F3AF241556a7A627bA09",
+ "router": "0x5123208Aa3C6A37615327a8c479a5e1654c0200E",
+ "NATIVE_PLACEHOLDER": "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
+ "WTTRUST": "0x81cFb09cb44f7184Ad934C09F82000701A4bF672",
+ "TSWP": "0x0000000000000000000000000000000000000000",
+ "StakingRewardsFactory": "0x0000000000000000000000000000000000000000"
+}
diff --git a/packages/sdk/src/chains.ts b/packages/sdk/src/chains.ts
index bdc855657..9fb463c92 100644
--- a/packages/sdk/src/chains.ts
+++ b/packages/sdk/src/chains.ts
@@ -1,9 +1,9 @@
// packages/sdk/src/chain.ts
import { defineChain } from "viem";
-export const INTUITION = defineChain({
+export const intuitionTestnet = defineChain({
id: 13579,
- name: "Intuition Testnet",
+ name: "Testnet",
nativeCurrency: { name: "tTRUST", symbol: "tTRUST", decimals: 18 },
rpcUrls: {
default: { http: ["https://testnet.rpc.intuition.systems/http"] },
@@ -19,3 +19,35 @@ export const INTUITION = defineChain({
}
}
});
+
+
+export const intuitionMainnet = defineChain({
+ id: 1155,
+ name: "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
+ },
+ },
+ contracts: {
+ multicall3: {
+ address: "0x31E7C4ef16e1c3c149D2F0a62517d621bDa6D037",
+ blockCreated: 117543
+ }
+ }
+})
\ No newline at end of file