Skip to content

Commit c680b5c

Browse files
authored
cli: improve RPC error detection (#720)
* cli: improve RPC error detection * add rpc error handling to sui & solana + extend error message for evm chains
1 parent 8bd672c commit c680b5c

File tree

2 files changed

+101
-6
lines changed

2 files changed

+101
-6
lines changed

cli/src/error.ts

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import chalk from "chalk";
2+
import type { Chain, Network } from "@wormhole-foundation/sdk";
3+
import { chainToPlatform } from "@wormhole-foundation/sdk-base";
4+
5+
/**
6+
* Handles RPC-related errors and provides helpful error messages with suggestions.
7+
*
8+
* @param error - The error that occurred
9+
* @param chain - The chain being deployed to
10+
* @param network - The network (Mainnet, Testnet, Devnet)
11+
* @param rpc - The RPC endpoint URL that failed
12+
*/
13+
export function handleRpcError(
14+
error: any,
15+
chain: Chain,
16+
network: Network,
17+
rpc: string
18+
): never {
19+
const errorMessage = error?.message || String(error);
20+
const errorStack = error?.stack || "";
21+
22+
// Check if this is an RPC-related error by looking for common RPC error indicators
23+
const isRpcError =
24+
errorMessage.toLowerCase().includes("jsonrpc") ||
25+
errorStack.toLowerCase().includes("jsonrpc") ||
26+
errorMessage.toLowerCase().includes("rpc") ||
27+
errorMessage.toLowerCase().includes("connection") ||
28+
errorMessage.toLowerCase().includes("network error");
29+
30+
if (isRpcError) {
31+
console.error(chalk.red(`RPC connection error for ${chain} on ${network}\n`));
32+
console.error(chalk.yellow("RPC endpoint:"), chalk.white(rpc));
33+
console.error(chalk.yellow("Error:"), errorMessage);
34+
console.error();
35+
console.error(
36+
chalk.yellow(
37+
"This error usually means the RPC endpoint is missing, invalid, or unreachable."
38+
)
39+
);
40+
console.error(
41+
chalk.yellow(
42+
"You can specify a private RPC endpoint by creating an overrides.json file.\n"
43+
)
44+
);
45+
console.error(chalk.cyan("Create a file named ") + chalk.white("overrides.json") + chalk.cyan(" in your project root:"));
46+
console.error(chalk.white(`
47+
{
48+
"chains": {
49+
"${chain}": {
50+
"rpc": "https://your-private-rpc-endpoint"
51+
}
52+
}
53+
}
54+
`));
55+
56+
// Show chainlist.org only for EVM chains
57+
try {
58+
const platform = chainToPlatform(chain as any);
59+
if (platform === "Evm") {
60+
console.error(
61+
chalk.cyan(
62+
`Find RPC endpoints for ${chain}: https://chainlist.org`
63+
)
64+
);
65+
}
66+
} catch (e) {
67+
// If chainToPlatform fails, just skip the platform-specific message
68+
}
69+
70+
console.error(
71+
chalk.cyan(
72+
`For more information about overrides.json:\n` +
73+
` • https://wormhole.com/docs/products/token-transfers/native-token-transfers/faqs/#how-can-i-specify-a-custom-rpc-for-ntt`
74+
)
75+
);
76+
} else {
77+
console.error(chalk.red("\n Error during deployment:"));
78+
console.error(errorMessage);
79+
}
80+
process.exit(1);
81+
}

cli/src/index.ts

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ import { type SuiChains } from "@wormhole-foundation/sdk-sui";
7070

7171
import { colorizeDiff, diffObjects } from "./diff";
7272
import { forgeSignerArgs, getSigner, type SignerType } from "./getSigner";
73+
import { handleRpcError } from "./error";
7374

7475
// Configuration fields that should be excluded from diff operations
7576
// These are local-only configurations that don't have on-chain representations
@@ -3032,6 +3033,7 @@ async function deployEvm<N extends Network, C extends Chain>(
30323033
}
30333034

30343035
const rpc = ch.config.rpc;
3036+
30353037
// TODO: how to make specialRelayer configurable??
30363038
let specialRelayer: string;
30373039
if (ch.chain === "Avalanche") {
@@ -3042,10 +3044,17 @@ async function deployEvm<N extends Network, C extends Chain>(
30423044
specialRelayer = "0x63BE47835c7D66c4aA5B2C688Dc6ed9771c94C74";
30433045
}
30443046

3045-
const provider = new ethers.JsonRpcProvider(rpc);
3046-
const abi = ["function decimals() external view returns (uint8)"];
3047-
const tokenContract = new ethers.Contract(token, abi, provider);
3048-
const decimals: number = await tokenContract.decimals();
3047+
let provider: ethers.JsonRpcProvider;
3048+
let decimals: number;
3049+
3050+
try {
3051+
provider = new ethers.JsonRpcProvider(rpc);
3052+
const abi = ["function decimals() external view returns (uint8)"];
3053+
const tokenContract = new ethers.Contract(token, abi, provider);
3054+
decimals = await tokenContract.decimals();
3055+
} catch (error) {
3056+
handleRpcError(error, ch.chain, ch.network, rpc);
3057+
}
30493058

30503059
// TODO: should actually make these ENV variables.
30513060
const sig = "run(address,address,address,address,uint8,uint8)";
@@ -3279,7 +3288,12 @@ async function deploySolana<N extends Network, C extends SolanaChains>(
32793288
// get the mint authority of 'token'
32803289
const tokenMint = new PublicKey(token);
32813290
const connection: Connection = await ch.getRpc();
3282-
const mintInfo = await connection.getAccountInfo(tokenMint);
3291+
let mintInfo;
3292+
try {
3293+
mintInfo = await connection.getAccountInfo(tokenMint);
3294+
} catch (error) {
3295+
handleRpcError(error, ch.chain, ch.network, ch.config.rpc);
3296+
}
32833297
if (!mintInfo) {
32843298
console.error(`Mint ${token} not found on ${ch.chain} ${ch.network}`);
32853299
process.exit(1);
@@ -4092,7 +4106,7 @@ async function deploySui<N extends Network, C extends Chain>(
40924106
} catch (deploymentError) {
40934107
// Restore original Move.toml files if deployment fails
40944108
restore();
4095-
throw deploymentError;
4109+
handleRpcError(deploymentError, ch.chain, ch.network, ch.config.rpc);
40964110
}
40974111
});
40984112
}

0 commit comments

Comments
 (0)