Skip to content
This repository was archived by the owner on Mar 5, 2025. It is now read-only.

Commit 21b7649

Browse files
Test with injected external providers (#5652)
* fix: sending tx with injected provider (#5651) Co-authored-by: Marin Petrunic <[email protected]> * adding a test for using `ganache` provider * enable the jsonrpc `id` to optionally be incremented starting from a number (Inspired by: #5373 (comment) and needed as a work around for blockchainsllc/in3#46) * test with `in3` as a provider & skip `in3` test if it takes too long * increase integration test timeout at web3 package * add a test for using `hardhat` provider * improve how legacy providers, such as hardhat, is used * implement `isPromise` that works in the browsers * refactor external providers tests * update CHANGELOG.md
1 parent 30bffb6 commit 21b7649

23 files changed

+1407
-127
lines changed

.gitignore

+3
Original file line numberDiff line numberDiff line change
@@ -36,3 +36,6 @@ tsconfig.tsbuildinfo
3636
package-lock.json
3737

3838
tmp/
39+
40+
# Incubed (in3) nodelist
41+
packages/web3/.in3/

CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -902,6 +902,8 @@ should use 4.0.1-alpha.0 for testing.
902902
#### web3-utils
903903

904904
- Export a new function `uuidV4` that generates a random v4 Uuid (#5373).
905+
- Enable passing a starting number, to increment based on it, for the Json Rpc Request `id` (#5652).
906+
- Export a new function `isPromise` that checks if an object is a promise (#5652).
905907

906908
#### web3-eth-contract
907909

packages/web3-core/src/utils.ts

+1-2
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,7 @@ export const isEIP1193Provider = <API extends Web3APISpec>(
4141
): provider is EIP1193Provider<API> =>
4242
typeof provider !== 'string' &&
4343
'request' in provider &&
44-
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
45-
(provider.request as any)[Symbol.toStringTag] === 'AsyncFunction';
44+
provider.request.constructor.name === 'AsyncFunction';
4645

4746
export const isLegacySendProvider = <API extends Web3APISpec>(
4847
provider: SupportedProviders<API>,

packages/web3-core/src/web3_request_manager.ts

+43-21
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ along with web3.js. If not, see <http://www.gnu.org/licenses/>.
1616
*/
1717

1818
import { Socket } from 'net';
19+
1920
import {
2021
ContractExecutionError,
2122
InvalidResponseError,
@@ -42,7 +43,7 @@ import {
4243
Web3BaseProvider,
4344
Web3BaseProviderConstructor,
4445
} from 'web3-types';
45-
import { isNullish, jsonRpc } from 'web3-utils';
46+
import { isNullish, isPromise, jsonRpc } from 'web3-utils';
4647
import {
4748
isEIP1193Provider,
4849
isLegacyRequestProvider,
@@ -200,34 +201,55 @@ export class Web3RequestManager<
200201
);
201202
}
202203

203-
// TODO: This should be deprecated and removed.
204+
// TODO: This could be deprecated and removed.
204205
if (isLegacyRequestProvider(provider)) {
205-
return new Promise<JsonRpcResponse<ResponseType>>((resolve, reject): void => {
206-
provider.request<ResponseType>(payload, (err, response) => {
207-
if (err) {
208-
return reject(
209-
this._processJsonRpcResponse(
210-
payload,
211-
err as unknown as JsonRpcResponse<ResponseType>,
212-
{
213-
legacy: true,
214-
error: true,
215-
},
216-
),
217-
);
218-
}
219-
220-
return resolve(
206+
return new Promise<JsonRpcResponse<ResponseType>>((resolve, reject) => {
207+
const rejectWithError = (err: unknown) =>
208+
reject(
209+
this._processJsonRpcResponse(
210+
payload,
211+
err as JsonRpcResponse<ResponseType>,
212+
{
213+
legacy: true,
214+
error: true,
215+
},
216+
),
217+
);
218+
const resolveWithResponse = (response: JsonRpcResponse<ResponseType>) =>
219+
resolve(
221220
this._processJsonRpcResponse(payload, response, {
222221
legacy: true,
223222
error: false,
224223
}),
225224
);
226-
});
225+
const result = provider.request<ResponseType>(
226+
payload,
227+
// a callback that is expected to be called after getting the response:
228+
(err, response) => {
229+
if (err) {
230+
return rejectWithError(err);
231+
}
232+
233+
return resolveWithResponse(response);
234+
},
235+
);
236+
// Some providers, that follow a previous drafted version of EIP1193, has a `request` function
237+
// that is not defined as `async`, but it returns a promise.
238+
// Such providers would not be picked with if(isEIP1193Provider(provider)) above
239+
// because the `request` function was not defined with `async` and so the function definition is not `AsyncFunction`.
240+
// Like this provider: https://github.dev/NomicFoundation/hardhat/blob/62bea2600785595ba36f2105564076cf5cdf0fd8/packages/hardhat-core/src/internal/core/providers/backwards-compatibility.ts#L19
241+
// So check if the returned result is a Promise, and resolve with it accordingly.
242+
// Note: in this case we expect the callback provided above to never be called.
243+
if (isPromise(result)) {
244+
const responsePromise = result as unknown as Promise<
245+
JsonRpcResponse<ResponseType>
246+
>;
247+
responsePromise.then(resolveWithResponse).catch(rejectWithError);
248+
}
227249
});
228250
}
229251

230-
// TODO: This should be deprecated and removed.
252+
// TODO: This could be deprecated and removed.
231253
if (isLegacySendProvider(provider)) {
232254
return new Promise<JsonRpcResponse<ResponseType>>((resolve, reject): void => {
233255
provider.send<ResponseType>(payload, (err, response) => {
@@ -261,7 +283,7 @@ export class Web3RequestManager<
261283
});
262284
}
263285

264-
// TODO: This should be deprecated and removed.
286+
// TODO: This could be deprecated and removed.
265287
if (isLegacySendAsyncProvider(provider)) {
266288
return provider
267289
.sendAsync<ResponseType>(payload)

packages/web3-eth/src/utils/reject_if_block_timeout.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -161,12 +161,12 @@ export async function rejectIfBlockTimeout(
161161
web3Context: Web3Context<EthExecutionAPI>,
162162
transactionHash?: Bytes,
163163
): Promise<[Promise<never>, ResourceCleaner]> {
164-
const provider: Web3BaseProvider = web3Context.requestManager.provider as Web3BaseProvider;
164+
const { provider } = web3Context.requestManager;
165165
let callingRes: [Promise<never>, ResourceCleaner];
166166
const starterBlockNumber = await getBlockNumber(web3Context, NUMBER_DATA_FORMAT);
167167
// TODO: once https://github.com/web3/web3.js/issues/5521 is implemented, remove checking for `enableExperimentalFeatures.useSubscriptionWhenCheckingBlockTimeout`
168168
if (
169-
provider.supportsSubscriptions() &&
169+
(provider as Web3BaseProvider).supportsSubscriptions?.() &&
170170
web3Context.enableExperimentalFeatures.useSubscriptionWhenCheckingBlockTimeout
171171
) {
172172
callingRes = await resolveBySubscription(web3Context, starterBlockNumber, transactionHash);

packages/web3-eth/test/integration/eth.test.ts

+3-5
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import HttpProvider from 'web3-providers-http';
1919
// eslint-disable-next-line import/no-extraneous-dependencies
2020
import { Contract } from 'web3-eth-contract';
2121
// eslint-disable-next-line import/no-extraneous-dependencies
22-
import { SupportedProviders, Web3EthExecutionAPI } from 'web3-types';
22+
import { SupportedProviders } from 'web3-types';
2323
// eslint-disable-next-line import/no-extraneous-dependencies
2424
import IpcProvider from 'web3-providers-ipc';
2525
import { Web3Eth } from '../../src';
@@ -68,12 +68,10 @@ describe('eth', () => {
6868

6969
const deoloyedContract = await contract.deploy(deployOptions).send(sendOptions);
7070
const { provider } = web3Eth;
71-
web3Eth.setProvider(
72-
deoloyedContract.provider as SupportedProviders<Web3EthExecutionAPI>,
73-
);
71+
web3Eth.setProvider(deoloyedContract.provider as SupportedProviders);
7472

7573
expect(web3Eth.provider).toBe(deoloyedContract.provider);
76-
web3Eth.setProvider(provider as SupportedProviders<Web3EthExecutionAPI>);
74+
web3Eth.setProvider(provider as SupportedProviders);
7775
});
7876
it('providers', () => {
7977
const res = web3Eth.providers;

packages/web3-types/src/web3_base_provider.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import {
2828
JsonRpcSubscriptionResult,
2929
} from './json_rpc_types';
3030
import { Web3APISpec, Web3APIMethod, Web3APIReturnType, Web3APIPayload } from './web3_api_types';
31+
import { Web3EthExecutionAPI } from './apis/web3_eth_execution_api';
3132

3233
const symbol = Symbol.for('web3/base-provider');
3334

@@ -166,7 +167,7 @@ export abstract class Web3BaseProvider<API extends Web3APISpec = EthExecutionAPI
166167
public abstract reset(): void;
167168
}
168169

169-
export type SupportedProviders<API extends Web3APISpec> =
170+
export type SupportedProviders<API extends Web3APISpec = Web3EthExecutionAPI> =
170171
| EIP1193Provider<API>
171172
| Web3BaseProvider<API>
172173
| LegacyRequestProvider

packages/web3-utils/CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
4646
### Added
4747

4848
- Export a new function `uuidV4` that generates a random v4 Uuid (#5373).
49+
- Enable passing a starting number, to increment based on it, for the Json Rpc Request `id` (#5652).
50+
- Export a new function `isPromise` that checks if an object is a promise (#5652).
4951

5052
### Fixed
5153

packages/web3-utils/src/json_rpc.ts

+26-6
Original file line numberDiff line numberDiff line change
@@ -87,14 +87,34 @@ export const isBatchResponse = <Result = unknown, Error = unknown>(
8787
): response is JsonRpcBatchResponse<Result, Error> =>
8888
Array.isArray(response) && response.length > 1 && isValidResponse(response);
8989

90+
// internal optional variable to increment and use for the jsonrpc `id`
91+
let requestIdSeed: number | undefined;
92+
93+
/**
94+
* Optionally use to make the jsonrpc `id` start from a specific number.
95+
* Without calling this function, the `id` will be filled with a Uuid.
96+
* But after this being called with a number, the `id` will be a number staring from the provided `start` variable.
97+
* However, if `undefined` was passed to this function, the `id` will be a Uuid again.
98+
* @param start - a number to start incrementing from.
99+
* Or `undefined` to use a new Uuid (this is the default behavior)
100+
*/
101+
export const setRequestIdStart = (start: number | undefined) => {
102+
requestIdSeed = start;
103+
};
104+
90105
export const toPayload = <ParamType = unknown[]>(
91106
request: JsonRpcOptionalRequest<ParamType>,
92-
): JsonRpcPayload<ParamType> => ({
93-
jsonrpc: request.jsonrpc ?? '2.0',
94-
id: request.id ?? uuidV4(),
95-
method: request.method,
96-
params: request.params ?? undefined,
97-
});
107+
): JsonRpcPayload<ParamType> => {
108+
if (typeof requestIdSeed !== 'undefined') {
109+
requestIdSeed += 1;
110+
}
111+
return {
112+
jsonrpc: request.jsonrpc ?? '2.0',
113+
id: request.id ?? requestIdSeed ?? uuidV4(),
114+
method: request.method,
115+
params: request.params ?? undefined,
116+
};
117+
};
98118

99119
export const toBatchPayload = (requests: JsonRpcOptionalRequest<unknown>[]): JsonRpcBatchRequest =>
100120
requests.map(request => toPayload<unknown>(request)) as JsonRpcBatchRequest;

packages/web3-utils/src/promise_helpers.ts

+13
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,19 @@ along with web3.js. If not, see <http://www.gnu.org/licenses/>.
1717

1818
import { isNullish } from 'web3-validator';
1919

20+
/**
21+
* An alternative to the node function `isPromise` that exists in `util/types` because it is not available on the browser.
22+
* @param object to check if it is a `Promise`
23+
* @returns `true` if it is an `object` or a `function` that has a `then` function. And returns `false` otherwise.
24+
*/
25+
export function isPromise(object: unknown): boolean {
26+
return (
27+
(typeof object === 'object' || typeof object === 'function') &&
28+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
29+
(typeof (object as any).then as unknown) === 'function'
30+
);
31+
}
32+
2033
export type AsyncFunction<T, K = unknown> = (...args: K[]) => Promise<T>;
2134

2235
export function waitWithTimeout<T>(

packages/web3/.eslintignore

+1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
dist
2+
hardhat.config.js
23
jest.config.js
34
webpack.config.js
45
.eslintrc.js

packages/web3/hardhat.config.js

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
// A dummy file to prevent `hardhat` from throwing
2+
// "HardhatError: HH1: You are not inside a Hardhat project."
3+
// Note: Hardhat is just used inside the tests to ensure its compatibility.

packages/web3/package.json

+3
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,9 @@
5050
"eslint-config-prettier": "^8.5.0",
5151
"eslint-config-web3-base": "0.1.0",
5252
"eslint-plugin-import": "^2.26.0",
53+
"ganache": "^7.5.0",
54+
"hardhat": "^2.12.2",
55+
"in3": "^3.3.3",
5356
"jest": "^28.1.3",
5457
"jest-extended": "^3.0.1",
5558
"prettier": "^2.7.1",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/*
2+
This file is part of web3.js.
3+
4+
web3.js is free software: you can redistribute it and/or modify
5+
it under the terms of the GNU Lesser General Public License as published by
6+
the Free Software Foundation, either version 3 of the License, or
7+
(at your option) any later version.
8+
9+
web3.js is distributed in the hope that it will be useful,
10+
but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
GNU Lesser General Public License for more details.
13+
14+
You should have received a copy of the GNU Lesser General Public License
15+
along with web3.js. If not, see <http://www.gnu.org/licenses/>.
16+
*/
17+
18+
import Web3 from '../../../src/index';
19+
20+
describe('compatibility with extremely simple external provider', () => {
21+
it('should accept a simple instance that is compatible with EIP1193', () => {
22+
interface RequestArguments {
23+
readonly method: string;
24+
readonly params?: readonly unknown[] | object;
25+
}
26+
27+
class Provider {
28+
// eslint-disable-next-line class-methods-use-this, @typescript-eslint/require-await, @typescript-eslint/no-unused-vars
29+
public async request(_: RequestArguments): Promise<unknown> {
30+
return undefined as unknown;
31+
}
32+
}
33+
34+
const testProvider = new Provider();
35+
const { provider } = new Web3(testProvider);
36+
expect(provider).toBeDefined();
37+
});
38+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/*
2+
This file is part of web3.js.
3+
4+
web3.js is free software: you can redistribute it and/or modify
5+
it under the terms of the GNU Lesser General Public License as published by
6+
the Free Software Foundation, either version 3 of the License, or
7+
(at your option) any later version.
8+
9+
web3.js is distributed in the hope that it will be useful,
10+
but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
GNU Lesser General Public License for more details.
13+
14+
You should have received a copy of the GNU Lesser General Public License
15+
along with web3.js. If not, see <http://www.gnu.org/licenses/>.
16+
*/
17+
18+
// eslint-disable-next-line import/no-extraneous-dependencies
19+
import ganache from 'ganache';
20+
21+
import { performBasicRpcCalls } from './helper';
22+
23+
import { getSystemTestMnemonic } from '../../shared_fixtures/system_tests_utils';
24+
25+
describe('compatibility with `ganache` provider', () => {
26+
it('should initialize Web3, get accounts & block number and send a transaction', async () => {
27+
const { provider } = ganache.server({
28+
wallet: {
29+
mnemonic: getSystemTestMnemonic(),
30+
},
31+
});
32+
33+
await performBasicRpcCalls(provider);
34+
});
35+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*
2+
This file is part of web3.js.
3+
4+
web3.js is free software: you can redistribute it and/or modify
5+
it under the terms of the GNU Lesser General Public License as published by
6+
the Free Software Foundation, either version 3 of the License, or
7+
(at your option) any later version.
8+
9+
web3.js is distributed in the hope that it will be useful,
10+
but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
GNU Lesser General Public License for more details.
13+
14+
You should have received a copy of the GNU Lesser General Public License
15+
along with web3.js. If not, see <http://www.gnu.org/licenses/>.
16+
*/
17+
18+
// eslint-disable-next-line import/no-extraneous-dependencies
19+
import hardhat from 'hardhat';
20+
21+
import { performBasicRpcCalls } from './helper';
22+
23+
describe('compatibility with `hardhat` provider', () => {
24+
it('should initialize Web3, get accounts & block number and send a transaction', async () => {
25+
// use the hardhat provider for web3.js
26+
await performBasicRpcCalls(hardhat.network.provider);
27+
});
28+
});

0 commit comments

Comments
 (0)