From 713e4622ca879dcdc4978ee4d722a6ade724cf69 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 21 Sep 2026 01:08:11 +0000 Subject: [PATCH] Refresh leftover Solana explorer after low activity Point leftover Solana account and transaction links at SolanaFM after explorer.solana.com started returning a Vercel Security Checkpoint 429. Denylist the retired host so later list edits do not bring it back. Co-authored-by: Corey --- README.md | 2 +- docs/ADDING_SVM_CHAIN.md | 6 +-- src/lib/networks/index.ts | 2 + src/lib/networks/solana.ts | 12 ++++-- src/lib/networks/supported-catalog.ts | 7 ++-- src/lib/networks/svm-endpoints.ts | 25 ++++++++++++- src/web/main.tsx | 2 +- tests/lib/networks/supported-catalog.test.ts | 6 +++ tests/lib/networks/svm-endpoints.test.ts | 39 ++++++++++++++++---- tests/popup/SupportedNetworksPanel.test.tsx | 2 + 10 files changed, 81 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index 25c1bbf..a41bc0d 100644 --- a/README.md +++ b/README.md @@ -61,7 +61,7 @@ Advertised EVM networks load balances through current public RPCs (PublicNode, D | ------- | --------------- | ------ | -------------- | ------- | | Solana | solana-mainnet | SOL | Base58 | Enabled | -Advertised Solana mainnet loads balances through current public RPCs (official Solana and PublicNode). Retired or key-gated hosts such as Ankr public RPC and Solana dRPC were removed. +Advertised Solana mainnet loads balances through current public RPCs (official Solana and PublicNode). Explorer links use SolanaFM after leftover explorer.solana.com started returning a Vercel Security Checkpoint 429. Retired or key-gated hosts such as Ankr public RPC and Solana dRPC were removed. Eclipse, Solana Devnet, and Solana Testnet are defined in config but disabled by default. diff --git a/docs/ADDING_SVM_CHAIN.md b/docs/ADDING_SVM_CHAIN.md index 2518fc7..e38862b 100644 --- a/docs/ADDING_SVM_CHAIN.md +++ b/docs/ADDING_SVM_CHAIN.md @@ -12,7 +12,7 @@ Solana mainnet is enabled. Devnet, testnet, and Eclipse are defined but disabled | ---------------------- | ------------ | ------------------------------------------ | | Address display | Supported | Shown on the dashboard SVM tab | | Native SOL balance | Supported | Via JSON-RPC `getBalance` | -| Explorer links | Supported | Solana Explorer / Eclipse Scan | +| Explorer links | Supported | SolanaFM / Eclipse Scan | | Extra SVM mainnets | Config-only | Add a config entry and enable when ready | ## Step 1: Define Network Configuration @@ -54,7 +54,7 @@ export const NEW_SVM_MAINNET: SvmNetworkConfig = { | `rpcUrls` | RPC endpoints, failover order | public HTTPS RPCs | | `enabled` | Whether users see the network | `true` for mainnets | -Use more than one public HTTPS RPC so failover can skip a dead host. Do not add retired or key-gated endpoints such as Project Serum, Ankr public RPC (`rpc.ankr.com`), Solana dRPC, or Lava's discontinued Solana host. +Use more than one public HTTPS RPC so failover can skip a dead host. Do not add retired or key-gated endpoints such as Project Serum, Ankr public RPC (`rpc.ankr.com`), Solana dRPC, or Lava's discontinued Solana host. Do not point Solana explorer links at leftover `explorer.solana.com` (Vercel Security Checkpoint 429); use [SolanaFM](https://solana.fm). ## Step 2: Register and Advertise @@ -75,7 +75,7 @@ Then run the web app or extension, open Settings, and confirm the network family ## Related Files - `src/lib/networks/solana.ts` — SVM network configs -- `src/lib/networks/svm-endpoints.ts` — public-RPC denylist and filters +- `src/lib/networks/svm-endpoints.ts` — public-RPC / leftover explorer denylist and filters - `src/lib/networks/supported-catalog.ts` — curated user-facing list - `src/lib/solana/client.ts` — JSON-RPC balance and account reads - `src/popup/components/SupportedNetworksPanel.tsx` — Settings summary diff --git a/src/lib/networks/index.ts b/src/lib/networks/index.ts index 1c9f63a..ffabb18 100644 --- a/src/lib/networks/index.ts +++ b/src/lib/networks/index.ts @@ -50,6 +50,8 @@ export { svmEndpointHaystack, usesDeprecatedSvmHost, filterPublicSvmRpcUrls, + selectPublicSvmExplorer, + type SvmExplorerCandidate, } from './svm-endpoints'; export { diff --git a/src/lib/networks/solana.ts b/src/lib/networks/solana.ts index 51a52be..52d2658 100644 --- a/src/lib/networks/solana.ts +++ b/src/lib/networks/solana.ts @@ -29,7 +29,9 @@ export const SOLANA_MAINNET: SvmNetworkConfig = { ], logoUrl: 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/solana/info/logo.png', - explorerUrl: 'https://explorer.solana.com', + // Leftover explorer.solana.com now returns a Vercel Security Checkpoint + // 429. SolanaFM still serves address/tx pages with the same paths. + explorerUrl: 'https://solana.fm', explorerAccountPath: '/address/{address}', explorerTxPath: '/tx/{txHash}', }; @@ -48,7 +50,9 @@ export const SOLANA_DEVNET: SvmNetworkConfig = { rpcUrls: ['https://api.devnet.solana.com'], logoUrl: 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/solana/info/logo.png', - explorerUrl: 'https://explorer.solana.com', + // Leftover explorer.solana.com now 429s. SolanaFM still serves + // ?cluster=devnet address/tx pages. + explorerUrl: 'https://solana.fm', explorerAccountPath: '/address/{address}?cluster=devnet', explorerTxPath: '/tx/{txHash}?cluster=devnet', }; @@ -66,7 +70,9 @@ export const SOLANA_TESTNET: SvmNetworkConfig = { rpcUrls: ['https://api.testnet.solana.com'], logoUrl: 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/solana/info/logo.png', - explorerUrl: 'https://explorer.solana.com', + // Leftover explorer.solana.com now 429s. SolanaFM still serves + // ?cluster=testnet address/tx pages. + explorerUrl: 'https://solana.fm', explorerAccountPath: '/address/{address}?cluster=testnet', explorerTxPath: '/tx/{txHash}?cluster=testnet', }; diff --git a/src/lib/networks/supported-catalog.ts b/src/lib/networks/supported-catalog.ts index bf6bd37..db94c2e 100644 --- a/src/lib/networks/supported-catalog.ts +++ b/src/lib/networks/supported-catalog.ts @@ -4,8 +4,7 @@ * The live registry can include additional auto-synced chains. This list is the * curated set shown in Settings and kept in sync with README.md. * - * Last reviewed: 2026-09-20 (leftover Avalanche snowscan explorer and - * leftover Juno Lavender.Five / Celestia LCD hop refresh). + * Last reviewed: 2026-09-21 (leftover Solana explorer refresh). */ export { DEPRECATED_SVM_ENDPOINT_HOSTS } from './svm-endpoints'; @@ -19,7 +18,7 @@ export interface SupportedNetworkEntry { family: SupportedNetworkFamily; } -export const SUPPORTED_NETWORK_CATALOG_REVIEWED_AT = '2026-09-20'; +export const SUPPORTED_NETWORK_CATALOG_REVIEWED_AT = '2026-09-21'; export const SUPPORTED_NETWORK_CATALOG: readonly SupportedNetworkEntry[] = [ { id: 'beezee-1', name: 'BeeZee', symbol: 'BZE', family: 'cosmos' }, @@ -73,7 +72,7 @@ export function getSupportedNetworkFamilySummary(family: SupportedNetworkFamily) case 'evm': return 'Ethereum and compatible networks load balances through current public RPCs (PublicNode, DRPC, and official chain endpoints). Retired leftover hosts such as snowscan.xyz were removed. Earlier leftover 1rpc.io/sepolia, leftover 1rpc.io/eth, moonriver.unitedbloc.com, Sepolia.org RPCs, zkevm.polygonscan.com, Cloudflare Ethereum, Ankr public RPC, and BlastAPI hops stay denylisted.'; case 'svm': - return 'Solana mainnet balances load through current public RPCs (official Solana and PublicNode). Retired or key-gated hosts such as Ankr public RPC and Solana dRPC were removed.'; + return 'Solana mainnet balances load through current public RPCs (official Solana and PublicNode). Explorer links use SolanaFM after leftover explorer.solana.com started returning a Vercel Security Checkpoint 429. Retired or key-gated hosts such as Ankr public RPC and Solana dRPC were removed.'; default: { const _exhaustive: never = family; return _exhaustive; diff --git a/src/lib/networks/svm-endpoints.ts b/src/lib/networks/svm-endpoints.ts index 403635e..23e0a47 100644 --- a/src/lib/networks/svm-endpoints.ts +++ b/src/lib/networks/svm-endpoints.ts @@ -3,8 +3,11 @@ * * SolanaClient starts on rpcUrls[0] and can switch hops for failover. * After a period of low activity, several bundled hosts were key-gated, - * paid-only, or discontinued. Keep those hosts out of advertised lists - * so the first hop can succeed without an API key. + * paid-only, or discontinued. Leftover explorer.solana.com then lingered + * after the official explorer started returning a Vercel Security + * Checkpoint 429. SolanaFM still serves public address/tx pages with + * the same path templates. Keep those hosts out of advertised lists so + * the first hop can succeed without an API key. */ export const DEPRECATED_SVM_ENDPOINT_HOSTS = [ @@ -14,6 +17,7 @@ export const DEPRECATED_SVM_ENDPOINT_HOSTS = [ 'solana.drpc.org', 'solana.lava.build', 'gateway.tatum.io', + 'explorer.solana.com', ] as const; export function svmEndpointHaystack(rpcUrls: readonly string[], explorerUrl?: string): string { @@ -40,3 +44,20 @@ export function filterPublicSvmRpcUrls(urls: string[]): string[] { }) .slice(0, 5); } + +export interface SvmExplorerCandidate { + url?: string; +} + +/** + * Pick a live public explorer, skipping leftover explorer.solana.com + * after it started returning a Vercel Security Checkpoint 429. + */ +export function selectPublicSvmExplorer( + explorers: readonly SvmExplorerCandidate[] = [] +): SvmExplorerCandidate | undefined { + return explorers.find((explorer) => { + const url = explorer.url; + return Boolean(url && url.startsWith('https://') && !usesDeprecatedSvmHost(url)); + }); +} diff --git a/src/web/main.tsx b/src/web/main.tsx index a6228cb..c1bbad6 100644 --- a/src/web/main.tsx +++ b/src/web/main.tsx @@ -111,7 +111,7 @@ const WebAppFrame: React.FC<{ children: React.ReactNode }> = ({ children }) => ( Vidulum is a cross-chain wallet for Cosmos, Bitcoin-like, EVM, and Solana networks. Use Vidulum for IBC transfers, multi-chain swaps, and asset management across supported - networks. Leftover Avalanche snowscan explorer last checked in September 2026. + networks. Leftover Solana explorer last checked in September 2026. diff --git a/tests/lib/networks/supported-catalog.test.ts b/tests/lib/networks/supported-catalog.test.ts index 8503baa..6fde404 100644 --- a/tests/lib/networks/supported-catalog.test.ts +++ b/tests/lib/networks/supported-catalog.test.ts @@ -94,6 +94,12 @@ describe('Supported network catalog', () => { expect(getSupportedNetworkFamilySummary('svm')).toMatch( /current public RPCs/ ); + expect(getSupportedNetworkFamilySummary('svm')).toMatch( + /SolanaFM/ + ); + expect(getSupportedNetworkFamilySummary('svm')).toMatch( + /explorer\.solana\.com/ + ); }); it('formats the catalog review month for Settings copy', () => { diff --git a/tests/lib/networks/svm-endpoints.test.ts b/tests/lib/networks/svm-endpoints.test.ts index 26bac2e..56dda7c 100644 --- a/tests/lib/networks/svm-endpoints.test.ts +++ b/tests/lib/networks/svm-endpoints.test.ts @@ -2,8 +2,9 @@ * SVM endpoint freshness tests * * After the lull, advertised Solana still listed Ankr (403 without a key) - * and Solana dRPC (paid-plan only). Cosmos/EVM/UTXO lists were already - * cleaned; this keeps SVM RPC lists honest. + * and Solana dRPC (paid-plan only). Leftover explorer.solana.com then + * lingered after the official explorer started returning a Vercel + * Security Checkpoint 429. This keeps SVM RPC and explorer lists honest. */ import { @@ -12,6 +13,7 @@ import { filterPublicSvmRpcUrls, getExplorerAccountUrl, getExplorerTxUrl, + selectPublicSvmExplorer, SOLANA_DEVNET, SOLANA_MAINNET, SOLANA_TESTNET, @@ -42,15 +44,24 @@ describe('SVM endpoint freshness', () => { ]); }); - it('keeps official Solana explorer paths for account and transaction links', () => { - expect(SOLANA_MAINNET.explorerUrl).toBe('https://explorer.solana.com'); + it('points Solana users at SolanaFM instead of leftover explorer.solana.com', () => { + expect(SOLANA_MAINNET.explorerUrl).toBe('https://solana.fm'); expect(getExplorerAccountUrl('solana-mainnet', 'So11111111111111111111111111111111111111112')).toBe( - 'https://explorer.solana.com/address/So11111111111111111111111111111111111111112' + 'https://solana.fm/address/So11111111111111111111111111111111111111112' ); - expect(getExplorerTxUrl('solana-mainnet', 'abcd')).toBe('https://explorer.solana.com/tx/abcd'); - expect(SOLANA_DEVNET.explorerUrl).toBe('https://explorer.solana.com'); - expect(SOLANA_TESTNET.explorerUrl).toBe('https://explorer.solana.com'); + expect(getExplorerTxUrl('solana-mainnet', 'abcd')).toBe('https://solana.fm/tx/abcd'); + expect(getExplorerAccountUrl('solana-devnet', 'So11111111111111111111111111111111111111112')).toBe( + 'https://solana.fm/address/So11111111111111111111111111111111111111112?cluster=devnet' + ); + expect(getExplorerTxUrl('solana-testnet', 'abcd')).toBe( + 'https://solana.fm/tx/abcd?cluster=testnet' + ); + expect(SOLANA_DEVNET.explorerUrl).toBe('https://solana.fm'); + expect(SOLANA_TESTNET.explorerUrl).toBe('https://solana.fm'); expect(ECLIPSE_MAINNET.explorerUrl).toBe('https://eclipsescan.xyz'); + expect(svmEndpointHaystack(SOLANA_MAINNET.rpcUrls, SOLANA_MAINNET.explorerUrl)).not.toContain( + 'explorer.solana.com' + ); }); it('classifies retired hosts and strips them from public RPC lists', () => { @@ -58,6 +69,8 @@ describe('SVM endpoint freshness', () => { expect(usesDeprecatedSvmHost('https://solana.drpc.org')).toBe(true); expect(usesDeprecatedSvmHost('https://solana.lava.build')).toBe(true); expect(usesDeprecatedSvmHost('https://solana-api.projectserum.com')).toBe(true); + expect(usesDeprecatedSvmHost('https://explorer.solana.com')).toBe(true); + expect(usesDeprecatedSvmHost('https://solana.fm')).toBe(false); expect(usesDeprecatedSvmHost('https://solana-rpc.publicnode.com')).toBe(false); expect( @@ -72,4 +85,14 @@ describe('SVM endpoint freshness', () => { ]) ).toEqual(['https://api.mainnet-beta.solana.com', 'https://solana-rpc.publicnode.com']); }); + + it('skips leftover explorer.solana.com when picking a public SVM explorer', () => { + expect(selectPublicSvmExplorer([{ url: 'https://explorer.solana.com' }])).toBeUndefined(); + expect( + selectPublicSvmExplorer([ + { url: 'https://explorer.solana.com' }, + { url: 'https://solana.fm' }, + ])?.url + ).toBe('https://solana.fm'); + }); }); diff --git a/tests/popup/SupportedNetworksPanel.test.tsx b/tests/popup/SupportedNetworksPanel.test.tsx index dbfc7a3..4075c77 100644 --- a/tests/popup/SupportedNetworksPanel.test.tsx +++ b/tests/popup/SupportedNetworksPanel.test.tsx @@ -63,6 +63,8 @@ describe('SupportedNetworksPanel', () => { expect(screen.getByText(/1rpc\.io\/sepolia, leftover 1rpc\.io\/eth, moonriver\.unitedbloc\.com, Sepolia\.org RPCs, zkevm\.polygonscan\.com/)).toBeInTheDocument(); expect(screen.getByText(/^Solana$/)).toBeInTheDocument(); expect(screen.getByText(/official Solana and PublicNode/)).toBeInTheDocument(); + expect(screen.getByText(/SolanaFM/)).toBeInTheDocument(); + expect(screen.getByText(/explorer\.solana\.com/)).toBeInTheDocument(); expect(screen.getByText(/Ankr public RPC and Solana dRPC/)).toBeInTheDocument(); }); });