Refactor provider model for better socket provider support.
This commit is contained in:
parent
a6faed5098
commit
e14cca31ba
@ -1,10 +1,11 @@
|
|||||||
import {
|
import {
|
||||||
defineProperties, FetchRequest, throwArgumentError
|
defineProperties, FetchRequest, throwArgumentError, throwError
|
||||||
} from "../utils/index.js";
|
} from "../utils/index.js";
|
||||||
|
|
||||||
import { showThrottleMessage } from "./community.js";
|
import { showThrottleMessage } from "./community.js";
|
||||||
import { Network } from "./network.js";
|
import { Network } from "./network.js";
|
||||||
import { JsonRpcProvider } from "./provider-jsonrpc.js";
|
import { JsonRpcProvider } from "./provider-jsonrpc.js";
|
||||||
|
import { WebSocketProvider } from "./provider-websocket.js";
|
||||||
|
|
||||||
import type { AbstractProvider } from "./abstract-provider.js";
|
import type { AbstractProvider } from "./abstract-provider.js";
|
||||||
import type { CommunityResourcable } from "./community.js";
|
import type { CommunityResourcable } from "./community.js";
|
||||||
@ -15,7 +16,7 @@ const defaultProjectId = "84842078b09946638c03157f83405213";
|
|||||||
|
|
||||||
function getHost(name: string): string {
|
function getHost(name: string): string {
|
||||||
switch(name) {
|
switch(name) {
|
||||||
case "homestead":
|
case "mainnet":
|
||||||
return "mainnet.infura.io";
|
return "mainnet.infura.io";
|
||||||
case "ropsten":
|
case "ropsten":
|
||||||
return "ropsten.infura.io";
|
return "ropsten.infura.io";
|
||||||
@ -42,12 +43,39 @@ function getHost(name: string): string {
|
|||||||
return throwArgumentError("unsupported network", "network", name);
|
return throwArgumentError("unsupported network", "network", name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class InfuraWebSocketProvider extends WebSocketProvider implements CommunityResourcable {
|
||||||
|
readonly projectId!: string;
|
||||||
|
readonly projectSecret!: null | string;
|
||||||
|
|
||||||
|
constructor(network?: Networkish, apiKey?: any) {
|
||||||
|
const provider = new InfuraProvider(network, apiKey);
|
||||||
|
|
||||||
|
const req = provider._getConnection();
|
||||||
|
if (req.credentials) {
|
||||||
|
throwError("INFURA WebSocket project secrets unsupported", "UNSUPPORTED_OPERATION", {
|
||||||
|
operation: "InfuraProvider.getWebSocketProvider()"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const url = req.url.replace(/^http/i, "ws").replace("/v3/", "/ws/v3/");
|
||||||
|
super(url, network);
|
||||||
|
|
||||||
|
defineProperties<InfuraWebSocketProvider>(this, {
|
||||||
|
projectId: provider.projectId,
|
||||||
|
projectSecret: provider.projectSecret
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
isCommunityResource(): boolean {
|
||||||
|
return (this.projectId === defaultProjectId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export class InfuraProvider extends JsonRpcProvider implements CommunityResourcable {
|
export class InfuraProvider extends JsonRpcProvider implements CommunityResourcable {
|
||||||
readonly projectId!: string;
|
readonly projectId!: string;
|
||||||
readonly projectSecret!: null | string;
|
readonly projectSecret!: null | string;
|
||||||
|
|
||||||
constructor(_network: Networkish = "homestead", projectId?: null | string, projectSecret?: null | string) {
|
constructor(_network: Networkish = "mainnet", projectId?: null | string, projectSecret?: null | string) {
|
||||||
const network = Network.from(_network);
|
const network = Network.from(_network);
|
||||||
if (projectId == null) { projectId = defaultProjectId; }
|
if (projectId == null) { projectId = defaultProjectId; }
|
||||||
if (projectSecret == null) { projectSecret = null; }
|
if (projectSecret == null) { projectSecret = null; }
|
||||||
@ -65,6 +93,14 @@ export class InfuraProvider extends JsonRpcProvider implements CommunityResourca
|
|||||||
return super._getProvider(chainId);
|
return super._getProvider(chainId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isCommunityResource(): boolean {
|
||||||
|
return (this.projectId === defaultProjectId);
|
||||||
|
}
|
||||||
|
|
||||||
|
static getWebSocketProvider(network?: Networkish, apiKey?: any): InfuraWebSocketProvider {
|
||||||
|
return new InfuraWebSocketProvider(network, apiKey);
|
||||||
|
}
|
||||||
|
|
||||||
static getRequest(network: Network, projectId?: null | string, projectSecret?: null | string): FetchRequest {
|
static getRequest(network: Network, projectId?: null | string, projectSecret?: null | string): FetchRequest {
|
||||||
if (projectId == null) { projectId = defaultProjectId; }
|
if (projectId == null) { projectId = defaultProjectId; }
|
||||||
if (projectSecret == null) { projectSecret = null; }
|
if (projectSecret == null) { projectSecret = null; }
|
||||||
@ -83,7 +119,4 @@ export class InfuraProvider extends JsonRpcProvider implements CommunityResourca
|
|||||||
return request;
|
return request;
|
||||||
}
|
}
|
||||||
|
|
||||||
isCommunityResource(): boolean {
|
|
||||||
return (this.projectId === defaultProjectId);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -355,86 +355,22 @@ export class JsonRpcApiProvider extends AbstractProvider {
|
|||||||
|
|
||||||
#options: Required<JsonRpcApiProviderOptions>;
|
#options: Required<JsonRpcApiProviderOptions>;
|
||||||
|
|
||||||
|
// The next ID to use for the JSON-RPC ID field
|
||||||
#nextId: number;
|
#nextId: number;
|
||||||
|
|
||||||
|
// Payloads are queued and triggered in batches using the drainTimer
|
||||||
#payloads: Array<Payload>;
|
#payloads: Array<Payload>;
|
||||||
|
|
||||||
#ready: boolean;
|
|
||||||
#starting: null | Promise<void>;
|
|
||||||
|
|
||||||
#drainTimer: null | NodeJS.Timer;
|
#drainTimer: null | NodeJS.Timer;
|
||||||
|
|
||||||
|
#notReady: null | {
|
||||||
|
promise: Promise<void>,
|
||||||
|
resolve: null | ((v: void) => void)
|
||||||
|
};
|
||||||
|
|
||||||
#network: null | Network;
|
#network: null | Network;
|
||||||
|
|
||||||
constructor(network?: Networkish, options?: JsonRpcApiProviderOptions) {
|
|
||||||
super(network);
|
|
||||||
|
|
||||||
this.#ready = false;
|
|
||||||
this.#starting = null;
|
|
||||||
|
|
||||||
this.#nextId = 1;
|
|
||||||
this.#options = Object.assign({ }, defaultOptions, options || { });
|
|
||||||
|
|
||||||
this.#payloads = [ ];
|
|
||||||
this.#drainTimer = null;
|
|
||||||
|
|
||||||
this.#network = null;
|
|
||||||
|
|
||||||
// This could be relaxed in the future to just check equivalent networks
|
|
||||||
const staticNetwork = this._getOption("staticNetwork");
|
|
||||||
if (staticNetwork) {
|
|
||||||
if (staticNetwork !== network) {
|
|
||||||
throwArgumentError("staticNetwork MUST match network object", "options", options);
|
|
||||||
}
|
|
||||||
this.#network = staticNetwork;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the value associated with the option %%key%%.
|
|
||||||
*
|
|
||||||
* Sub-classes can use this to inquire about configuration options.
|
|
||||||
*/
|
|
||||||
_getOption<K extends keyof JsonRpcApiProviderOptions>(key: K): JsonRpcApiProviderOptions[K] {
|
|
||||||
return this.#options[key];
|
|
||||||
}
|
|
||||||
|
|
||||||
get _network(): Network {
|
|
||||||
if (!this.#network) {
|
|
||||||
throwError("network is not available yet", "NETWORK_ERROR");
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.#network;
|
|
||||||
}
|
|
||||||
|
|
||||||
get ready(): boolean { return this.#ready; }
|
|
||||||
|
|
||||||
async _start(): Promise<void> {
|
|
||||||
if (this.#ready) { return; }
|
|
||||||
if (this.#starting) { return this.#starting; }
|
|
||||||
|
|
||||||
this.#starting = (async () => {
|
|
||||||
|
|
||||||
// Bootstrap the network
|
|
||||||
if (this.#network == null) {
|
|
||||||
try {
|
|
||||||
this.#network = await this._detectNetwork();
|
|
||||||
} catch (error) {
|
|
||||||
console.log("JsonRpcProvider failed to startup; retry in 1s");
|
|
||||||
await stall(1000);
|
|
||||||
this.#starting = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.#ready = true;
|
|
||||||
this.#starting = null;
|
|
||||||
|
|
||||||
// Start dispatching requests
|
|
||||||
this.#scheduleDrain();
|
|
||||||
})();
|
|
||||||
}
|
|
||||||
|
|
||||||
#scheduleDrain(): void {
|
#scheduleDrain(): void {
|
||||||
if (this.#drainTimer || !this.ready) { return; }
|
if (this.#drainTimer) { return; }
|
||||||
|
|
||||||
// If we aren't using batching, no hard in sending it immeidately
|
// If we aren't using batching, no hard in sending it immeidately
|
||||||
const stallTime = (this._getOption("batchMaxCount") === 1) ? 0: this._getOption("batchStallTime");
|
const stallTime = (this._getOption("batchMaxCount") === 1) ? 0: this._getOption("batchStallTime");
|
||||||
@ -503,34 +439,54 @@ export class JsonRpcApiProvider extends AbstractProvider {
|
|||||||
}, stallTime);
|
}, stallTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
constructor(network?: Networkish, options?: JsonRpcApiProviderOptions) {
|
||||||
* Requests the %%method%% with %%params%% via the JSON-RPC protocol
|
super(network);
|
||||||
* over the underlying channel. This can be used to call methods
|
|
||||||
* on the backend that do not have a high-level API within the Provider
|
|
||||||
* API.
|
|
||||||
*
|
|
||||||
* This method queues requests according to the batch constraints
|
|
||||||
* in the options, assigns the request a unique ID.
|
|
||||||
*
|
|
||||||
* **Do NOT override** this method in sub-classes; instead
|
|
||||||
* override [[_send]] or force the options values in the
|
|
||||||
* call to the constructor to modify this method's behavior.
|
|
||||||
*/
|
|
||||||
send(method: string, params: Array<any> | Record<string, any>): Promise<any> {
|
|
||||||
// @TODO: cache chainId?? purge on switch_networks
|
|
||||||
|
|
||||||
const id = this.#nextId++;
|
this.#nextId = 1;
|
||||||
const promise = new Promise((resolve, reject) => {
|
this.#options = Object.assign({ }, defaultOptions, options || { });
|
||||||
this.#payloads.push({
|
|
||||||
resolve, reject,
|
this.#payloads = [ ];
|
||||||
payload: { method, params, id, jsonrpc: "2.0" }
|
this.#drainTimer = null;
|
||||||
|
|
||||||
|
this.#network = null;
|
||||||
|
|
||||||
|
{
|
||||||
|
let resolve: null | ((value: void) => void) = null;
|
||||||
|
const promise = new Promise((_resolve: (value: void) => void) => {
|
||||||
|
resolve = _resolve;
|
||||||
});
|
});
|
||||||
});
|
this.#notReady = { promise, resolve };
|
||||||
|
}
|
||||||
|
|
||||||
// If there is not a pending drainTimer, set one
|
// This could be relaxed in the future to just check equivalent networks
|
||||||
this.#scheduleDrain();
|
const staticNetwork = this._getOption("staticNetwork");
|
||||||
|
if (staticNetwork) {
|
||||||
|
if (staticNetwork !== network) {
|
||||||
|
throwArgumentError("staticNetwork MUST match network object", "options", options);
|
||||||
|
}
|
||||||
|
this.#network = staticNetwork;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return <Promise<JsonRpcResult>>promise;
|
/**
|
||||||
|
* Returns the value associated with the option %%key%%.
|
||||||
|
*
|
||||||
|
* Sub-classes can use this to inquire about configuration options.
|
||||||
|
*/
|
||||||
|
_getOption<K extends keyof JsonRpcApiProviderOptions>(key: K): JsonRpcApiProviderOptions[K] {
|
||||||
|
return this.#options[key];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the [[Network]] this provider has committed to. On each call, the network
|
||||||
|
* is detected, and if it has changed, the call will reject.
|
||||||
|
*/
|
||||||
|
get _network(): Network {
|
||||||
|
if (!this.#network) {
|
||||||
|
throwError("network is not available yet", "NETWORK_ERROR");
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.#network;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -544,49 +500,46 @@ export class JsonRpcApiProvider extends AbstractProvider {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resolves to the [[Signer]] account for %%address%% managed by
|
* Resolves to the non-normalized value by performing %%req%%.
|
||||||
* the client.
|
|
||||||
*
|
*
|
||||||
* If the %%address%% is a number, it is used as an index in the
|
* Sub-classes may override this to modify behavior of actions,
|
||||||
* the accounts from [[listAccounts]].
|
* and should generally call ``super._perform`` as a fallback.
|
||||||
*
|
|
||||||
* This can only be used on clients which manage accounts (such as
|
|
||||||
* Geth with imported account or MetaMask).
|
|
||||||
*
|
|
||||||
* Throws if the account doesn't exist.
|
|
||||||
*/
|
*/
|
||||||
async getSigner(address: number | string = 0): Promise<JsonRpcSigner> {
|
async _perform(req: PerformActionRequest): Promise<any> {
|
||||||
|
// Legacy networks do not like the type field being passed along (which
|
||||||
const accountsPromise = this.send("eth_accounts", [ ]);
|
// is fair), so we delete type if it is 0 and a non-EIP-1559 network
|
||||||
|
if (req.method === "call" || req.method === "estimateGas") {
|
||||||
// Account index
|
let tx = req.transaction;
|
||||||
if (typeof(address) === "number") {
|
if (tx && tx.type != null && getBigInt(tx.type)) {
|
||||||
const accounts = <Array<string>>(await accountsPromise);
|
// If there are no EIP-1559 properties, it might be non-EIP-a559
|
||||||
if (address > accounts.length) { throw new Error("no such account"); }
|
if (tx.maxFeePerGas == null && tx.maxPriorityFeePerGas == null) {
|
||||||
return new JsonRpcSigner(this, accounts[address]);
|
const feeData = await this.getFeeData();
|
||||||
}
|
if (feeData.maxFeePerGas == null && feeData.maxPriorityFeePerGas == null) {
|
||||||
|
// Network doesn't know about EIP-1559 (and hence type)
|
||||||
const { accounts } = await resolveProperties({
|
req = Object.assign({ }, req, {
|
||||||
network: this.getNetwork(),
|
transaction: Object.assign({ }, tx, { type: undefined })
|
||||||
accounts: accountsPromise
|
});
|
||||||
});
|
}
|
||||||
|
}
|
||||||
// Account address
|
|
||||||
address = getAddress(address);
|
|
||||||
for (const account of accounts) {
|
|
||||||
if (getAddress(account) === account) {
|
|
||||||
return new JsonRpcSigner(this, account);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new Error("invalid account");
|
const request = this.getRpcRequest(req);
|
||||||
|
|
||||||
|
if (request != null) {
|
||||||
|
return await this.send(request.method, request.args);
|
||||||
|
}
|
||||||
|
|
||||||
|
return super._perform(req);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Sub-classes can override this; it detects the *actual* network that
|
/** Sub-classes may override this; it detects the *actual* network that
|
||||||
* we are **currently** connected to.
|
* we are **currently** connected to.
|
||||||
*
|
*
|
||||||
* Keep in mind that [[send]] may only be used once [[ready]].
|
* Keep in mind that [[send]] may only be used once [[ready]], otherwise the
|
||||||
|
* _send primitive must be used instead.
|
||||||
*/
|
*/
|
||||||
async _detectNetwork(): Promise<Network> {
|
async _detectNetwork(): Promise<Network> {
|
||||||
const network = this._getOption("staticNetwork");
|
const network = this._getOption("staticNetwork");
|
||||||
@ -604,7 +557,15 @@ export class JsonRpcApiProvider extends AbstractProvider {
|
|||||||
};
|
};
|
||||||
|
|
||||||
this.emit("debug", { action: "sendRpcPayload", payload });
|
this.emit("debug", { action: "sendRpcPayload", payload });
|
||||||
const result = (await this._send(payload))[0];
|
|
||||||
|
let result: JsonRpcResult | JsonRpcError;
|
||||||
|
try {
|
||||||
|
result = (await this._send(payload))[0];
|
||||||
|
} catch (error) {
|
||||||
|
this.emit("debug", { action: "receiveRpcError", error });
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
this.emit("debug", { action: "receiveRpcResult", result });
|
this.emit("debug", { action: "receiveRpcResult", result });
|
||||||
|
|
||||||
if ("result" in result) {
|
if ("result" in result) {
|
||||||
@ -614,10 +575,51 @@ export class JsonRpcApiProvider extends AbstractProvider {
|
|||||||
throw this.getRpcError(payload, result);
|
throw this.getRpcError(payload, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sub-classes **MUST** call this. Until [[_start]] has been called, no calls
|
||||||
|
* will be passed to [[_send]] from [[send]]. If it is overridden, then
|
||||||
|
* ``super._start()`` **MUST** be called.
|
||||||
|
*
|
||||||
|
* Calling it multiple times is safe and has no effect.
|
||||||
|
*/
|
||||||
|
_start(): void {
|
||||||
|
if (this.#notReady == null || this.#notReady.resolve == null) { return; }
|
||||||
|
|
||||||
|
this.#notReady.resolve();
|
||||||
|
this.#notReady = null;
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
|
||||||
|
// Bootstrap the network
|
||||||
|
while (this.#network == null) {
|
||||||
|
try {
|
||||||
|
this.#network = await this._detectNetwork();
|
||||||
|
} catch (error) {
|
||||||
|
console.log("JsonRpcProvider failed to startup; retry in 1s");
|
||||||
|
await stall(1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start dispatching requests
|
||||||
|
this.#scheduleDrain();
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolves once the [[_start]] has been called. This can be used in
|
||||||
|
* sub-classes to defer sending data until the connection has been
|
||||||
|
* established.
|
||||||
|
*/
|
||||||
|
async _waitUntilReady(): Promise<void> {
|
||||||
|
if (this.#notReady == null) { return; }
|
||||||
|
return await this.#notReady.promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return a Subscriber that will manage the %%sub%%.
|
* Return a Subscriber that will manage the %%sub%%.
|
||||||
*
|
*
|
||||||
* Sub-classes can override this to modify the behavior of
|
* Sub-classes may override this to modify the behavior of
|
||||||
* subscription management.
|
* subscription management.
|
||||||
*/
|
*/
|
||||||
_getSubscriber(sub: Subscription): Subscriber {
|
_getSubscriber(sub: Subscription): Subscriber {
|
||||||
@ -637,6 +639,11 @@ export class JsonRpcApiProvider extends AbstractProvider {
|
|||||||
return super._getSubscriber(sub);
|
return super._getSubscriber(sub);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true only if the [[_start]] has been called.
|
||||||
|
*/
|
||||||
|
get ready(): boolean { return this.#notReady == null; }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns %%tx%% as a normalized JSON-RPC transaction request,
|
* Returns %%tx%% as a normalized JSON-RPC transaction request,
|
||||||
* which has all values hexlified and any numeric values converted
|
* which has all values hexlified and any numeric values converted
|
||||||
@ -700,7 +707,7 @@ export class JsonRpcApiProvider extends AbstractProvider {
|
|||||||
args: [ getLowerCase(req.address), req.blockTag ]
|
args: [ getLowerCase(req.address), req.blockTag ]
|
||||||
};
|
};
|
||||||
|
|
||||||
case "getStorageAt":
|
case "getStorage":
|
||||||
return {
|
return {
|
||||||
method: "eth_getStorageAt",
|
method: "eth_getStorageAt",
|
||||||
args: [
|
args: [
|
||||||
@ -836,38 +843,74 @@ export class JsonRpcApiProvider extends AbstractProvider {
|
|||||||
return makeError("could not coalesce error", "UNKNOWN_ERROR", { error });
|
return makeError("could not coalesce error", "UNKNOWN_ERROR", { error });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resolves to the non-normalized value by performing %%req%%.
|
* Requests the %%method%% with %%params%% via the JSON-RPC protocol
|
||||||
|
* over the underlying channel. This can be used to call methods
|
||||||
|
* on the backend that do not have a high-level API within the Provider
|
||||||
|
* API.
|
||||||
*
|
*
|
||||||
* Sub-classes may override this to modify behavior of actions,
|
* This method queues requests according to the batch constraints
|
||||||
* and should generally call ``super._perform`` as a fallback.
|
* in the options, assigns the request a unique ID.
|
||||||
|
*
|
||||||
|
* **Do NOT override** this method in sub-classes; instead
|
||||||
|
* override [[_send]] or force the options values in the
|
||||||
|
* call to the constructor to modify this method's behavior.
|
||||||
*/
|
*/
|
||||||
async _perform(req: PerformActionRequest): Promise<any> {
|
send(method: string, params: Array<any> | Record<string, any>): Promise<any> {
|
||||||
// Legacy networks do not like the type field being passed along (which
|
// @TODO: cache chainId?? purge on switch_networks
|
||||||
// is fair), so we delete type if it is 0 and a non-EIP-1559 network
|
|
||||||
if (req.method === "call" || req.method === "estimateGas") {
|
const id = this.#nextId++;
|
||||||
let tx = req.transaction;
|
const promise = new Promise((resolve, reject) => {
|
||||||
if (tx && tx.type != null && getBigInt(tx.type)) {
|
this.#payloads.push({
|
||||||
// If there are no EIP-1559 properties, it might be non-EIP-a559
|
resolve, reject,
|
||||||
if (tx.maxFeePerGas == null && tx.maxPriorityFeePerGas == null) {
|
payload: { method, params, id, jsonrpc: "2.0" }
|
||||||
const feeData = await this.getFeeData();
|
});
|
||||||
if (feeData.maxFeePerGas == null && feeData.maxPriorityFeePerGas == null) {
|
});
|
||||||
// Network doesn't know about EIP-1559 (and hence type)
|
|
||||||
req = Object.assign({ }, req, {
|
// If there is not a pending drainTimer, set one
|
||||||
transaction: Object.assign({ }, tx, { type: undefined })
|
this.#scheduleDrain();
|
||||||
});
|
|
||||||
}
|
return <Promise<JsonRpcResult>>promise;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolves to the [[Signer]] account for %%address%% managed by
|
||||||
|
* the client.
|
||||||
|
*
|
||||||
|
* If the %%address%% is a number, it is used as an index in the
|
||||||
|
* the accounts from [[listAccounts]].
|
||||||
|
*
|
||||||
|
* This can only be used on clients which manage accounts (such as
|
||||||
|
* Geth with imported account or MetaMask).
|
||||||
|
*
|
||||||
|
* Throws if the account doesn't exist.
|
||||||
|
*/
|
||||||
|
async getSigner(address: number | string = 0): Promise<JsonRpcSigner> {
|
||||||
|
|
||||||
|
const accountsPromise = this.send("eth_accounts", [ ]);
|
||||||
|
|
||||||
|
// Account index
|
||||||
|
if (typeof(address) === "number") {
|
||||||
|
const accounts = <Array<string>>(await accountsPromise);
|
||||||
|
if (address > accounts.length) { throw new Error("no such account"); }
|
||||||
|
return new JsonRpcSigner(this, accounts[address]);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { accounts } = await resolveProperties({
|
||||||
|
network: this.getNetwork(),
|
||||||
|
accounts: accountsPromise
|
||||||
|
});
|
||||||
|
|
||||||
|
// Account address
|
||||||
|
address = getAddress(address);
|
||||||
|
for (const account of accounts) {
|
||||||
|
if (getAddress(account) === account) {
|
||||||
|
return new JsonRpcSigner(this, account);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const request = this.getRpcRequest(req);
|
throw new Error("invalid account");
|
||||||
|
|
||||||
if (request != null) {
|
|
||||||
return await this.send(request.method, request.args);
|
|
||||||
}
|
|
||||||
|
|
||||||
return super._perform(req);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -897,10 +940,14 @@ export class JsonRpcProvider extends JsonRpcApiProvider {
|
|||||||
this.#pollingInterval = 4000;
|
this.#pollingInterval = 4000;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_getConnection(): FetchRequest {
|
||||||
|
return this.#connect.clone();
|
||||||
|
}
|
||||||
|
|
||||||
async send(method: string, params: Array<any> | Record<string, any>): Promise<any> {
|
async send(method: string, params: Array<any> | Record<string, any>): Promise<any> {
|
||||||
// All requests are over HTTP, so we can just start handling requests
|
// All requests are over HTTP, so we can just start handling requests
|
||||||
// We do this here rather than the constructor so that we don't send any
|
// We do this here rather than the constructor so that we don't send any
|
||||||
// requests to the network until we absolutely have to.
|
// requests to the network (i.e. eth_chainId) until we absolutely have to.
|
||||||
await this._start();
|
await this._start();
|
||||||
|
|
||||||
return await super.send(method, params);
|
return await super.send(method, params);
|
||||||
@ -908,7 +955,7 @@ export class JsonRpcProvider extends JsonRpcApiProvider {
|
|||||||
|
|
||||||
async _send(payload: JsonRpcPayload | Array<JsonRpcPayload>): Promise<Array<JsonRpcResult>> {
|
async _send(payload: JsonRpcPayload | Array<JsonRpcPayload>): Promise<Array<JsonRpcResult>> {
|
||||||
// Configure a POST connection for the requested method
|
// Configure a POST connection for the requested method
|
||||||
const request = this.#connect.clone();
|
const request = this._getConnection();
|
||||||
request.body = JSON.stringify(payload);
|
request.body = JSON.stringify(payload);
|
||||||
|
|
||||||
const response = await request.send();
|
const response = await request.send();
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { UnmanagedSubscriber } from "./abstract-provider.js";
|
import { UnmanagedSubscriber } from "./abstract-provider.js";
|
||||||
import { assertArgument, makeError, throwError } from "../utils/index.js";
|
import { assertArgument, throwError } from "../utils/index.js";
|
||||||
import { JsonRpcApiProvider } from "./provider-jsonrpc.js";
|
import { JsonRpcApiProvider } from "./provider-jsonrpc.js";
|
||||||
|
|
||||||
import type { Subscriber, Subscription } from "./abstract-provider.js";
|
import type { Subscriber, Subscription } from "./abstract-provider.js";
|
||||||
@ -196,10 +196,16 @@ export class SocketProvider extends JsonRpcApiProvider {
|
|||||||
assertArgument(!Array.isArray(payload), "WebSocket does not support batch send", "payload", payload);
|
assertArgument(!Array.isArray(payload), "WebSocket does not support batch send", "payload", payload);
|
||||||
|
|
||||||
// @TODO: stringify payloads here and store to prevent mutations
|
// @TODO: stringify payloads here and store to prevent mutations
|
||||||
|
|
||||||
|
// Prepare a promise to respond to
|
||||||
const promise = new Promise((resolve, reject) => {
|
const promise = new Promise((resolve, reject) => {
|
||||||
this.#callbacks.set(payload.id, { payload, resolve, reject });
|
this.#callbacks.set(payload.id, { payload, resolve, reject });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Wait until the socket is connected before writing to it
|
||||||
|
await this._waitUntilReady();
|
||||||
|
|
||||||
|
// Write the request to the socket
|
||||||
await this._write(JSON.stringify(payload));
|
await this._write(JSON.stringify(payload));
|
||||||
|
|
||||||
return <Array<JsonRpcResult | JsonRpcError>>[ await promise ];
|
return <Array<JsonRpcResult | JsonRpcError>>[ await promise ];
|
||||||
@ -232,6 +238,9 @@ export class SocketProvider extends JsonRpcApiProvider {
|
|||||||
}
|
}
|
||||||
this.#callbacks.delete(result.id);
|
this.#callbacks.delete(result.id);
|
||||||
|
|
||||||
|
callback.resolve(result);
|
||||||
|
|
||||||
|
/*
|
||||||
if ("error" in result) {
|
if ("error" in result) {
|
||||||
const { message, code, data } = result.error;
|
const { message, code, data } = result.error;
|
||||||
const error = makeError(message || "unkonwn error", "SERVER_ERROR", {
|
const error = makeError(message || "unkonwn error", "SERVER_ERROR", {
|
||||||
@ -242,7 +251,7 @@ export class SocketProvider extends JsonRpcApiProvider {
|
|||||||
} else {
|
} else {
|
||||||
callback.resolve(result.result);
|
callback.resolve(result.result);
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
} else if (result.method === "eth_subscription") {
|
} else if (result.method === "eth_subscription") {
|
||||||
const filterId = result.params.subscription;
|
const filterId = result.params.subscription;
|
||||||
const subscriber = this.#subs.get(filterId);
|
const subscriber = this.#subs.get(filterId);
|
||||||
|
@ -18,10 +18,11 @@ export interface WebSocketLike {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class WebSocketProvider extends SocketProvider {
|
export class WebSocketProvider extends SocketProvider {
|
||||||
url!: string;
|
#websocket: null | WebSocketLike;
|
||||||
|
get websocket(): WebSocketLike {
|
||||||
#websocket: WebSocketLike;
|
if (this.#websocket == null) { throw new Error("websocket closed"); }
|
||||||
get websocket(): WebSocketLike { return this.#websocket; }
|
return this.#websocket;
|
||||||
|
}
|
||||||
|
|
||||||
constructor(url: string | WebSocketLike, network?: Networkish) {
|
constructor(url: string | WebSocketLike, network?: Networkish) {
|
||||||
super(network);
|
super(network);
|
||||||
@ -48,4 +49,10 @@ export class WebSocketProvider extends SocketProvider {
|
|||||||
async _write(message: string): Promise<void> {
|
async _write(message: string): Promise<void> {
|
||||||
this.websocket.send(message);
|
this.websocket.send(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async destroy(): Promise<void> {
|
||||||
|
if (this.#websocket == null) { return; }
|
||||||
|
this.#websocket.close();
|
||||||
|
this.#websocket = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user