diff --git a/client/src/rpc_custom_error.rs b/client/src/rpc_custom_error.rs index 1a6a6b84cee54f..95ef60293cae18 100644 --- a/client/src/rpc_custom_error.rs +++ b/client/src/rpc_custom_error.rs @@ -7,6 +7,7 @@ use { thiserror::Error, }; +// Keep in sync with web3.js/src/errors.ts pub const JSON_RPC_SERVER_ERROR_BLOCK_CLEANED_UP: i64 = -32001; pub const JSON_RPC_SERVER_ERROR_SEND_TRANSACTION_PREFLIGHT_FAILURE: i64 = -32002; pub const JSON_RPC_SERVER_ERROR_TRANSACTION_SIGNATURE_VERIFICATION_FAILURE: i64 = -32003; diff --git a/web3.js/src/connection.ts b/web3.js/src/connection.ts index e3f3c45241a5cf..3715632dfffaaf 100644 --- a/web3.js/src/connection.ts +++ b/web3.js/src/connection.ts @@ -26,7 +26,7 @@ import RpcClient from 'jayson/lib/client/browser'; import {AgentManager} from './agent-manager'; import {EpochSchedule} from './epoch-schedule'; -import {SendTransactionError} from './errors'; +import {SendTransactionError, SolanaJSONRPCError} from './errors'; import fetchImpl, {Response} from './fetch-impl'; import {NonceAccount} from './nonce-account'; import {PublicKey} from './publickey'; @@ -2383,11 +2383,9 @@ export class Connection { const unsafeRes = await this._rpcRequest('getBalance', args); const res = create(unsafeRes, jsonRpcResultAndContext(number())); if ('error' in res) { - throw new Error( - 'failed to get balance for ' + - publicKey.toBase58() + - ': ' + - res.error.message, + throw new SolanaJSONRPCError( + res.error, + `failed to get balance for ${publicKey.toBase58()}`, ); } return res.result; @@ -2416,8 +2414,9 @@ export class Connection { const unsafeRes = await this._rpcRequest('getBlockTime', [slot]); const res = create(unsafeRes, jsonRpcResult(nullable(number()))); if ('error' in res) { - throw new Error( - 'failed to get block time for slot ' + slot + ': ' + res.error.message, + throw new SolanaJSONRPCError( + res.error, + `failed to get block time for slot ${slot}`, ); } return res.result; @@ -2431,8 +2430,9 @@ export class Connection { const unsafeRes = await this._rpcRequest('minimumLedgerSlot', []); const res = create(unsafeRes, jsonRpcResult(number())); if ('error' in res) { - throw new Error( - 'failed to get minimum ledger slot: ' + res.error.message, + throw new SolanaJSONRPCError( + res.error, + 'failed to get minimum ledger slot', ); } return res.result; @@ -2445,8 +2445,9 @@ export class Connection { const unsafeRes = await this._rpcRequest('getFirstAvailableBlock', []); const res = create(unsafeRes, SlotRpcResult); if ('error' in res) { - throw new Error( - 'failed to get first available block: ' + res.error.message, + throw new SolanaJSONRPCError( + res.error, + 'failed to get first available block', ); } return res.result; @@ -2475,7 +2476,7 @@ export class Connection { const unsafeRes = await this._rpcRequest('getSupply', [configArg]); const res = create(unsafeRes, GetSupplyRpcResult); if ('error' in res) { - throw new Error('failed to get supply: ' + res.error.message); + throw new SolanaJSONRPCError(res.error, 'failed to get supply'); } return res.result; } @@ -2491,7 +2492,7 @@ export class Connection { const unsafeRes = await this._rpcRequest('getTokenSupply', args); const res = create(unsafeRes, jsonRpcResultAndContext(TokenAmountResult)); if ('error' in res) { - throw new Error('failed to get token supply: ' + res.error.message); + throw new SolanaJSONRPCError(res.error, 'failed to get token supply'); } return res.result; } @@ -2507,8 +2508,9 @@ export class Connection { const unsafeRes = await this._rpcRequest('getTokenAccountBalance', args); const res = create(unsafeRes, jsonRpcResultAndContext(TokenAmountResult)); if ('error' in res) { - throw new Error( - 'failed to get token account balance: ' + res.error.message, + throw new SolanaJSONRPCError( + res.error, + 'failed to get token account balance', ); } return res.result; @@ -2539,11 +2541,9 @@ export class Connection { const unsafeRes = await this._rpcRequest('getTokenAccountsByOwner', args); const res = create(unsafeRes, GetTokenAccountsByOwner); if ('error' in res) { - throw new Error( - 'failed to get token accounts owned by account ' + - ownerAddress.toBase58() + - ': ' + - res.error.message, + throw new SolanaJSONRPCError( + res.error, + `failed to get token accounts owned by account ${ownerAddress.toBase58()}`, ); } return res.result; @@ -2574,11 +2574,9 @@ export class Connection { const unsafeRes = await this._rpcRequest('getTokenAccountsByOwner', args); const res = create(unsafeRes, GetParsedTokenAccountsByOwner); if ('error' in res) { - throw new Error( - 'failed to get token accounts owned by account ' + - ownerAddress.toBase58() + - ': ' + - res.error.message, + throw new SolanaJSONRPCError( + res.error, + `failed to get token accounts owned by account ${ownerAddress.toBase58()}`, ); } return res.result; @@ -2598,7 +2596,7 @@ export class Connection { const unsafeRes = await this._rpcRequest('getLargestAccounts', args); const res = create(unsafeRes, GetLargestAccountsRpcResult); if ('error' in res) { - throw new Error('failed to get largest accounts: ' + res.error.message); + throw new SolanaJSONRPCError(res.error, 'failed to get largest accounts'); } return res.result; } @@ -2615,8 +2613,9 @@ export class Connection { const unsafeRes = await this._rpcRequest('getTokenLargestAccounts', args); const res = create(unsafeRes, GetTokenLargestAccountsResult); if ('error' in res) { - throw new Error( - 'failed to get token largest accounts: ' + res.error.message, + throw new SolanaJSONRPCError( + res.error, + 'failed to get token largest accounts', ); } return res.result; @@ -2636,11 +2635,9 @@ export class Connection { jsonRpcResultAndContext(nullable(AccountInfoResult)), ); if ('error' in res) { - throw new Error( - 'failed to get info about account ' + - publicKey.toBase58() + - ': ' + - res.error.message, + throw new SolanaJSONRPCError( + res.error, + `failed to get info about account ${publicKey.toBase58()}`, ); } return res.result; @@ -2666,11 +2663,9 @@ export class Connection { jsonRpcResultAndContext(nullable(ParsedAccountInfoResult)), ); if ('error' in res) { - throw new Error( - 'failed to get info about account ' + - publicKey.toBase58() + - ': ' + - res.error.message, + throw new SolanaJSONRPCError( + res.error, + `failed to get info about account ${publicKey.toBase58()}`, ); } return res.result; @@ -2708,8 +2703,9 @@ export class Connection { jsonRpcResultAndContext(array(nullable(AccountInfoResult))), ); if ('error' in res) { - throw new Error( - 'failed to get info for accounts ' + keys + ': ' + res.error.message, + throw new SolanaJSONRPCError( + res.error, + `failed to get info for accounts ${keys}`, ); } return res.result; @@ -2747,10 +2743,9 @@ export class Connection { const unsafeRes = await this._rpcRequest('getStakeActivation', args); const res = create(unsafeRes, jsonRpcResult(StakeActivationResult)); if ('error' in res) { - throw new Error( - `failed to get Stake Activation ${publicKey.toBase58()}: ${ - res.error.message - }`, + throw new SolanaJSONRPCError( + res.error, + `failed to get Stake Activation ${publicKey.toBase58()}`, ); } return res.result; @@ -2794,11 +2789,9 @@ export class Connection { const unsafeRes = await this._rpcRequest('getProgramAccounts', args); const res = create(unsafeRes, jsonRpcResult(array(KeyedAccountInfoResult))); if ('error' in res) { - throw new Error( - 'failed to get accounts owned by program ' + - programId.toBase58() + - ': ' + - res.error.message, + throw new SolanaJSONRPCError( + res.error, + `failed to get accounts owned by program ${programId.toBase58()}`, ); } return res.result; @@ -2845,11 +2838,9 @@ export class Connection { jsonRpcResult(array(KeyedParsedAccountInfoResult)), ); if ('error' in res) { - throw new Error( - 'failed to get accounts owned by program ' + - programId.toBase58() + - ': ' + - res.error.message, + throw new SolanaJSONRPCError( + res.error, + `failed to get accounts owned by program ${programId.toBase58()}`, ); } return res.result; @@ -3004,7 +2995,7 @@ export class Connection { const unsafeRes = await this._rpcRequest('getClusterNodes', []); const res = create(unsafeRes, jsonRpcResult(array(ContactInfoResult))); if ('error' in res) { - throw new Error('failed to get cluster nodes: ' + res.error.message); + throw new SolanaJSONRPCError(res.error, 'failed to get cluster nodes'); } return res.result; } @@ -3017,7 +3008,7 @@ export class Connection { const unsafeRes = await this._rpcRequest('getVoteAccounts', args); const res = create(unsafeRes, GetVoteAccounts); if ('error' in res) { - throw new Error('failed to get vote accounts: ' + res.error.message); + throw new SolanaJSONRPCError(res.error, 'failed to get vote accounts'); } return res.result; } @@ -3030,7 +3021,7 @@ export class Connection { const unsafeRes = await this._rpcRequest('getSlot', args); const res = create(unsafeRes, jsonRpcResult(number())); if ('error' in res) { - throw new Error('failed to get slot: ' + res.error.message); + throw new SolanaJSONRPCError(res.error, 'failed to get slot'); } return res.result; } @@ -3043,7 +3034,7 @@ export class Connection { const unsafeRes = await this._rpcRequest('getSlotLeader', args); const res = create(unsafeRes, jsonRpcResult(string())); if ('error' in res) { - throw new Error('failed to get slot leader: ' + res.error.message); + throw new SolanaJSONRPCError(res.error, 'failed to get slot leader'); } return res.result; } @@ -3062,7 +3053,7 @@ export class Connection { const unsafeRes = await this._rpcRequest('getSlotLeaders', args); const res = create(unsafeRes, jsonRpcResult(array(PublicKeyFromString))); if ('error' in res) { - throw new Error('failed to get slot leaders: ' + res.error.message); + throw new SolanaJSONRPCError(res.error, 'failed to get slot leaders'); } return res.result; } @@ -3097,7 +3088,7 @@ export class Connection { const unsafeRes = await this._rpcRequest('getSignatureStatuses', params); const res = create(unsafeRes, GetSignatureStatusesRpcResult); if ('error' in res) { - throw new Error('failed to get signature status: ' + res.error.message); + throw new SolanaJSONRPCError(res.error, 'failed to get signature status'); } return res.result; } @@ -3110,7 +3101,10 @@ export class Connection { const unsafeRes = await this._rpcRequest('getTransactionCount', args); const res = create(unsafeRes, jsonRpcResult(number())); if ('error' in res) { - throw new Error('failed to get transaction count: ' + res.error.message); + throw new SolanaJSONRPCError( + res.error, + 'failed to get transaction count', + ); } return res.result; } @@ -3138,7 +3132,7 @@ export class Connection { const unsafeRes = await this._rpcRequest('getInflationGovernor', args); const res = create(unsafeRes, GetInflationGovernorRpcResult); if ('error' in res) { - throw new Error('failed to get inflation: ' + res.error.message); + throw new SolanaJSONRPCError(res.error, 'failed to get inflation'); } return res.result; } @@ -3162,7 +3156,7 @@ export class Connection { const unsafeRes = await this._rpcRequest('getInflationReward', args); const res = create(unsafeRes, GetInflationRewardResult); if ('error' in res) { - throw new Error('failed to get inflation reward: ' + res.error.message); + throw new SolanaJSONRPCError(res.error, 'failed to get inflation reward'); } return res.result; } @@ -3175,7 +3169,7 @@ export class Connection { const unsafeRes = await this._rpcRequest('getEpochInfo', args); const res = create(unsafeRes, GetEpochInfoRpcResult); if ('error' in res) { - throw new Error('failed to get epoch info: ' + res.error.message); + throw new SolanaJSONRPCError(res.error, 'failed to get epoch info'); } return res.result; } @@ -3187,7 +3181,7 @@ export class Connection { const unsafeRes = await this._rpcRequest('getEpochSchedule', []); const res = create(unsafeRes, GetEpochScheduleRpcResult); if ('error' in res) { - throw new Error('failed to get epoch schedule: ' + res.error.message); + throw new SolanaJSONRPCError(res.error, 'failed to get epoch schedule'); } const epochSchedule = res.result; return new EpochSchedule( @@ -3207,7 +3201,7 @@ export class Connection { const unsafeRes = await this._rpcRequest('getLeaderSchedule', []); const res = create(unsafeRes, GetLeaderScheduleRpcResult); if ('error' in res) { - throw new Error('failed to get leader schedule: ' + res.error.message); + throw new SolanaJSONRPCError(res.error, 'failed to get leader schedule'); } return res.result; } @@ -3248,7 +3242,7 @@ export class Connection { const unsafeRes = await this._rpcRequest('getRecentBlockhash', args); const res = create(unsafeRes, GetRecentBlockhashAndContextRpcResult); if ('error' in res) { - throw new Error('failed to get recent blockhash: ' + res.error.message); + throw new SolanaJSONRPCError(res.error, 'failed to get recent blockhash'); } return res.result; } @@ -3267,8 +3261,9 @@ export class Connection { ); const res = create(unsafeRes, GetRecentPerformanceSamplesRpcResult); if ('error' in res) { - throw new Error( - 'failed to get recent performance samples: ' + res.error.message, + throw new SolanaJSONRPCError( + res.error, + 'failed to get recent performance samples', ); } @@ -3292,7 +3287,7 @@ export class Connection { const res = create(unsafeRes, GetFeeCalculatorRpcResult); if ('error' in res) { - throw new Error('failed to get fee calculator: ' + res.error.message); + throw new SolanaJSONRPCError(res.error, 'failed to get fee calculator'); } const {context, value} = res.result; return { @@ -3314,7 +3309,7 @@ export class Connection { const res = create(unsafeRes, jsonRpcResultAndContext(nullable(number()))); if ('error' in res) { - throw new Error('failed to get slot: ' + res.error.message); + throw new SolanaJSONRPCError(res.error, 'failed to get slot'); } if (res.result === null) { throw new Error('invalid blockhash'); @@ -3365,7 +3360,7 @@ export class Connection { const unsafeRes = await this._rpcRequest('getLatestBlockhash', args); const res = create(unsafeRes, GetLatestBlockhashRpcResult); if ('error' in res) { - throw new Error('failed to get latest blockhash: ' + res.error.message); + throw new SolanaJSONRPCError(res.error, 'failed to get latest blockhash'); } return res.result; } @@ -3377,7 +3372,7 @@ export class Connection { const unsafeRes = await this._rpcRequest('getVersion', []); const res = create(unsafeRes, jsonRpcResult(VersionResult)); if ('error' in res) { - throw new Error('failed to get version: ' + res.error.message); + throw new SolanaJSONRPCError(res.error, 'failed to get version'); } return res.result; } @@ -3389,7 +3384,7 @@ export class Connection { const unsafeRes = await this._rpcRequest('getGenesisHash', []); const res = create(unsafeRes, jsonRpcResult(string())); if ('error' in res) { - throw new Error('failed to get genesis hash: ' + res.error.message); + throw new SolanaJSONRPCError(res.error, 'failed to get genesis hash'); } return res.result; } @@ -3409,7 +3404,7 @@ export class Connection { const res = create(unsafeRes, GetBlockRpcResult); if ('error' in res) { - throw new Error('failed to get confirmed block: ' + res.error.message); + throw new SolanaJSONRPCError(res.error, 'failed to get confirmed block'); } const result = res.result; @@ -3438,8 +3433,9 @@ export class Connection { const unsafeRes = await this._rpcRequest('getBlockHeight', args); const res = create(unsafeRes, jsonRpcResult(number())); if ('error' in res) { - throw new Error( - 'failed to get block height information: ' + res.error.message, + throw new SolanaJSONRPCError( + res.error, + 'failed to get block height information', ); } @@ -3467,8 +3463,9 @@ export class Connection { const unsafeRes = await this._rpcRequest('getBlockProduction', args); const res = create(unsafeRes, BlockProductionResponseStruct); if ('error' in res) { - throw new Error( - 'failed to get block production information: ' + res.error.message, + throw new SolanaJSONRPCError( + res.error, + 'failed to get block production information', ); } @@ -3489,7 +3486,7 @@ export class Connection { const unsafeRes = await this._rpcRequest('getTransaction', args); const res = create(unsafeRes, GetTransactionRpcResult); if ('error' in res) { - throw new Error('failed to get transaction: ' + res.error.message); + throw new SolanaJSONRPCError(res.error, 'failed to get transaction'); } const result = res.result; @@ -3519,7 +3516,7 @@ export class Connection { const unsafeRes = await this._rpcRequest('getTransaction', args); const res = create(unsafeRes, GetParsedTransactionRpcResult); if ('error' in res) { - throw new Error('failed to get transaction: ' + res.error.message); + throw new SolanaJSONRPCError(res.error, 'failed to get transaction'); } return res.result; } @@ -3547,7 +3544,7 @@ export class Connection { const res = unsafeRes.map((unsafeRes: any) => { const res = create(unsafeRes, GetParsedTransactionRpcResult); if ('error' in res) { - throw new Error('failed to get transactions: ' + res.error.message); + throw new SolanaJSONRPCError(res.error, 'failed to get transactions'); } return res.result; }); @@ -3575,7 +3572,7 @@ export class Connection { const res = unsafeRes.map((unsafeRes: any) => { const res = create(unsafeRes, GetTransactionRpcResult); if ('error' in res) { - throw new Error('failed to get transactions: ' + res.error.message); + throw new SolanaJSONRPCError(res.error, 'failed to get transactions'); } const result = res.result; if (!result) return result; @@ -3607,7 +3604,7 @@ export class Connection { const res = create(unsafeRes, GetConfirmedBlockRpcResult); if ('error' in res) { - throw new Error('failed to get confirmed block: ' + res.error.message); + throw new SolanaJSONRPCError(res.error, 'failed to get confirmed block'); } const result = res.result; @@ -3658,7 +3655,7 @@ export class Connection { const unsafeRes = await this._rpcRequest('getBlocks', args); const res = create(unsafeRes, jsonRpcResult(array(number()))); if ('error' in res) { - throw new Error('failed to get blocks: ' + res.error.message); + throw new SolanaJSONRPCError(res.error, 'failed to get blocks'); } return res.result; } @@ -3682,7 +3679,7 @@ export class Connection { const unsafeRes = await this._rpcRequest('getBlock', args); const res = create(unsafeRes, GetBlockSignaturesRpcResult); if ('error' in res) { - throw new Error('failed to get block: ' + res.error.message); + throw new SolanaJSONRPCError(res.error, 'failed to get block'); } const result = res.result; if (!result) { @@ -3712,7 +3709,7 @@ export class Connection { const unsafeRes = await this._rpcRequest('getConfirmedBlock', args); const res = create(unsafeRes, GetBlockSignaturesRpcResult); if ('error' in res) { - throw new Error('failed to get confirmed block: ' + res.error.message); + throw new SolanaJSONRPCError(res.error, 'failed to get confirmed block'); } const result = res.result; if (!result) { @@ -3734,7 +3731,7 @@ export class Connection { const unsafeRes = await this._rpcRequest('getConfirmedTransaction', args); const res = create(unsafeRes, GetTransactionRpcResult); if ('error' in res) { - throw new Error('failed to get transaction: ' + res.error.message); + throw new SolanaJSONRPCError(res.error, 'failed to get transaction'); } const result = res.result; @@ -3765,8 +3762,9 @@ export class Connection { const unsafeRes = await this._rpcRequest('getConfirmedTransaction', args); const res = create(unsafeRes, GetParsedTransactionRpcResult); if ('error' in res) { - throw new Error( - 'failed to get confirmed transaction: ' + res.error.message, + throw new SolanaJSONRPCError( + res.error, + 'failed to get confirmed transaction', ); } return res.result; @@ -3797,8 +3795,9 @@ export class Connection { const res = unsafeRes.map((unsafeRes: any) => { const res = create(unsafeRes, GetParsedTransactionRpcResult); if ('error' in res) { - throw new Error( - 'failed to get confirmed transactions: ' + res.error.message, + throw new SolanaJSONRPCError( + res.error, + 'failed to get confirmed transactions', ); } return res.result; @@ -3903,8 +3902,9 @@ export class Connection { ); const res = create(unsafeRes, GetConfirmedSignaturesForAddress2RpcResult); if ('error' in res) { - throw new Error( - 'failed to get confirmed signatures for address: ' + res.error.message, + throw new SolanaJSONRPCError( + res.error, + 'failed to get confirmed signatures for address', ); } return res.result; @@ -3932,8 +3932,9 @@ export class Connection { const unsafeRes = await this._rpcRequest('getSignaturesForAddress', args); const res = create(unsafeRes, GetSignaturesForAddressRpcResult); if ('error' in res) { - throw new Error( - 'failed to get signatures for address: ' + res.error.message, + throw new SolanaJSONRPCError( + res.error, + 'failed to get signatures for address', ); } return res.result; @@ -4005,8 +4006,9 @@ export class Connection { ]); const res = create(unsafeRes, RequestAirdropRpcResult); if ('error' in res) { - throw new Error( - 'airdrop to ' + to.toBase58() + ' failed: ' + res.error.message, + throw new SolanaJSONRPCError( + res.error, + `airdrop to ${to.toBase58()} failed`, ); } return res.result; diff --git a/web3.js/src/errors.ts b/web3.js/src/errors.ts index c1155137b39fa9..24ac09b6611f3c 100644 --- a/web3.js/src/errors.ts +++ b/web3.js/src/errors.ts @@ -7,3 +7,44 @@ export class SendTransactionError extends Error { this.logs = logs; } } + +// Keep in sync with client/src/rpc_custom_errors.rs +// Typescript `enums` thwart tree-shaking. See https://bargsten.org/jsts/enums/ +export const SolanaJSONRPCErrorCode = { + JSON_RPC_SERVER_ERROR_BLOCK_CLEANED_UP: -32001, + JSON_RPC_SERVER_ERROR_SEND_TRANSACTION_PREFLIGHT_FAILURE: -32002, + JSON_RPC_SERVER_ERROR_TRANSACTION_SIGNATURE_VERIFICATION_FAILURE: -32003, + JSON_RPC_SERVER_ERROR_BLOCK_NOT_AVAILABLE: -32004, + JSON_RPC_SERVER_ERROR_NODE_UNHEALTHY: -32005, + JSON_RPC_SERVER_ERROR_TRANSACTION_PRECOMPILE_VERIFICATION_FAILURE: -32006, + JSON_RPC_SERVER_ERROR_SLOT_SKIPPED: -32007, + JSON_RPC_SERVER_ERROR_NO_SNAPSHOT: -32008, + JSON_RPC_SERVER_ERROR_LONG_TERM_STORAGE_SLOT_SKIPPED: -32009, + JSON_RPC_SERVER_ERROR_KEY_EXCLUDED_FROM_SECONDARY_INDEX: -32010, + JSON_RPC_SERVER_ERROR_TRANSACTION_HISTORY_NOT_AVAILABLE: -32011, + JSON_RPC_SCAN_ERROR: -32012, + JSON_RPC_SERVER_ERROR_TRANSACTION_SIGNATURE_LEN_MISMATCH: -32013, + JSON_RPC_SERVER_ERROR_BLOCK_STATUS_NOT_AVAILABLE_YET: -32014, + JSON_RPC_SERVER_ERROR_UNSUPPORTED_TRANSACTION_VERSION: -32015, + JSON_RPC_SERVER_ERROR_MIN_CONTEXT_SLOT_NOT_REACHED: -32016, +} as const; +export type SolanaJSONRPCErrorCodeEnum = + typeof SolanaJSONRPCErrorCode[keyof typeof SolanaJSONRPCErrorCode]; + +export class SolanaJSONRPCError extends Error { + code: SolanaJSONRPCErrorCodeEnum | unknown; + data?: any; + constructor( + { + code, + message, + data, + }: Readonly<{code: unknown; message: string; data?: any}>, + customMessage?: string, + ) { + super(customMessage != null ? `${customMessage}: ${message}` : message); + this.code = code; + this.data = data; + this.name = 'SolanaJSONRPCError'; + } +}