Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .env.local.example
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ NEXT_PUBLIC_BASE_RPC=
NEXT_PUBLIC_POLYGON_RPC=
NEXT_PUBLIC_UNICHAIN_RPC=
NEXT_PUBLIC_ARBITRUM_RPC=
NEXT_PUBLIC_ETHERLINK_RPC=
NEXT_PUBLIC_HYPEREVM_RPC=
NEXT_PUBLIC_MONAD_RPC=
# Set to "false" locally to skip RPC historical-rate fallback when Morpho API rolling rates fail.
Expand Down
3 changes: 3 additions & 0 deletions src/config/appkit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { createStorage, type Storage } from 'wagmi';
import localStorage from 'local-storage-fallback';
import { createAppKit } from '@reown/appkit/react';
import type { AppKitNetwork } from '@reown/appkit/networks';
import { etherlink } from 'viem/chains';
import { arbitrum, base, mainnet, monad, optimism, polygon, unichain } from 'wagmi/chains';
import { SupportedNetworks, getDefaultRPC, hyperEvm } from '@/utils/networks';

Expand Down Expand Up @@ -51,6 +52,7 @@ 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 customEtherlink = withAppKitRpc(etherlink, getDefaultRPC(SupportedNetworks.Etherlink));
const customMonad = withAppKitRpc(monad, getDefaultRPC(SupportedNetworks.Monad));
const customHyperEvm = withAppKitRpc(hyperEvm, getDefaultRPC(SupportedNetworks.HyperEVM));

