Skip to content
Merged
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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
6 changes: 3 additions & 3 deletions docs/ADDING_SVM_CHAIN.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand All @@ -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
2 changes: 2 additions & 0 deletions src/lib/networks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ export {
svmEndpointHaystack,
usesDeprecatedSvmHost,
filterPublicSvmRpcUrls,
selectPublicSvmExplorer,
type SvmExplorerCandidate,
} from './svm-endpoints';

export {
Expand Down
12 changes: 9 additions & 3 deletions src/lib/networks/solana.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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}',
};
Expand All @@ -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',
};
Expand All @@ -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',
};
Expand Down
7 changes: 3 additions & 4 deletions src/lib/networks/supported-catalog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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' },
Expand Down Expand Up @@ -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;
Expand Down
25 changes: 23 additions & 2 deletions src/lib/networks/svm-endpoints.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [
Expand All @@ -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 {
Expand All @@ -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));
});
}
2 changes: 1 addition & 1 deletion src/web/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ const WebAppFrame: React.FC<{ children: React.ReactNode }> = ({ children }) => (
<Text fontSize="xs" color="gray.400">
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.
</Text>
</Box>
</Flex>
Expand Down
6 changes: 6 additions & 0 deletions tests/lib/networks/supported-catalog.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand Down
39 changes: 31 additions & 8 deletions tests/lib/networks/svm-endpoints.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -12,6 +13,7 @@ import {
filterPublicSvmRpcUrls,
getExplorerAccountUrl,
getExplorerTxUrl,
selectPublicSvmExplorer,
SOLANA_DEVNET,
SOLANA_MAINNET,
SOLANA_TESTNET,
Expand Down Expand Up @@ -42,22 +44,33 @@ 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', () => {
expect(usesDeprecatedSvmHost('https://rpc.ankr.com/solana')).toBe(true);
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(
Expand All @@ -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');
});
});
2 changes: 2 additions & 0 deletions tests/popup/SupportedNetworksPanel.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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();
});
});
Loading