diff --git a/.env.local.example b/.env.local.example index 45f3da81..ab796d75 100644 --- a/.env.local.example +++ b/.env.local.example @@ -36,6 +36,7 @@ NEXT_PUBLIC_ALCHEMY_API_KEY= # Individual RPC URLs NEXT_PUBLIC_ETHEREUM_RPC= +NEXT_PUBLIC_OPTIMISM_RPC= NEXT_PUBLIC_BASE_RPC= NEXT_PUBLIC_POLYGON_RPC= NEXT_PUBLIC_UNICHAIN_RPC= diff --git a/src/config/appkit.ts b/src/config/appkit.ts index 132e58c1..382e551c 100644 --- a/src/config/appkit.ts +++ b/src/config/appkit.ts @@ -4,9 +4,20 @@ import { WagmiAdapter } from '@reown/appkit-adapter-wagmi'; import { createStorage, type Storage } from 'wagmi'; import localStorage from 'local-storage-fallback'; import { createAppKit } from '@reown/appkit/react'; -import { arbitrum, base, mainnet, polygon, type AppKitNetwork } from '@reown/appkit/networks'; -import { monad, unichain } from 'wagmi/chains'; -import { hyperEvm } from '@/utils/networks'; +import type { AppKitNetwork } from '@reown/appkit/networks'; +import { arbitrum, base, mainnet, monad, optimism, polygon, unichain } from 'wagmi/chains'; +import { SupportedNetworks, getDefaultRPC, hyperEvm } from '@/utils/networks'; + +type ChainWithRpcUrls = { + rpcUrls: { + default: { + http: readonly string[]; + }; + public?: { + http: readonly string[]; + }; + }; +}; // Get project ID from environment const projectId = process.env.NEXT_PUBLIC_REOWN_PROJECT_ID ?? ''; @@ -18,16 +29,42 @@ if (!projectId) { throw new Error('NEXT_PUBLIC_REOWN_PROJECT_ID is not set'); } -// Cast custom chains to AppKitNetwork for type compatibility -const customUnichain = unichain as unknown as AppKitNetwork; -const customMonad = monad as unknown as AppKitNetwork; -const customHyperEvm = hyperEvm as unknown as AppKitNetwork; +const withAppKitRpc = (chain: T, rpcUrl: string): AppKitNetwork => + ({ + ...chain, + rpcUrls: { + ...chain.rpcUrls, + default: { + ...chain.rpcUrls.default, + http: [rpcUrl], + }, + public: { + ...(chain.rpcUrls.public ?? chain.rpcUrls.default), + http: [rpcUrl], + }, + }, + }) as unknown as AppKitNetwork; + +const customMainnet = withAppKitRpc(mainnet, getDefaultRPC(SupportedNetworks.Mainnet)); +const customOptimism = withAppKitRpc(optimism, getDefaultRPC(SupportedNetworks.Optimism)); +const customBase = withAppKitRpc(base, getDefaultRPC(SupportedNetworks.Base)); +const customPolygon = withAppKitRpc(polygon, getDefaultRPC(SupportedNetworks.Polygon)); +const customArbitrum = withAppKitRpc(arbitrum, getDefaultRPC(SupportedNetworks.Arbitrum)); +const customUnichain = withAppKitRpc(unichain, getDefaultRPC(SupportedNetworks.Unichain)); +const customMonad = withAppKitRpc(monad, getDefaultRPC(SupportedNetworks.Monad)); +const customHyperEvm = withAppKitRpc(hyperEvm, getDefaultRPC(SupportedNetworks.HyperEVM)); // Define networks for AppKit (non-empty tuple type required) -export const networks = [mainnet, base, polygon, arbitrum, customUnichain, customHyperEvm, customMonad] as [ - AppKitNetwork, - ...AppKitNetwork[], -]; +export const networks = [ + customMainnet, + customOptimism, + customBase, + customPolygon, + customArbitrum, + customUnichain, + customHyperEvm, + customMonad, +] as [AppKitNetwork, ...AppKitNetwork[]]; // Metadata for the app const metadata = { diff --git a/src/config/dataSources.ts b/src/config/dataSources.ts index f45fb93b..c0f17488 100644 --- a/src/config/dataSources.ts +++ b/src/config/dataSources.ts @@ -6,6 +6,7 @@ import { SupportedNetworks } from '@/utils/networks'; export const supportsMorphoApi = (network: SupportedNetworks): boolean => { switch (network) { case SupportedNetworks.Mainnet: + case SupportedNetworks.Optimism: case SupportedNetworks.Base: case SupportedNetworks.Unichain: case SupportedNetworks.Polygon: diff --git a/src/constants/public-allocator.ts b/src/constants/public-allocator.ts index b127488c..41f7a6c9 100644 --- a/src/constants/public-allocator.ts +++ b/src/constants/public-allocator.ts @@ -2,6 +2,7 @@ import { SupportedNetworks } from '@/utils/networks'; export const PUBLIC_ALLOCATOR_ADDRESSES: Partial> = { [SupportedNetworks.Mainnet]: '0xfd32fA2ca22c76dD6E550706Ad913FC6CE91c75D', + [SupportedNetworks.Optimism]: '0x0d68a97324E602E02799CD83B42D337207B40658', [SupportedNetworks.Base]: '0xA090dD1a701408Df1d4d0B85b716c87565f90467', [SupportedNetworks.Polygon]: '0xfac15aff53ADd2ff80C2962127C434E8615Df0d3', [SupportedNetworks.Unichain]: '0xB0c9a107fA17c779B3378210A7a593e88938C7C9', diff --git a/src/data-sources/morpho-api/vaults.ts b/src/data-sources/morpho-api/vaults.ts index acde72de..9493e088 100644 --- a/src/data-sources/morpho-api/vaults.ts +++ b/src/data-sources/morpho-api/vaults.ts @@ -7,7 +7,7 @@ type VaultAddressByNetwork = { }; // Constants for Morpho vault fetching -const MORPHO_SUPPORTED_CHAIN_IDS = [1, 8453, 999, 137, 42_161, 130]; +const MORPHO_SUPPORTED_CHAIN_IDS = [1, 10, 8453, 999, 137, 42_161, 130]; const MAX_VAULTS_LIMIT = 500; // Type for vault from Morpho API diff --git a/src/imgs/chains/op.svg b/src/imgs/chains/op.svg new file mode 100644 index 00000000..7e0b5b68 --- /dev/null +++ b/src/imgs/chains/op.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/imgs/tokens/susdc.svg b/src/imgs/tokens/susdc.svg new file mode 100644 index 00000000..c91a76b8 --- /dev/null +++ b/src/imgs/tokens/susdc.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/src/store/createWagmiConfig.ts b/src/store/createWagmiConfig.ts index e80ab44c..b2948350 100644 --- a/src/store/createWagmiConfig.ts +++ b/src/store/createWagmiConfig.ts @@ -1,5 +1,5 @@ import { createConfig, http } from 'wagmi'; -import { arbitrum, base, mainnet, monad, polygon, unichain } from 'wagmi/chains'; +import { arbitrum, base, mainnet, monad, optimism, polygon, unichain } from 'wagmi/chains'; import type { CustomRpcUrls } from '@/stores/useCustomRpc'; import { SupportedNetworks, getDefaultRPC, hyperEvm } from '@/utils/networks'; @@ -10,6 +10,7 @@ import { SupportedNetworks, getDefaultRPC, hyperEvm } from '@/utils/networks'; export function createWagmiConfig(customRpcUrls: CustomRpcUrls = {}) { // Use custom RPC URLs if provided, otherwise fall back to defaults const rpcMainnet = customRpcUrls[SupportedNetworks.Mainnet] ?? getDefaultRPC(SupportedNetworks.Mainnet); + const rpcOptimism = customRpcUrls[SupportedNetworks.Optimism] ?? getDefaultRPC(SupportedNetworks.Optimism); const rpcBase = customRpcUrls[SupportedNetworks.Base] ?? getDefaultRPC(SupportedNetworks.Base); const rpcPolygon = customRpcUrls[SupportedNetworks.Polygon] ?? getDefaultRPC(SupportedNetworks.Polygon); const rpcUnichain = customRpcUrls[SupportedNetworks.Unichain] ?? getDefaultRPC(SupportedNetworks.Unichain); @@ -19,9 +20,10 @@ export function createWagmiConfig(customRpcUrls: CustomRpcUrls = {}) { return createConfig({ ssr: true, - chains: [mainnet, base, polygon, unichain, arbitrum, hyperEvm, monad], + chains: [mainnet, optimism, base, polygon, unichain, arbitrum, hyperEvm, monad], transports: { [mainnet.id]: http(rpcMainnet), + [optimism.id]: http(rpcOptimism), [base.id]: http(rpcBase), [polygon.id]: http(rpcPolygon), [unichain.id]: http(rpcUnichain), diff --git a/src/types/token.ts b/src/types/token.ts index bead2f8c..f2743a98 100644 --- a/src/types/token.ts +++ b/src/types/token.ts @@ -19,6 +19,7 @@ export type NetworkToken = { /** Canonical token addresses used for deterministic route checks. */ export const WETH_BY_CHAIN: Partial> = { [SupportedNetworks.Mainnet]: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', + [SupportedNetworks.Optimism]: '0x4200000000000000000000000000000000000006', [SupportedNetworks.Base]: '0x4200000000000000000000000000000000000006', [SupportedNetworks.Polygon]: '0x7ceb23fd6bc0add59e62ac25578270cff1b9f619', [SupportedNetworks.Unichain]: '0x4200000000000000000000000000000000000006', diff --git a/src/utils/external.ts b/src/utils/external.ts index 224e2923..159d8288 100644 --- a/src/utils/external.ts +++ b/src/utils/external.ts @@ -37,6 +37,8 @@ const getChainNameForMerkl = (chainId: number): string => { switch (chainId) { case SupportedNetworks.Mainnet: return 'ethereum'; + case SupportedNetworks.Optimism: + return 'optimism'; case SupportedNetworks.Base: return 'base'; case SupportedNetworks.Polygon: diff --git a/src/utils/morpho.ts b/src/utils/morpho.ts index bbab6b99..5f5eab9e 100644 --- a/src/utils/morpho.ts +++ b/src/utils/morpho.ts @@ -9,6 +9,8 @@ export const getMorphoAddress = (chain: SupportedNetworks) => { switch (chain) { case SupportedNetworks.Mainnet: return '0xbbbbbbbbbb9cc5e90e3b3af64bdaf62c37eeffcb'; + case SupportedNetworks.Optimism: + return '0xce95AfbB8EA029495c66020883F87aaE8864AF92'; case SupportedNetworks.Base: return '0xbbbbbbbbbb9cc5e90e3b3af64bdaf62c37eeffcb'; case SupportedNetworks.Polygon: @@ -30,6 +32,8 @@ export const getBundlerV2 = (chain: SupportedNetworks) => { switch (chain) { case SupportedNetworks.Mainnet: return '0x4095F064B8d3c3548A3bebfd0Bbfd04750E30077'; + case SupportedNetworks.Optimism: + return '0x5738366B9348f22607294007e75114922dF2a16A'; // ChainAgnosticBundlerV2 we deployed case SupportedNetworks.Base: // ChainAgnosticBundlerV2 return '0x23055618898e202386e6c13955a58D3C68200BFB'; @@ -52,6 +56,8 @@ export const getIRMTitle = (address: string) => { switch (address.toLowerCase()) { case '0x870ac11d48b15db9a138cf899d20f13f79ba00bc': return 'Adaptive Curve'; + case '0x8cd70a8f399428456b29546bc5dbe10ab6a06ef6': // on optimism + return 'Adaptive Curve'; case '0x46415998764c29ab2a25cbea6254146d50d22687': // on base return 'Adaptive Curve'; case '0xe675a2161d4a6e2de2eed70ac98eebf257fbf0b0': // on polygon @@ -90,6 +96,8 @@ export function getMorphoGenesisDate(chainId: number): Date { return new Date('2023-12-28T09:09:23.000Z'); case SupportedNetworks.Base: return new Date('2024-05-03T13:40:43.000Z'); + case SupportedNetworks.Optimism: + return new Date('2026-01-17T06:39:15.000Z'); case SupportedNetworks.Polygon: return new Date('2025-01-20T02:03:12.000Z'); case SupportedNetworks.Unichain: diff --git a/src/utils/networks.ts b/src/utils/networks.ts index 78eb35dd..46b2ca2c 100644 --- a/src/utils/networks.ts +++ b/src/utils/networks.ts @@ -1,5 +1,5 @@ import { type Address, type Chain, defineChain } from 'viem'; -import { arbitrum, base, mainnet, polygon, unichain, monad, hyperEvm as hyperEvmOld } from 'viem/chains'; +import { arbitrum, base, mainnet, monad, optimism, polygon, unichain, hyperEvm as hyperEvmOld } from 'viem/chains'; import { v2AgentsBase } from './monarch-agent'; import type { AgentMetadata } from './types'; @@ -29,6 +29,7 @@ const getRpcUrl = (specificRpcUrl: string | undefined, alchemySubdomain: string) export enum SupportedNetworks { Mainnet = 1, + Optimism = 10, Base = 8453, Polygon = 137, Unichain = 130, @@ -39,6 +40,7 @@ export enum SupportedNetworks { export const ALL_SUPPORTED_NETWORKS = [ SupportedNetworks.Mainnet, + SupportedNetworks.Optimism, SupportedNetworks.Base, SupportedNetworks.Polygon, SupportedNetworks.Unichain, @@ -97,6 +99,17 @@ export const networks: NetworkConfig[] = [ explorerUrl: 'https://etherscan.io', wrappedNativeToken: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', }, + { + network: SupportedNetworks.Optimism, + logo: require('../imgs/chains/op.svg') as string, + name: 'Optimism', + chain: optimism, + defaultRPC: getRpcUrl(process.env.NEXT_PUBLIC_OPTIMISM_RPC, 'opt-mainnet'), + blocktime: 2, + maxBlockDelay: 10, + explorerUrl: 'https://optimistic.etherscan.io/', + wrappedNativeToken: '0x4200000000000000000000000000000000000006', + }, { network: SupportedNetworks.Base, logo: require('../imgs/chains/base.webp') as string, diff --git a/src/utils/oracle.ts b/src/utils/oracle.ts index 2b0f188d..5fdfdb58 100644 --- a/src/utils/oracle.ts +++ b/src/utils/oracle.ts @@ -90,6 +90,7 @@ export function mapProviderToVendor(provider: OracleFeedProvider): PriceFeedVend export function getChainlinkFeedUrl(chainId: number, ens: string): string { const networkPaths: Partial> = { [SupportedNetworks.Mainnet]: 'ethereum/mainnet', + [SupportedNetworks.Optimism]: 'optimism/mainnet', [SupportedNetworks.Base]: 'base/base', [SupportedNetworks.Polygon]: 'polygon/mainnet', [SupportedNetworks.Arbitrum]: 'arbitrum/mainnet', diff --git a/src/utils/rpc.ts b/src/utils/rpc.ts index 0376f4de..7acd774e 100644 --- a/src/utils/rpc.ts +++ b/src/utils/rpc.ts @@ -1,5 +1,5 @@ import { createPublicClient, http, type PublicClient } from 'viem'; -import { arbitrum, base, mainnet, polygon, unichain, monad } from 'viem/chains'; +import { arbitrum, base, mainnet, monad, optimism, polygon, unichain } from 'viem/chains'; import { getDefaultRPC, getViemChain, SupportedNetworks, hyperEvm } from './networks'; // Default clients (cached) @@ -13,6 +13,10 @@ const initializeDefaultClients = () => { chain: mainnet, transport: http(getDefaultRPC(SupportedNetworks.Mainnet)), }), + [SupportedNetworks.Optimism]: createPublicClient({ + chain: optimism, + transport: http(getDefaultRPC(SupportedNetworks.Optimism)), + }) as PublicClient, [SupportedNetworks.Base]: createPublicClient({ chain: base, transport: http(getDefaultRPC(SupportedNetworks.Base)), diff --git a/src/utils/subgraph-urls.ts b/src/utils/subgraph-urls.ts index 73219379..4490ce1d 100644 --- a/src/utils/subgraph-urls.ts +++ b/src/utils/subgraph-urls.ts @@ -15,6 +15,10 @@ const mainnetSubgraphUrl = apiKey ? `https://gateway.thegraph.com/api/${apiKey}/subgraphs/id/8Lz789DP5VKLXumTMTgygjU2xtuzx8AhbaacgN5PYCAs` : undefined; +const optimismSubgraphUrl = apiKey + ? `https://gateway.thegraph.com/api/${apiKey}/subgraphs/id/5y8d3K3vVCR7r5YwANGCjupLc3hUge54XvhYMEq3Jmq1` + : undefined; + const polygonSubgraphUrl = apiKey ? `https://gateway.thegraph.com/api/${apiKey}/subgraphs/id/EhFokmwryNs7qbvostceRqVdjc3petuD13mmdUiMBw8Y` : undefined; @@ -32,6 +36,7 @@ const arbitrumSubgraph = apiKey export const SUBGRAPH_URLS: Partial> = { [SupportedNetworks.Base]: baseSubgraphUrl, [SupportedNetworks.Mainnet]: mainnetSubgraphUrl, + [SupportedNetworks.Optimism]: optimismSubgraphUrl, [SupportedNetworks.Polygon]: polygonSubgraphUrl, [SupportedNetworks.Unichain]: unichainSubgraphUrl, [SupportedNetworks.Arbitrum]: arbitrumSubgraph, diff --git a/src/utils/tokens.ts b/src/utils/tokens.ts index 713a124b..850da2b8 100644 --- a/src/utils/tokens.ts +++ b/src/utils/tokens.ts @@ -1,4 +1,4 @@ -import { type Chain, base, mainnet, polygon, unichain, arbitrum, monad } from 'viem/chains'; +import { type Chain, base, mainnet, polygon, unichain, arbitrum, optimism, monad } from 'viem/chains'; import { getWrappedNativeToken, hyperEvm } from './networks'; export type TokenSource = 'local' | 'external' | 'unknown'; @@ -56,6 +56,7 @@ const supportedTokens = [ { chain: mainnet, address: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48' }, { chain: base, address: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913' }, { chain: polygon, address: '0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359' }, + { chain: optimism, address: '0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85' }, { chain: unichain, address: '0x078d782b760474a361dda0af3839290b0ef57ad6', @@ -261,6 +262,10 @@ const supportedTokens = [ chain: unichain, address: '0x4200000000000000000000000000000000000006', }, + { + chain: optimism, + address: '0x4200000000000000000000000000000000000006', + }, { chain: arbitrum, address: '0x82af49447d8a07e3bd95bd0d56f35241523fbab1', @@ -310,6 +315,10 @@ const supportedTokens = [ chain: arbitrum, address: '0x5979D7b546E38E414F7E9822514be443A4800529', }, + { + chain: optimism, + address: '0x1F32b1c2345538c0c6f582fCB022739c4A194Ebb', + }, { chain: monad, address: '0x10Aeaf63194db8d453d4D85a06E5eFE1dd0b5417' }, { chain: unichain, @@ -362,6 +371,7 @@ const supportedTokens = [ networks: [ { chain: mainnet, address: '0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599' }, { chain: polygon, address: '0x1bfd67037b42cf73acf2047067bd4f2c47d9bfd6' }, + { chain: optimism, address: '0x68f180fcCe6836688e9084f035309E29Bf0A2095' }, { chain: unichain, address: '0x927B51f251480a681271180DA4de28D44EC4AfB8', @@ -607,6 +617,13 @@ const supportedTokens = [ networks: [{ chain: mainnet, address: '0xC5d6A7B61d18AfA11435a889557b068BB9f29930' }], peg: TokenPeg.USD, }, + { + symbol: 'sUSDC', + img: require('../imgs/tokens/susdc.svg') as string, + decimals: 18, + networks: [{ chain: optimism, address: '0xCF9326e24EBfFBEF22ce1050007A43A3c0B6DB55' }], + peg: TokenPeg.USD, + }, { symbol: 'pufETH', img: require('../imgs/tokens/pufETH.png') as string, diff --git a/src/utils/urls.ts b/src/utils/urls.ts index 252158c6..b839af62 100644 --- a/src/utils/urls.ts +++ b/src/utils/urls.ts @@ -7,18 +7,10 @@ export const URLS = { MORPHO_REWARDS_API: 'https://rewards.morpho.org/v1', } as const; -export const MONARCH_AGENT_URLS: Record = { +export const MONARCH_AGENT_URLS: Partial> = { [SupportedNetworks.Base]: 'https://api.studio.thegraph.com/query/110397/monarch-agent-base/version/latest', [SupportedNetworks.Polygon]: 'https://api.studio.thegraph.com/query/110397/monarch-agent-polygon/version/latest', -} as Record; +}; // Helper function to get URL by chainId, returns undefined if not supported -export const getMonarchAgentUrl = (chainId: number): string | undefined => { - if (chainId === SupportedNetworks.Base) { - return MONARCH_AGENT_URLS[SupportedNetworks.Base]; - } - if (chainId === SupportedNetworks.Polygon) { - return MONARCH_AGENT_URLS[SupportedNetworks.Polygon]; - } - return undefined; -}; +export const getMonarchAgentUrl = (chainId: number): string | undefined => MONARCH_AGENT_URLS[chainId as SupportedNetworks];