diff --git a/.env.example b/.env.example index 083c815..cea4113 100644 --- a/.env.example +++ b/.env.example @@ -1 +1,19 @@ -NODE_ENV=development \ No newline at end of file +VITE_ENV=development + +# --------------------------- +# Alchemy - Optional +# The Alchemy ID var is optional to connect to the Ethereum network. If not set the default rpc will be used. +# Create your account on https://alchemy.com and set your own ID value here. +VITE_ALCHEMY_ID="" + +# --------------------------- +# Infura - Optional +# The Infura ID var is optional to connect to the Ethereum network. If not set the default rpc will be used. +# Create your account on https://infura.io and set your own ID value here. +VITE_INFURA_ID="" + +# --------------------------- +# Wallet Connect Project ID - REQUIRED +# The Wallet Connect Project ID var is required to connect to the Ethereum network. +# Create your account on https://cloud.walletconnect.org/app and set your own ID value here. +VITE_WALLET_CONNECT_PROJECT_ID="" \ No newline at end of file diff --git a/package.json b/package.json index 68f774b..591f4c3 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "dependencies": { "@allo-team/allo-v2-sdk": "1.1.1", "@gitcoin/gitcoin-chain-data": "^1.0.51", - "@gitcoin/ui": "0.0.0-beta.10", + "@gitcoin/ui": "0.0.0-beta.23", "@rainbow-me/rainbowkit": "^2.2.0", "@tanstack/react-query": "^5.61.5", "dotenv": "^16.4.5", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f6d3436..b2783ef 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -15,8 +15,8 @@ importers: specifier: ^1.0.51 version: 1.0.51(bufferutil@4.0.8)(typescript@5.6.3)(utf-8-validate@5.0.10)(zod@3.23.8) '@gitcoin/ui': - specifier: 0.0.0-beta.10 - version: 0.0.0-beta.10(@types/react-dom@18.3.1)(@types/react@18.3.12)(bufferutil@4.0.8)(graphql@16.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rollup@4.28.0)(storybook@8.4.6(bufferutil@4.0.8)(prettier@3.4.1)(utf-8-validate@5.0.10))(tailwindcss@3.4.15)(typescript@5.6.3)(utf-8-validate@5.0.10)(vite@6.0.1(@types/node@20.17.9)(jiti@2.4.1)(yaml@2.6.1)) + specifier: 0.0.0-beta.23 + version: 0.0.0-beta.23(@types/react-dom@18.3.1)(@types/react@18.3.12)(bufferutil@4.0.8)(graphql@16.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rollup@4.28.0)(storybook@8.4.6(bufferutil@4.0.8)(prettier@3.4.1)(utf-8-validate@5.0.10))(tailwindcss@3.4.15)(typescript@5.6.3)(utf-8-validate@5.0.10)(vite@6.0.1(@types/node@20.17.9)(jiti@2.4.1)(yaml@2.6.1)) '@rainbow-me/rainbowkit': specifier: ^2.2.0 version: 2.2.0(@tanstack/react-query@5.62.0(react@18.3.1))(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(viem@2.21.53(bufferutil@4.0.8)(typescript@5.6.3)(utf-8-validate@5.0.10)(zod@3.23.8))(wagmi@2.13.2(@tanstack/query-core@5.62.0)(@tanstack/react-query@5.62.0(react@18.3.1))(@types/react@18.3.12)(bufferutil@4.0.8)(react@18.3.1)(typescript@5.6.3)(utf-8-validate@5.0.10)(viem@2.21.53(bufferutil@4.0.8)(typescript@5.6.3)(utf-8-validate@5.0.10)(zod@3.23.8))(zod@3.23.8)) @@ -506,8 +506,8 @@ packages: peerDependencies: typescript: ^5.0.0 - '@gitcoin/ui@0.0.0-beta.10': - resolution: {integrity: sha512-N3c6330+ZV3WbW36aKQOz+hKp6pA+dEavbHHS8gGupukPifCdjIRzS6RwwAycGE84J0YtSIbCKBOqm3iExHMRA==} + '@gitcoin/ui@0.0.0-beta.23': + resolution: {integrity: sha512-b+1jxTmrqAXfB6gXnkLbWGIGqaq0MEhnLRcZUGJwoGV+UM/pnkrQjbRPMAsEezf1HHBqk4SWbOMN7YWbBeH/3w==} peerDependencies: react: 18.3.1 react-dom: 18.3.1 @@ -6173,7 +6173,7 @@ snapshots: - utf-8-validate - zod - '@gitcoin/ui@0.0.0-beta.10(@types/react-dom@18.3.1)(@types/react@18.3.12)(bufferutil@4.0.8)(graphql@16.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rollup@4.28.0)(storybook@8.4.6(bufferutil@4.0.8)(prettier@3.4.1)(utf-8-validate@5.0.10))(tailwindcss@3.4.15)(typescript@5.6.3)(utf-8-validate@5.0.10)(vite@6.0.1(@types/node@20.17.9)(jiti@2.4.1)(yaml@2.6.1))': + '@gitcoin/ui@0.0.0-beta.23(@types/react-dom@18.3.1)(@types/react@18.3.12)(bufferutil@4.0.8)(graphql@16.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rollup@4.28.0)(storybook@8.4.6(bufferutil@4.0.8)(prettier@3.4.1)(utf-8-validate@5.0.10))(tailwindcss@3.4.15)(typescript@5.6.3)(utf-8-validate@5.0.10)(vite@6.0.1(@types/node@20.17.9)(jiti@2.4.1)(yaml@2.6.1))': dependencies: '@gitcoin/gitcoin-chain-data': 1.0.51(bufferutil@4.0.8)(typescript@5.6.3)(utf-8-validate@5.0.10)(zod@3.23.8) '@heroicons/react': 1.0.6(react@18.3.1) @@ -6215,6 +6215,7 @@ snapshots: graphql-request: 7.1.2(graphql@16.9.0) idb: 8.0.1 input-otp: 1.4.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + lodash: 4.17.21 moment: 2.30.1 moment-timezone: 0.5.46 next-themes: 0.3.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) diff --git a/src/providers/Providers.tsx b/src/providers/Providers.tsx index af5d481..5f11374 100644 --- a/src/providers/Providers.tsx +++ b/src/providers/Providers.tsx @@ -1,7 +1,7 @@ "use client"; import { WagmiProvider } from "wagmi"; -import { config } from "./WagmiConfig"; +import { wagmiConfig } from "./WagmiConfig"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { RainbowKitProvider } from "@rainbow-me/rainbowkit"; import "@rainbow-me/rainbowkit/styles.css"; @@ -10,7 +10,7 @@ const queryClient = new QueryClient(); export function Providers({ children }: { children: React.ReactNode }) { return ( - + {children} diff --git a/src/providers/WagmiConfig.tsx b/src/providers/WagmiConfig.tsx index 754a9fb..b6e4c64 100644 --- a/src/providers/WagmiConfig.tsx +++ b/src/providers/WagmiConfig.tsx @@ -1,74 +1,19 @@ -import { getDefaultConfig } from "@rainbow-me/rainbowkit"; -import { http } from "wagmi"; +import { createClient, fallback, http } from "viem"; +import { createConfig } from "wagmi"; +import { targetNetworks } from "./chains"; +import { wagmiConnectors } from "./wagmiConnectors"; -import { - mainnet, - sepolia, - optimism, - optimismSepolia, - celo, - celoAlfajores, - polygon, - arbitrum, - arbitrumSepolia, - base, - avalancheFuji, - avalanche, - scroll, - fantom, - fantomTestnet, - zksync, - zksyncSepoliaTestnet, - filecoin, - sei, - seiDevnet, - lukso, - luksoTestnet, - metis, - gnosis, -} from "wagmi/chains"; - -const PRODUCTION_CHAINS = [ - mainnet, - optimism, - celo, - polygon, - arbitrum, - avalanche, - scroll, - fantom, - zksync, - filecoin, - sei, - lukso, - metis, - gnosis, - base, -]; - -const TEST_CHAINS = [ - sepolia, - optimismSepolia, - celoAlfajores, - arbitrumSepolia, - avalancheFuji, - fantomTestnet, - zksyncSepoliaTestnet, - seiDevnet, - luksoTestnet, -]; - -const ALL_CHAINS = [...PRODUCTION_CHAINS, ...TEST_CHAINS]; - -const NODE_ENV = process.env.NODE_ENV || "development"; +export const wagmiConfig = createConfig({ + chains: targetNetworks, + connectors: wagmiConnectors, + ssr: true, + client({ chain }) { + const rpcFallbacks = [http()]; -export const config = getDefaultConfig({ - appName: "Checker - Gitcoin", - projectId: "YOUR_PROJECT_ID", - chains: NODE_ENV == "development" ? (ALL_CHAINS as any) : (PRODUCTION_CHAINS as any), - transports: { - [mainnet.id]: http(), - [sepolia.id]: http(), + return createClient({ + chain, + transport: fallback(rpcFallbacks), + pollingInterval: 30000, + }); }, - ssr: true, }); diff --git a/src/providers/chains.ts b/src/providers/chains.ts new file mode 100644 index 0000000..1957c72 --- /dev/null +++ b/src/providers/chains.ts @@ -0,0 +1,134 @@ +import { getChains, TChain } from "@gitcoin/gitcoin-chain-data"; +import { Chain } from "@rainbow-me/rainbowkit"; +import { zeroAddress } from "viem"; +import * as chains from "viem/chains"; + +const chainData = getChains(); + +const { alchemyId, infuraId, availableNetworks, isDevelopment } = { + alchemyId: import.meta.env.VITE_ALCHEMY_ID, + infuraId: import.meta.env.VITE_INFURA_ID, + availableNetworks: chainData.map((chain) => chain.id), + isDevelopment: import.meta.env.VITE_ENV === "development", +}; + +interface RpcConfig { + [chainId: number]: string | undefined; +} + +// Separate RPC configuration +const rpcUrls: RpcConfig = { + 1: "https://eth-mainnet.g.alchemy.com/v2/", // ethereum + 10: "https://opt-mainnet.g.alchemy.com/v2/", // optimism + 100: "https://gnosis-mainnet.g.alchemy.com/v2/", // gnosis + 42: "https://rpc.mainnet.lukso.network", // lukso + 137: "https://polygon-mainnet.g.alchemy.com/v2/", // polygon + // 250: "", // fantom + 300: "https://zksync-sepolia.g.alchemy.com/v2/", // zksync sepolia + 324: "https://zksync-mainnet.g.alchemy.com/v2/", // zksync mainnet + // 4201: "", // lukso test + 1088: "https://metis-mainnet.g.alchemy.com/v2/", // metis + 8453: "https://base-mainnet.g.alchemy.com/v2/", // base + 42161: "https://arb-mainnet.g.alchemy.com/v2/", // arbitrum + 42220: "https://celo-mainnet.infura.io/v3/", // celo + 43113: "https://avalanche-fuji.infura.io/v3/", // fuji + 43114: "https://avalanche-mainnet.infura.io/v3/", // avax + 44787: "https://celo-alfajores.infura.io/v3/", // alfajores + // 534351: "", // scroll sepolia + // 534352: "", // scroll mainnet + // 1329: "", // sei + // 713715: "", // sei devnet + 11155111: "https://eth-sepolia.g.alchemy.com/v2/", +}; +// Configuration types +interface ChainConfig { + availableNetworks: number[]; + isDevelopment: boolean; +} + +// RPC URL helper functions +const appendProviderKey = (url: string, provider: "alchemy" | "infura"): string => { + const envKey = provider === "alchemy" ? alchemyId : infuraId; + + return envKey ? url + envKey : url; +}; + +export const getRpcUrl = (chain: TChain): string => { + const baseUrl = rpcUrls[chain.id] ?? chain.rpc; + + if (baseUrl.includes("alchemy")) { + return appendProviderKey(baseUrl, "alchemy"); + } + if (baseUrl.includes("infura")) { + return appendProviderKey(baseUrl, "infura"); + } + + return baseUrl; +}; + +export function stringToBlobUrl(data: string): string { + const blob = new Blob([data], { type: "image/svg+xml" }); + const url = URL.createObjectURL(blob); + return url; +} + +export const parseRainbowChain = (chain: TChain): Chain => { + const nativeToken = chain.tokens.find((token) => token.address === zeroAddress); + + const rpc = getRpcUrl(chain); + + // Map the TChain to @rainbow-me/rainbowkit/Chain + const mappedChain: Chain = { + id: chain.id, + name: chain.prettyName, + iconUrl: stringToBlobUrl(chain.icon), + iconBackground: "rgba(255, 255, 255, 0)", + nativeCurrency: { + name: nativeToken?.code as string, + symbol: nativeToken?.code as string, + decimals: nativeToken?.decimals as number, + }, + rpcUrls: { + default: { + http: [rpc], + webSocket: undefined, + }, + public: { + http: [chain.rpc], + webSocket: undefined, + }, + }, + contracts: { + ensUniversalResolver: { + address: chain.contracts.ensUniversalResolver ?? zeroAddress, + }, + }, + } as const satisfies Chain; + return mappedChain; +}; + +const filterChainsByEnvironment = (isDevelopment: boolean): TChain[] => { + return isDevelopment ? chainData : chainData.filter((chain) => chain.type === "mainnet"); +}; + +const filterChainsByAvailability = (chains: TChain[], availableNetworks: number[]): TChain[] => { + return chains.filter((chain) => availableNetworks.includes(chain.id)); +}; + +export const getTargetNetworks = (config: ChainConfig): [Chain, ...Chain[]] => { + const environmentChains = filterChainsByEnvironment(config.isDevelopment); + const availableChains = filterChainsByAvailability(environmentChains, config.availableNetworks); + + const parsedChains = availableChains.map(parseRainbowChain) as [Chain, ...Chain[]]; + + return parsedChains.length > 0 ? parsedChains : ([chains.mainnet] as [Chain, ...Chain[]]); +}; + +// Export chain data +export const allNetworks = chainData; +export const mainnetNetworks = chainData.filter((chain) => chain.type === "mainnet"); + +export const targetNetworks = getTargetNetworks({ + availableNetworks, + isDevelopment, +}); diff --git a/src/providers/wagmiConnectors.ts b/src/providers/wagmiConnectors.ts new file mode 100644 index 0000000..1c03413 --- /dev/null +++ b/src/providers/wagmiConnectors.ts @@ -0,0 +1,35 @@ +import { connectorsForWallets } from "@rainbow-me/rainbowkit"; +import { + coinbaseWallet, + ledgerWallet, + metaMaskWallet, + rainbowWallet, + safeWallet, + walletConnectWallet, +} from "@rainbow-me/rainbowkit/wallets"; + +const wallets = [ + metaMaskWallet, + walletConnectWallet, + ledgerWallet, + coinbaseWallet, + rainbowWallet, + safeWallet, +]; + +/** + * wagmi connectors for the wagmi context + */ +export const wagmiConnectors = connectorsForWallets( + [ + { + groupName: "Supported Wallets", + wallets, + }, + ], + + { + appName: "Gitcoin: Checker app", + projectId: import.meta.env.VITE_WALLET_CONNECT_PROJECT_ID ?? "42a4d689485d5a324116e53ed077d907", + }, +);