Expand All @@ -62,6 +64,7 @@ export const networks = [
customPolygon,
customArbitrum,
customUnichain,
customEtherlink,
customHyperEvm,
customMonad,
] as [AppKitNetwork, ...AppKitNetwork[]];
Expand Down
1 change: 1 addition & 0 deletions src/constants/public-allocator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export const PUBLIC_ALLOCATOR_ADDRESSES: Partial<Record<SupportedNetworks, `0x${
[SupportedNetworks.Polygon]: '0xfac15aff53ADd2ff80C2962127C434E8615Df0d3',
[SupportedNetworks.Unichain]: '0xB0c9a107fA17c779B3378210A7a593e88938C7C9',
[SupportedNetworks.Arbitrum]: '0x769583Af5e9D03589F159EbEC31Cc2c23E8C355E',
[SupportedNetworks.Etherlink]: '0x8b8B1bd41d36c06253203CD21463994aB752c1e6',
[SupportedNetworks.HyperEVM]: '0x517505be22D9068687334e69ae7a02fC77edf4Fc',
[SupportedNetworks.Monad]: '0xfd70575B732F9482F4197FE1075492e114E97302',
};
1 change: 1 addition & 0 deletions src/features/admin-v2/components/chain-volume-chart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ const CHAIN_COLOR_INDEX: Record<number, number> = {
137: 3, // Polygon
130: 4, // Unichain
42161: 6, // Arbitrum
42793: 7, // Etherlink
999: 5, // HyperEVM
143: 2, // Monad
};
Expand Down
30 changes: 16 additions & 14 deletions src/features/autovault/components/vault-identity.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { ExternalLinkIcon } from '@radix-ui/react-icons';
import { TokenIcon } from '@/components/shared/token-icon';
import { TooltipContent } from '@/components/shared/tooltip-content';
import type { VaultCurator } from '@/constants/vaults/known_vaults';
import { getVaultURL } from '@/utils/external';
import { getVaultURL, supportsMorphoAppLinks } from '@/utils/external';
import { VaultIcon } from './vault-icon';

type VaultIdentityVariant = 'chip' | 'inline' | 'icon';
Expand Down Expand Up @@ -44,6 +44,7 @@ export function VaultIdentity({
showAddressInTooltip = true,
}: VaultIdentityProps) {
const vaultHref = useMemo(() => getVaultURL(address, chainId), [address, chainId]);
const canLinkToMorpho = useMemo(() => supportsMorphoAppLinks(chainId), [chainId]);
const formattedAddress = `${address.slice(0, 6)}...${address.slice(-4)}`;
const displayName = vaultName ?? formattedAddress;
const curatorLabel = curator === 'unknown' ? 'Curator unknown' : `Curated by ${curator}`;
Expand Down Expand Up @@ -92,19 +93,20 @@ export function VaultIdentity({
);
})();

const interactiveContent = showLink ? (
<Link
href={vaultHref}
target="_blank"
rel="noopener noreferrer"
className="no-underline"
onClick={(e) => e.stopPropagation()}
>
{baseContent}
</Link>
) : (
baseContent
);
const interactiveContent =
showLink && canLinkToMorpho ? (
<Link
href={vaultHref}
target="_blank"
rel="noopener noreferrer"
className="no-underline"
onClick={(e) => e.stopPropagation()}
>
{baseContent}
</Link>
) : (
baseContent
);

if (!showTooltip) {
return interactiveContent;
Expand Down
16 changes: 9 additions & 7 deletions src/features/market-detail/components/market-header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ import { convertApyToApr } from '@/utils/rateMath';
import { formatReadable } from '@/utils/balance';
import { getIRMTitle } from '@/utils/morpho';
import { getNetworkImg, getNetworkName, type SupportedNetworks } from '@/utils/networks';
import { getMarketURL } from '@/utils/external';
import { getMarketURL, supportsMorphoAppLinks } from '@/utils/external';
import type { Market, MarketPosition, WarningWithDetail } from '@/utils/types';
import { WarningCategory } from '@/utils/types';
import { getRiskLevel, countWarningsByLevel, type RiskLevel } from '@/utils/warnings';
Expand Down Expand Up @@ -630,12 +630,14 @@ export function MarketHeader({
Accrue Interest
</DropdownMenuItem>
)}
<DropdownMenuItem
onClick={() => window.open(getMarketURL(resolvedMarketId, network), '_blank')}
startContent={<FiExternalLink className="h-4 w-4" />}
>
View on Morpho
</DropdownMenuItem>
{supportsMorphoAppLinks(network) && (
<DropdownMenuItem
onClick={() => window.open(getMarketURL(resolvedMarketId, network), '_blank')}
startContent={<FiExternalLink className="h-4 w-4" />}
>
View on Morpho
</DropdownMenuItem>
)}
</DropdownMenuContent>
</DropdownMenu>
</div>
Expand Down
7 changes: 7 additions & 0 deletions src/imgs/chains/etherlink.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/imgs/tokens/mbasis.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
20 changes: 20 additions & 0 deletions src/imgs/tokens/mmev.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/imgs/tokens/mtbill.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/imgs/tokens/xu3o8.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 4 additions & 1 deletion src/store/createWagmiConfig.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { createConfig, http } from 'wagmi';
import { etherlink } from 'viem/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';
Expand All @@ -15,19 +16,21 @@ export function createWagmiConfig(customRpcUrls: CustomRpcUrls = {}) {
const rpcPolygon = customRpcUrls[SupportedNetworks.Polygon] ?? getDefaultRPC(SupportedNetworks.Polygon);
const rpcUnichain = customRpcUrls[SupportedNetworks.Unichain] ?? getDefaultRPC(SupportedNetworks.Unichain);
const rpcArbitrum = customRpcUrls[SupportedNetworks.Arbitrum] ?? getDefaultRPC(SupportedNetworks.Arbitrum);
const rpcEtherlink = customRpcUrls[SupportedNetworks.Etherlink] ?? getDefaultRPC(SupportedNetworks.Etherlink);
const rpcHyperEVM = customRpcUrls[SupportedNetworks.HyperEVM] ?? getDefaultRPC(SupportedNetworks.HyperEVM);
const rpcMonad = customRpcUrls[SupportedNetworks.Monad] ?? getDefaultRPC(SupportedNetworks.Monad);

return createConfig({
ssr: true,
chains: [mainnet, optimism, base, polygon, unichain, arbitrum, hyperEvm, monad],
chains: [mainnet, optimism, base, polygon, unichain, arbitrum, etherlink, hyperEvm, monad],
transports: {
[mainnet.id]: http(rpcMainnet),
[optimism.id]: http(rpcOptimism),
[base.id]: http(rpcBase),
[polygon.id]: http(rpcPolygon),
[unichain.id]: http(rpcUnichain),
[arbitrum.id]: http(rpcArbitrum),
[etherlink.id]: http(rpcEtherlink),
[hyperEvm.id]: http(rpcHyperEVM),
[monad.id]: http(rpcMonad),
},
Expand Down
1 change: 1 addition & 0 deletions src/types/token.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export const WETH_BY_CHAIN: Partial<Record<SupportedNetworks, Address>> = {
[SupportedNetworks.Polygon]: '0x7ceb23fd6bc0add59e62ac25578270cff1b9f619',
[SupportedNetworks.Unichain]: '0x4200000000000000000000000000000000000006',
[SupportedNetworks.Arbitrum]: '0x82af49447d8a07e3bd95bd0d56f35241523fbab1',
[SupportedNetworks.Etherlink]: '0xfc24f770F94edBca6D6f885E12d4317320BcB401',
[SupportedNetworks.Monad]: '0xEE8c0E9f1BFFb4Eb878d8f15f368A02a35481242',
};

Expand Down
11 changes: 10 additions & 1 deletion src/utils/external.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,32 @@
import { getNetworkName, SupportedNetworks, getExplorerUrl } from './networks';

const getMorphoNetworkSlug = (chainId: number): string | undefined => {
export const getMorphoNetworkSlug = (chainId: number): string | undefined => {
const network = getNetworkName(chainId)?.toLowerCase();
if (chainId === SupportedNetworks.HyperEVM) {
return 'hyperevm';
}
if (chainId === SupportedNetworks.Mainnet) {
return 'ethereum';
}
if (chainId === SupportedNetworks.Etherlink) {
return undefined;
}
return network;
};

export const supportsMorphoAppLinks = (chainId: number): boolean => {
return getMorphoNetworkSlug(chainId) !== undefined;
};

export const getMarketURL = (id: string, chainId: number): string => {
const network = getMorphoNetworkSlug(chainId);
if (!network) return 'https://app.morpho.org';
return `https://app.morpho.org/${network}/market/${id}`;
};

