diff --git a/packages/providers/src.ts/alchemy-provider.ts b/packages/providers/src.ts/alchemy-provider.ts index 49c12ca29..6b4b66f18 100644 --- a/packages/providers/src.ts/alchemy-provider.ts +++ b/packages/providers/src.ts/alchemy-provider.ts @@ -18,8 +18,11 @@ const defaultApiKey = "_gg7wSSi0KMBsdKnGVfHDueq6xMB9EkC" export class AlchemyProvider extends UrlJsonRpcProvider { readonly apiKey: string; - static getApiKey(apiKey: string): string { + static getApiKey(apiKey: any): any { if (apiKey == null) { return defaultApiKey; } + if (apiKey && typeof(apiKey) !== "string") { + logger.throwArgumentError("invalid apiKey", "apiKey", apiKey); + } return apiKey; } diff --git a/packages/providers/src.ts/cloudflare-provider.ts b/packages/providers/src.ts/cloudflare-provider.ts index cf03316c4..8e163eed4 100644 --- a/packages/providers/src.ts/cloudflare-provider.ts +++ b/packages/providers/src.ts/cloudflare-provider.ts @@ -9,11 +9,14 @@ const logger = new Logger(version); export class CloudflareProvider extends UrlJsonRpcProvider { - static getUrl(network: Network, apiKey?: string): string { + static getApiKey(apiKey: any): any { if (apiKey != null) { logger.throwArgumentError("apiKey not supported for cloudflare", "apiKey", apiKey); } + return null; + } + static getUrl(network: Network, apiKey?: any): string { let host = null; switch (network.name) { case "homestead": diff --git a/packages/providers/src.ts/infura-provider.ts b/packages/providers/src.ts/infura-provider.ts index f86e9891d..795eeb1a5 100644 --- a/packages/providers/src.ts/infura-provider.ts +++ b/packages/providers/src.ts/infura-provider.ts @@ -1,6 +1,7 @@ "use strict"; import { Network } from "@ethersproject/networks"; +import { ConnectionInfo } from "@ethersproject/web"; import { Logger } from "@ethersproject/logger"; import { version } from "./_version"; @@ -12,15 +13,40 @@ import { UrlJsonRpcProvider } from "./url-json-rpc-provider"; const defaultProjectId = "84842078b09946638c03157f83405213" export class InfuraProvider extends UrlJsonRpcProvider { - get projectId(): string { return this.apiKey; } + readonly projectId: string; + readonly projectSecret: string; - static getApiKey(apiKey: string): string { - if (apiKey == null) { return defaultProjectId; } - return apiKey; + static getApiKey(apiKey: any): any { + const apiKeyObj: { apiKey: string, projectId: string, projectSecret: string } = { + apiKey: null, + projectId: defaultProjectId, + projectSecret: null + }; + + if (typeof(apiKey) === "string") { + apiKeyObj.projectId = apiKey; + + } else if (apiKey.projectSecret != null) { + if (typeof(apiKey.projectId) !== "string") { + logger.throwArgumentError("projectSecret requires a projectId", "projectId", apiKey.projectId); + } + if (typeof(apiKey.projectSecret) !== "string") { + logger.throwArgumentError("invalid projectSecret", "projectSecret", "[REDACTED]"); + } + apiKeyObj.projectId = apiKey.projectId; + apiKeyObj.projectSecret = apiKey.projectSecret; + + } else if (apiKey.projectId) { + apiKeyObj.projectId = apiKey.projectId; + } + + apiKeyObj.apiKey = apiKeyObj.projectId; + + return apiKeyObj; } - static getUrl(network: Network, apiKey: string): string { - let host = null; + static getUrl(network: Network, apiKey: any): string | ConnectionInfo { + let host: string = null; switch(network.name) { case "homestead": host = "mainnet.infura.io"; @@ -44,6 +70,15 @@ export class InfuraProvider extends UrlJsonRpcProvider { }); } - return "https:/" + "/" + host + "/v3/" + apiKey; + const connection: ConnectionInfo = { + url: ("https:/" + "/" + host + "/v3/" + apiKey.projectId) + }; + + if (apiKey.projectSecret != null) { + connection.user = ""; + connection.password = apiKey.projectSecret + } + + return connection; } } diff --git a/packages/providers/src.ts/json-rpc-provider.ts b/packages/providers/src.ts/json-rpc-provider.ts index c806708d4..2321756a3 100644 --- a/packages/providers/src.ts/json-rpc-provider.ts +++ b/packages/providers/src.ts/json-rpc-provider.ts @@ -266,11 +266,11 @@ export class JsonRpcProvider extends BaseProvider { if (!url) { url = "http:/" + "/localhost:8545"; } if (typeof(url) === "string") { - this.connection = { + this.connection = Object.freeze({ url: url - }; + }); } else { - this.connection = url; + this.connection = Object.freeze(shallowCopy(url)); } this._nextId = 42; diff --git a/packages/providers/src.ts/nodesmith-provider.ts b/packages/providers/src.ts/nodesmith-provider.ts index 274659f20..d05a545b3 100644 --- a/packages/providers/src.ts/nodesmith-provider.ts +++ b/packages/providers/src.ts/nodesmith-provider.ts @@ -12,11 +12,14 @@ const defaultApiKey = "ETHERS_JS_SHARED"; export class NodesmithProvider extends UrlJsonRpcProvider { - static getApiKey(apiKey: string): string { + static getApiKey(apiKey: any): any { + if (apiKey && typeof(apiKey) !== "string") { + logger.throwArgumentError("invalid apiKey", "apiKey", apiKey); + } return apiKey || defaultApiKey; } - static getUrl(network: Network, apiKey?: string): string { + static getUrl(network: Network, apiKey?: any): string { let host = null; switch (network.name) { case "homestead": diff --git a/packages/providers/src.ts/url-json-rpc-provider.ts b/packages/providers/src.ts/url-json-rpc-provider.ts index b8b805c30..83272cbf8 100644 --- a/packages/providers/src.ts/url-json-rpc-provider.ts +++ b/packages/providers/src.ts/url-json-rpc-provider.ts @@ -2,6 +2,7 @@ import { Network, Networkish } from "@ethersproject/networks"; import { defineReadOnly, getStatic } from "@ethersproject/properties"; +import { ConnectionInfo } from "@ethersproject/web"; import { Logger } from "@ethersproject/logger"; import { version } from "./_version"; @@ -9,21 +10,29 @@ const logger = new Logger(version); import { JsonRpcProvider, JsonRpcSigner } from "./json-rpc-provider"; +type getUrlFunc = (network: Network, apiKey: string) => string | ConnectionInfo; + export abstract class UrlJsonRpcProvider extends JsonRpcProvider { readonly apiKey: string; - constructor(network?: Networkish, apiKey?: string) { + constructor(network?: Networkish, apiKey?: any) { logger.checkAbstract(new.target, UrlJsonRpcProvider); // Normalize the Network and API Key network = getStatic<(network: Networkish) => Network>(new.target, "getNetwork")(network); apiKey = getStatic<(apiKey: string) => string>(new.target, "getApiKey")(apiKey); - const url = getStatic<(network: Network, apiKey: string) => string>(new.target, "getUrl")(network, apiKey); + const connection = getStatic(new.target, "getUrl")(network, apiKey); - super(url, network); + super(connection, network); - defineReadOnly(this, "apiKey", apiKey); + if (typeof(apiKey) === "string") { + defineReadOnly(this, "apiKey", apiKey); + } else if (apiKey != null) { + Object.keys(apiKey).forEach((key) => { + defineReadOnly(this, key, apiKey[key]); + }); + } } _startPending(): void { @@ -31,29 +40,26 @@ export abstract class UrlJsonRpcProvider extends JsonRpcProvider { } getSigner(address?: string): JsonRpcSigner { - logger.throwError( + return logger.throwError( "API provider does not support signing", Logger.errors.UNSUPPORTED_OPERATION, { operation: "getSigner" } ); - return null; } listAccounts(): Promise> { return Promise.resolve([]); } -/* - static getNetwork(network?: Networkish): Network { - return getNetwork((network == null) ? "homestead": network); - } -*/ + // Return a defaultApiKey if null, otherwise validate the API key - static getApiKey(apiKey: string): string { + static getApiKey(apiKey: any): any { return apiKey; } - // Returns the url for the given network and API key - static getUrl(network: Network, apiKey: string): string { + // Returns the url or connection for the given network and API key. The + // API key will have been sanitized by the getApiKey first, so any validation + // or transformations can be done there. + static getUrl(network: Network, apiKey: any): string | ConnectionInfo { return logger.throwError("not implemented; sub-classes must override getUrl", Logger.errors.NOT_IMPLEMENTED, { operation: "getUrl" });