From 014004d9402d7fd8c15553792cfb7a8a84ed327a Mon Sep 17 00:00:00 2001 From: Richard Moore Date: Tue, 9 Apr 2024 20:36:16 -0400 Subject: [PATCH] Added Chainstack provider (#2741). --- docs.wrm/links/projects.txt | 1 + src.ts/_tests/create-provider.ts | 10 ++- src.ts/ethers.ts | 5 +- src.ts/providers/default-provider.ts | 8 ++ src.ts/providers/index.ts | 1 + src.ts/providers/provider-chainstack.ts | 113 ++++++++++++++++++++++++ 6 files changed, 135 insertions(+), 3 deletions(-) create mode 100644 src.ts/providers/provider-chainstack.ts diff --git a/docs.wrm/links/projects.txt b/docs.wrm/links/projects.txt index d084e0655..1219593d0 100644 --- a/docs.wrm/links/projects.txt +++ b/docs.wrm/links/projects.txt @@ -1,5 +1,6 @@ link-alchemy [Alchemy](https://alchemy.com/?a=ethers) link-ankr [Ankr](https://www.ankr.com) +link-chainstack [Chainstack](https://chainstack.com) link-cloudflare [Cloudflare](https://developers.cloudflare.com/distributed-web/ethereum-gateway/) link-ens [ENS](https://ens.domains/) link-ethereum [Ethereum](https://ethereumorg) diff --git a/src.ts/_tests/create-provider.ts b/src.ts/_tests/create-provider.ts index a4aef5b9c..2d792547a 100644 --- a/src.ts/_tests/create-provider.ts +++ b/src.ts/_tests/create-provider.ts @@ -2,6 +2,7 @@ import { AlchemyProvider, // AnkrProvider, // CloudflareProvider, + ChainstackProvider, EtherscanProvider, InfuraProvider, // PocketProvider, @@ -48,6 +49,13 @@ const ProviderCreators: Array = [ } }, */ + { + name: "ChainstackProvider", + networks: [ "default", "mainnet", "arbitrum", "bnb", "matic" ], + create: function(network: string) { + return new ChainstackProvider(network); + } + }, { name: "EtherscanProvider", networks: ethNetworks, @@ -143,7 +151,7 @@ export function getProvider(provider: string, network: string): null | AbstractP export function checkProvider(provider: string, network: string): boolean { const creator = getCreator(provider); - return (creator != null); + return (creator != null && creator.networks.indexOf(network) >= 0); } export function connect(network: string): AbstractProvider { diff --git a/src.ts/ethers.ts b/src.ts/ethers.ts index 1f5c54d8c..92b7de665 100644 --- a/src.ts/ethers.ts +++ b/src.ts/ethers.ts @@ -69,8 +69,9 @@ export { BrowserProvider, - AlchemyProvider, AnkrProvider, CloudflareProvider, EtherscanProvider, - InfuraProvider, InfuraWebSocketProvider, PocketProvider, QuickNodeProvider, + AlchemyProvider, AnkrProvider, ChainstackProvider, CloudflareProvider, + EtherscanProvider, InfuraProvider, InfuraWebSocketProvider, PocketProvider, + QuickNodeProvider, IpcSocketProvider, SocketProvider, WebSocketProvider, diff --git a/src.ts/providers/default-provider.ts b/src.ts/providers/default-provider.ts index f20195df2..d24eccea1 100644 --- a/src.ts/providers/default-provider.ts +++ b/src.ts/providers/default-provider.ts @@ -3,6 +3,7 @@ import { assert } from "../utils/index.js"; import { AnkrProvider } from "./provider-ankr.js"; import { AlchemyProvider } from "./provider-alchemy.js"; +import { ChainstackProvider } from "./provider-chainstack.js"; import { CloudflareProvider } from "./provider-cloudflare.js"; import { EtherscanProvider } from "./provider-etherscan.js"; import { InfuraProvider } from "./provider-infura.js"; @@ -48,6 +49,7 @@ const Testnets = "goerli kovan sepolia classicKotti optimism-goerli arbitrum-goe * - ``"alchemy"`` * - ``"ankr"`` * - ``"cloudflare"`` + * - ``"chainstack"`` * - ``"etherscan"`` * - ``"infura"`` * - ``"publicPolygon"`` @@ -117,6 +119,12 @@ export function getDefaultProvider(network?: string | Networkish | WebSocketLike } catch (error) { } } + if (allowService("chainstack")) { + try { + providers.push(new ChainstackProvider(network, options.chainstack)); + } catch (error) { } + } + if (allowService("cloudflare")) { try { providers.push(new CloudflareProvider(network)); diff --git a/src.ts/providers/index.ts b/src.ts/providers/index.ts index 00e6d79eb..c0a33dc8a 100644 --- a/src.ts/providers/index.ts +++ b/src.ts/providers/index.ts @@ -62,6 +62,7 @@ export { BrowserProvider } from "./provider-browser.js"; export { AlchemyProvider } from "./provider-alchemy.js"; export { AnkrProvider } from "./provider-ankr.js"; export { CloudflareProvider } from "./provider-cloudflare.js"; +export { ChainstackProvider } from "./provider-chainstack.js"; export { EtherscanProvider, EtherscanPlugin } from "./provider-etherscan.js"; export { InfuraProvider, InfuraWebSocketProvider } from "./provider-infura.js"; export { PocketProvider } from "./provider-pocket.js"; diff --git a/src.ts/providers/provider-chainstack.ts b/src.ts/providers/provider-chainstack.ts new file mode 100644 index 000000000..0daf8b791 --- /dev/null +++ b/src.ts/providers/provider-chainstack.ts @@ -0,0 +1,113 @@ +/** + * [[link-chainstack]] provides a third-party service for connecting to + * various blockchains over JSON-RPC. + * + * **Supported Networks** + * + * - Ethereum Mainnet (``mainnet``) + * - Arbitrum (``arbitrum``) + * - BNB Smart Chain Mainnet (``bnb``) + * - Polygon (``matic``) + * + * @_subsection: api/providers/thirdparty:Chainstack [providers-chainstack] + */ +import { + defineProperties, FetchRequest, assertArgument +} from "../utils/index.js"; + +import { showThrottleMessage } from "./community.js"; +import { Network } from "./network.js"; +import { JsonRpcProvider } from "./provider-jsonrpc.js"; + +import type { AbstractProvider } from "./abstract-provider.js"; +import type { CommunityResourcable } from "./community.js"; +import type { Networkish } from "./network.js"; + + +function getApiKey(name: string): string { + switch (name) { + case "mainnet": return "39f1d67cedf8b7831010a665328c9197"; + case "arbitrum": return "0550c209db33c3abf4cc927e1e18cea1" + case "bnb": return "98b5a77e531614387366f6fc5da097f8"; + case "matic": return "cd9d4d70377471aa7c142ec4a4205249"; + } + + assertArgument(false, "unsupported network", "network", name); +} + +function getHost(name: string): string { + switch(name) { + case "mainnet": + return "ethereum-mainnet.core.chainstack.com"; + case "arbitrum": + return "arbitrum-mainnet.core.chainstack.com"; + case "bnb": + return "bsc-mainnet.core.chainstack.com"; + case "matic": + return "polygon-mainnet.core.chainstack.com"; + } + + assertArgument(false, "unsupported network", "network", name); +} + +/** + * The **ChainstackProvider** connects to the [[link-chainstack]] + * JSON-RPC end-points. + * + * By default, a highly-throttled API key is used, which is + * appropriate for quick prototypes and simple scripts. To + * gain access to an increased rate-limit, it is highly + * recommended to [sign up here](link-chainstack). + */ +export class ChainstackProvider extends JsonRpcProvider implements CommunityResourcable { + /** + * The API key for the Chainstack connection. + */ + readonly apiKey!: string; + + /** + * Creates a new **ChainstackProvider**. + */ + constructor(_network?: Networkish, apiKey?: null | string) { + if (_network == null) { _network = "mainnet"; } + const network = Network.from(_network); + + if (apiKey == null) { apiKey = getApiKey(network.name); } + + const request = ChainstackProvider.getRequest(network, apiKey); + super(request, network, { staticNetwork: network }); + + defineProperties(this, { apiKey }); + } + + _getProvider(chainId: number): AbstractProvider { + try { + return new ChainstackProvider(chainId, this.apiKey); + } catch (error) { } + return super._getProvider(chainId); + } + + isCommunityResource(): boolean { + return (this.apiKey === getApiKey(this._network.name)); + } + + /** + * Returns a prepared request for connecting to %%network%% + * with %%apiKey%% and %%projectSecret%%. + */ + static getRequest(network: Network, apiKey?: null | string): FetchRequest { + if (apiKey == null) { apiKey = getApiKey(network.name); } + + const request = new FetchRequest(`https:/\/${ getHost(network.name) }/${ apiKey }`); + request.allowGzip = true; + + if (apiKey === getApiKey(network.name)) { + request.retryFunc = async (request, response, attempt) => { + showThrottleMessage("ChainstackProvider"); + return true; + }; + } + + return request; + } +}