export const getVaultURL = (address: string, chainId: number): string => {
const network = getMorphoNetworkSlug(chainId);
if (!network) return 'https://app.morpho.org';
return `https://app.morpho.org/${network}/vault/${address}`;
};

Expand Down
8 changes: 8 additions & 0 deletions src/utils/morpho.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ export const getMorphoAddress = (chain: SupportedNetworks) => {
return '0x8f5ae9cddb9f68de460c77730b018ae7e04a140a';
case SupportedNetworks.Arbitrum:
return '0x6c247b1F6182318877311737BaC0844bAa518F5e';
case SupportedNetworks.Etherlink:
return '0xbCE7364E63C3B13C73E9977a83c9704E2aCa876e';
case SupportedNetworks.HyperEVM:
return '0x68e37dE8d93d3496ae143F2E900490f6280C57cD';
case SupportedNetworks.Monad:
Expand All @@ -43,6 +45,8 @@ export const getBundlerV2 = (chain: SupportedNetworks) => {
return '0x5738366B9348f22607294007e75114922dF2a16A'; // ChainAgnosticBundlerV2 we deployed
case SupportedNetworks.Arbitrum:
return '0x5738366B9348f22607294007e75114922dF2a16A'; // ChainAgnosticBundlerV2 we deployed
case SupportedNetworks.Etherlink:
return '0x5738366B9348f22607294007e75114922dF2a16A';
case SupportedNetworks.HyperEVM:
return '0x5738366B9348f22607294007e75114922dF2a16A'; // ChainAgnosticBundlerV2 we deployed
case SupportedNetworks.Monad:
Expand All @@ -66,6 +70,8 @@ export const getIRMTitle = (address: string) => {
return 'Adaptive Curve';
case '0x66f30587fb8d4206918deb78eca7d5ebbafd06da': // on arbitrum
return 'Adaptive Curve';
case '0xc1523be776e66ba07b609b1914d0925278f21fe5': // on etherlink
return 'Adaptive Curve';
case '0xd4a426f010986dcad727e8dd6eed44ca4a9b7483': // on hyperevm
return 'Adaptive Curve';
case '0x09475a3d6ea8c314c592b1a3799bde044e2f400f': // on monad
Expand Down Expand Up @@ -104,6 +110,8 @@ export function getMorphoGenesisDate(chainId: number): Date {
return new Date('2025-02-18T02:03:6.000Z');
case SupportedNetworks.Arbitrum:
return new Date('2025-01-17T06:04:51.000Z');
case SupportedNetworks.Etherlink:
return new Date('2025-07-14T20:41:53.000Z');
case SupportedNetworks.HyperEVM:
return new Date('2025-04-03T04:52:00.000Z');
case SupportedNetworks.Monad:
Expand Down
30 changes: 27 additions & 3 deletions src/utils/networks.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
import { type Address, type Chain, defineChain } from 'viem';
import { arbitrum, base, mainnet, monad, optimism, polygon, unichain, hyperEvm as hyperEvmOld } from 'viem/chains';
import {
arbitrum,
base,
etherlink as etherlinkChain,
mainnet,
monad,
optimism,
polygon,
unichain,
hyperEvm as hyperEvmOld,
} from 'viem/chains';
import { v2AgentsBase } from './monarch-agent';
import type { AgentMetadata } from './types';

Expand All @@ -13,10 +23,10 @@ const _apiKey = process.env.NEXT_PUBLIC_THEGRAPH_API_KEY;
* - If NEXT_PUBLIC_RPC_PRIORITY === 'ALCHEMY': Use Alchemy first, fall back to specific RPC
* - Otherwise (default): Use specific network RPC first, fall back to Alchemy
*/
const getRpcUrl = (specificRpcUrl: string | undefined, alchemySubdomain: string): string => {
const getRpcUrl = (specificRpcUrl: string | undefined, alchemySubdomain?: string): string => {
// Sanitize empty strings to undefined for correct fallback behavior
const targetRpc = specificRpcUrl || undefined;
const alchemyUrl = alchemyKey ? `https://${alchemySubdomain}.g.alchemy.com/v2/${alchemyKey}` : undefined;
const alchemyUrl = alchemyKey && alchemySubdomain ? `https://${alchemySubdomain}.g.alchemy.com/v2/${alchemyKey}` : undefined;

if (rpcPriority === 'ALCHEMY') {
// Prioritize Alchemy when explicitly set
Expand All @@ -34,6 +44,7 @@ export enum SupportedNetworks {
Polygon = 137,
Unichain = 130,
Arbitrum = 42_161,
Etherlink = 42_793,
HyperEVM = 999,
Monad = 143,
}
Expand All @@ -45,6 +56,7 @@ export const ALL_SUPPORTED_NETWORKS = [
SupportedNetworks.Polygon,
SupportedNetworks.Unichain,
SupportedNetworks.Arbitrum,
SupportedNetworks.Etherlink,
SupportedNetworks.HyperEVM,
SupportedNetworks.Monad,
];
Expand Down Expand Up @@ -161,6 +173,18 @@ export const networks: NetworkConfig[] = [
explorerUrl: 'https://arbiscan.io',
wrappedNativeToken: '0x82af49447d8a07e3bd95bd0d56f35241523fbab1',
},
{
network: SupportedNetworks.Etherlink,
chain: etherlinkChain,
logo: require('../imgs/chains/etherlink.svg') as string,
name: 'Etherlink',
defaultRPC: getRpcUrl(process.env.NEXT_PUBLIC_ETHERLINK_RPC),
blocktime: 4.83,
maxBlockDelay: 10,
explorerUrl: 'https://explorer.etherlink.com',
nativeTokenSymbol: 'XTZ',
wrappedNativeToken: '0xc9B53AB2679f573e480d01e0f49e2B5CFB7a3EAb',
},
Comment on lines +176 to +187
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify default Etherlink public RPC is functional
curl -s -X POST "https://node.mainnet.etherlink.com" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","method":"eth_chainId","params":[],"id":1}' | jq .

Repository: antoncoding/monarch

Length of output: 118


🏁 Script executed:

# Check getRpcUrl implementation and alchemySubdomain handling
rg "function getRpcUrl|const getRpcUrl" -A 15 src/

Repository: antoncoding/monarch

Length of output: 1023


🏁 Script executed:

# Search for http() transport usage and how empty URLs are handled
rg "http\(" src/utils/rpc.ts -A 3 -B 3

Repository: antoncoding/monarch

Length of output: 1723


🏁 Script executed:

# Look for env var documentation and examples
find . -name ".env*" -o -name "README*" -o -name "docs" -type f | head -20

Repository: antoncoding/monarch

Length of output: 171


🏁 Script executed:

# Check if there are any error handlers or validation for missing RPC URLs
rg "NEXT_PUBLIC.*RPC|getRpcUrl" -B 2 -A 2 src/

Repository: antoncoding/monarch

Length of output: 4177


🏁 Script executed:

# Check .env.local.example for Etherlink RPC documentation
cat -n .env.local.example | grep -i etherlink

Repository: antoncoding/monarch

Length of output: 96


🏁 Script executed:

# Search for error handling or validation around http() or createPublicClient
rg "createPublicClient|http\(" src/utils/rpc.ts -B 3 -A 5 | head -50

Repository: antoncoding/monarch

Length of output: 2083


🏁 Script executed:

# Check if there's any error handling for empty or invalid RPC URLs
rg "RPC|rpcUrl" src/utils/rpc.ts -B 2 -A 2

Repository: antoncoding/monarch

Length of output: 2341


🏁 Script executed:

# Check if Alchemy supports Etherlink subdomain
rg "etherlink" -i src/utils/networks.ts | grep -i alchemy

Repository: antoncoding/monarch

Length of output: 45


🏁 Script executed:

# Verify viem's http() behavior with empty string - check if there's any validation or error handling in documentation or code comments
rg "http\(" -B 5 -A 5 src/ | grep -i "empty\|url\|validate\|error" | head -20

Repository: antoncoding/monarch

Length of output: 428


🏁 Script executed:

# Check getDefaultRPC function to see if it's the same as getRpcUrl
rg "function getDefaultRPC|const getDefaultRPC|export.*getDefaultRPC" -A 10 src/utils/networks.ts

Repository: antoncoding/monarch

Length of output: 436


🏁 Script executed:

# Check if there's initialization validation or error handling that would catch empty RPC URLs
rg "initializeDefaultClients|getClient" src/utils/rpc.ts -B 2 -A 10

Repository: antoncoding/monarch

Length of output: 940


🏁 Script executed:

# Look for any error boundaries or RPC validation in the app initialization
rg "try|catch|Error|throw" src/utils/rpc.ts | head -20

Repository: antoncoding/monarch

Length of output: 174


🏁 Script executed:

# Verify what happens with empty URL in http() - check if viem has inline validation
rg "http\(" -B 2 -A 2 src/utils/rpc.ts

Repository: antoncoding/monarch

Length of output: 1694


🏁 Script executed:

# Check if there's documentation in README about environment variables and Etherlink RPC requirement
grep -i "etherlink\|rpc\|environment" README.md | head -20

Repository: antoncoding/monarch

Length of output: 45


🏁 Script executed:

# Search for any error handling or runtime validation that might catch empty URLs
rg "defaultRPC|getDefaultRPC" src/ -B 3 -A 3 | grep -i "error\|throw\|valid\|empty" | head -20

Repository: antoncoding/monarch

Length of output: 145


🏁 Script executed:

# Check if there's a startup validation function that checks RPC URLs
rg "validate|check|init" src/utils/rpc.ts -i

Repository: antoncoding/monarch

Length of output: 163


🏁 Script executed:

# Check if there's any initialization code that validates RPC URLs on startup
rg "_app|initialization|startup" src/pages -i | head -10

Repository: antoncoding/monarch

Length of output: 112


🏁 Script executed:

# Verify if http() from viem throws on empty URL or just fails silently on request
rg "import.*http|from.*viem" src/utils/rpc.ts -A 2

Repository: antoncoding/monarch

Length of output: 320


🏁 Script executed:

# Check all network configs to compare Etherlink's RPC setup with others
rg "defaultRPC: getRpcUrl" src/utils/networks.ts

Repository: antoncoding/monarch

Length of output: 780


🏁 Script executed:

# Search for how the app uses these clients to see if there's error handling at usage time
rg "getClient|defaultClients" src/ | head -20

Repository: antoncoding/monarch

Length of output: 1614


🏁 Script executed:

# Check if the public Etherlink RPC is documented anywhere in the codebase or comments
rg "node.mainnet.etherlink|etherlink.com" src/

Repository: antoncoding/monarch

Length of output: 135


🏁 Script executed:

# Verify one more time that Etherlink is the only chain without alchemySubdomain
rg "getRpcUrl\(" src/utils/networks.ts | grep -v "alchemySubdomain"

Repository: antoncoding/monarch

Length of output: 780


Etherlink RPC lacks fallback—empty string if env var unset.

Unlike other chains, Etherlink's getRpcUrl() call omits the alchemySubdomain parameter, so when NEXT_PUBLIC_ETHERLINK_RPC is missing, it returns '' (empty string) instead of falling back to Alchemy. This empty string gets passed to http() transport in rpc.ts, causing request failures.

Other networks all have Alchemy subdomains: eth-mainnet, opt-mainnet, base-mainnet, polygon-mainnet, unichain-mainnet, arb-mainnet, hyperliquid-mainnet, monad-mainnet.

Options:

  1. Add alchemySubdomain: 'etherlink-mainnet' if Alchemy supports it, or document as required
  2. Provide public fallback: https://node.mainnet.etherlink.com (verified working, chainId 0xa729)
🧰 Tools
🪛 Betterleaks (1.1.1)

[high] 186-186: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/utils/networks.ts` around lines 176 - 187, The Etherlink network entry
(SupportedNetworks.Etherlink) calls
getRpcUrl(process.env.NEXT_PUBLIC_ETHERLINK_RPC) without an alchemySubdomain or
public fallback, which yields an empty string when the env var is unset and
breaks http() transport in rpc.ts; update the Etherlink object to call
getRpcUrl(process.env.NEXT_PUBLIC_ETHERLINK_RPC, { alchemySubdomain:
'etherlink-mainnet' }) if Alchemy supports it, or pass a public fallback like {
fallback: 'https://node.mainnet.etherlink.com' } (or similar param your
getRpcUrl supports) so defaultRPC is never an empty string and http() always
receives a valid URL.

{
network: SupportedNetworks.HyperEVM,
chain: hyperEvm,
Expand Down
6 changes: 5 additions & 1 deletion src/utils/rpc.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { createPublicClient, http, type PublicClient } from 'viem';
import { arbitrum, base, mainnet, monad, optimism, polygon, unichain } from 'viem/chains';
import { arbitrum, base, etherlink, mainnet, monad, optimism, polygon, unichain } from 'viem/chains';
import { getDefaultRPC, getViemChain, SupportedNetworks, hyperEvm } from './networks';

// Default clients (cached)
Expand Down Expand Up @@ -33,6 +33,10 @@ const initializeDefaultClients = () => {
chain: arbitrum,
transport: http(getDefaultRPC(SupportedNetworks.Arbitrum)),
}) as PublicClient,
[SupportedNetworks.Etherlink]: createPublicClient({
chain: etherlink,
transport: http(getDefaultRPC(SupportedNetworks.Etherlink)),
}) as PublicClient,
[SupportedNetworks.HyperEVM]: createPublicClient({
chain: hyperEvm,
transport: http(getDefaultRPC(SupportedNetworks.HyperEVM)),
Expand Down
Loading