Skip to content
This repository was archived by the owner on Mar 5, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,6 @@ tsconfig.tsbuildinfo
package-lock.json

tmp/

# Incubed (in3) nodelist
packages/web3/.in3/
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -902,6 +902,8 @@ should use 4.0.1-alpha.0 for testing.
#### web3-utils

- Export a new function `uuidV4` that generates a random v4 Uuid (#5373).
- Enable passing a starting number, to increment based on it, for the Json Rpc Request `id` (#5309).
- Export a new function `isPromise` that generates a random v4 Uuid (#5309).

### Fixed

Expand Down
3 changes: 1 addition & 2 deletions packages/web3-core/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,7 @@ export const isEIP1193Provider = <API extends Web3APISpec>(
): provider is EIP1193Provider<API> =>
typeof provider !== 'string' &&
'request' in provider &&
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
(provider.request as any)[Symbol.toStringTag] === 'AsyncFunction';
provider.request.constructor.name === 'AsyncFunction';

export const isLegacySendProvider = <API extends Web3APISpec>(
provider: SupportedProviders<API>,
Expand Down
64 changes: 43 additions & 21 deletions packages/web3-core/src/web3_request_manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ along with web3.js. If not, see <http://www.gnu.org/licenses/>.
*/

import { Socket } from 'net';

import {
ContractExecutionError,
InvalidResponseError,
Expand All @@ -42,7 +43,7 @@ import {
Web3BaseProvider,
Web3BaseProviderConstructor,
} from 'web3-types';
import { isNullish, jsonRpc } from 'web3-utils';
import { isNullish, isPromise, jsonRpc } from 'web3-utils';
import {
isEIP1193Provider,
isLegacyRequestProvider,
Expand Down Expand Up @@ -200,34 +201,55 @@ export class Web3RequestManager<
);
}

// TODO: This should be deprecated and removed.
// TODO: This could be deprecated and removed.
if (isLegacyRequestProvider(provider)) {
return new Promise<JsonRpcResponse<ResponseType>>((resolve, reject): void => {
provider.request<ResponseType>(payload, (err, response) => {
if (err) {
return reject(
this._processJsonRpcResponse(
payload,
err as unknown as JsonRpcResponse<ResponseType>,
{
legacy: true,
error: true,
},
),
);
}

return resolve(
return new Promise<JsonRpcResponse<ResponseType>>((resolve, reject) => {
const rejectWithError = (err: unknown) =>
reject(
this._processJsonRpcResponse(
payload,
err as JsonRpcResponse<ResponseType>,
{
legacy: true,
error: true,
},
),
);
const resolveWithResponse = (response: JsonRpcResponse<ResponseType>) =>
resolve(
this._processJsonRpcResponse(payload, response, {
legacy: true,
error: false,
}),
);
});
const result = provider.request<ResponseType>(
payload,
// a callback that is expected to be called after getting the response:
(err, response) => {
if (err) {
return rejectWithError(err);
}

return resolveWithResponse(response);
},
);
// Some providers, that follow a previous drafted version of EIP1193, has a `request` function
// that is not defined as `async`, but it returns a promise.
// Such providers would not be picked with if(isEIP1193Provider(provider)) above
// because the `request` function was not defined with `async` and so the function definition is not `AsyncFunction`.
// Like this provider: https://github.dev/NomicFoundation/hardhat/blob/62bea2600785595ba36f2105564076cf5cdf0fd8/packages/hardhat-core/src/internal/core/providers/backwards-compatibility.ts#L19
// So check if the returned result is a Promise, and resolve with it accordingly.
// Note: in this case we expect the callback provided above to never be called.
if (isPromise(result)) {
const responsePromise = result as unknown as Promise<
JsonRpcResponse<ResponseType>
>;
responsePromise.then(resolveWithResponse).catch(rejectWithError);
}
});
}

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

// TODO: This should be deprecated and removed.
// TODO: This could be deprecated and removed.
if (isLegacySendAsyncProvider(provider)) {
return provider
.sendAsync<ResponseType>(payload)
Expand Down
4 changes: 2 additions & 2 deletions packages/web3-eth/src/utils/reject_if_block_timeout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,12 +161,12 @@ export async function rejectIfBlockTimeout(
web3Context: Web3Context<EthExecutionAPI>,
transactionHash?: Bytes,
): Promise<[Promise<never>, ResourceCleaner]> {
const provider: Web3BaseProvider = web3Context.requestManager.provider as Web3BaseProvider;
const { provider } = web3Context.requestManager;
let callingRes: [Promise<never>, ResourceCleaner];
const starterBlockNumber = await getBlockNumber(web3Context, NUMBER_DATA_FORMAT);
// TODO: once https://github.com/web3/web3.js/issues/5521 is implemented, remove checking for `enableExperimentalFeatures.useSubscriptionWhenCheckingBlockTimeout`
if (
provider.supportsSubscriptions() &&
(provider as Web3BaseProvider).supportsSubscriptions?.() &&
web3Context.enableExperimentalFeatures.useSubscriptionWhenCheckingBlockTimeout
) {
callingRes = await resolveBySubscription(web3Context, starterBlockNumber, transactionHash);
Expand Down
2 changes: 2 additions & 0 deletions packages/web3-utils/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added

- Export a new function `uuidV4` that generates a random v4 Uuid (#5373).
- Enable passing a starting number, to increment based on it, for the Json Rpc Request `id` (#5309).
- Export a new function `isPromise` that generates a random v4 Uuid (#5309).

### Fixed

Expand Down
32 changes: 26 additions & 6 deletions packages/web3-utils/src/json_rpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,14 +87,34 @@ export const isBatchResponse = <Result = unknown, Error = unknown>(
): response is JsonRpcBatchResponse<Result, Error> =>
Array.isArray(response) && response.length > 1 && isValidResponse(response);

// internal optional variable to increment and use for the jsonrpc `id`
let requestIdSeed: number | undefined;

/**
* Optionally use to make the jsonrpc `id` start from a specific number.
* Without calling this function, the `id` will be filled with a Uuid.
* But after this being called with a number, the `id` will be a number staring from the provided `start` variable.
* However, if `undefined` was passed to this function, the `id` will be a Uuid again.
* @param start - a number to start incrementing from.
* Or `undefined` to use a new Uuid (this is the default behavior)
*/
export const setRequestIdStart = (start: number | undefined) => {
requestIdSeed = start;
};

export const toPayload = <ParamType = unknown[]>(
request: JsonRpcOptionalRequest<ParamType>,
): JsonRpcPayload<ParamType> => ({
jsonrpc: request.jsonrpc ?? '2.0',
id: request.id ?? uuidV4(),
method: request.method,
params: request.params ?? undefined,
});
): JsonRpcPayload<ParamType> => {
if (typeof requestIdSeed !== 'undefined') {
requestIdSeed += 1;
}
return {
jsonrpc: request.jsonrpc ?? '2.0',
id: request.id ?? requestIdSeed ?? uuidV4(),
method: request.method,
params: request.params ?? undefined,
};
};

export const toBatchPayload = (requests: JsonRpcOptionalRequest<unknown>[]): JsonRpcBatchRequest =>
requests.map(request => toPayload<unknown>(request)) as JsonRpcBatchRequest;
Expand Down
13 changes: 13 additions & 0 deletions packages/web3-utils/src/promise_helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,19 @@ along with web3.js. If not, see <http://www.gnu.org/licenses/>.

import { isNullish } from 'web3-validator';

/**
* An alternative to the node function `isPromise` that exists in `util/types` because it is not available on the browser.
* @param object to check if it is a `Promise`
* @returns `true` if it is an `object` or a `function` that has a `then` function. And returns `false` otherwise.
*/
export function isPromise(object: unknown): boolean {
return (
(typeof object === 'object' || typeof object === 'function') &&
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
(typeof (object as any).then as unknown) === 'function'
);
}

export type AsyncFunction<T, K = unknown> = (...args: K[]) => Promise<T>;

export function waitWithTimeout<T>(
Expand Down
1 change: 1 addition & 0 deletions packages/web3/.eslintignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
dist
hardhat.config.js
jest.config.js
webpack.config.js
.eslintrc.js
3 changes: 3 additions & 0 deletions packages/web3/hardhat.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// A dummy file to prevent `hardhat` from throwing
// "HardhatError: HH1: You are not inside a Hardhat project."
// Note: Hardhat is just used inside the tests to ensure its compatibility.
3 changes: 3 additions & 0 deletions packages/web3/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@
"eslint-config-prettier": "^8.5.0",
"eslint-config-web3-base": "0.1.0",
"eslint-plugin-import": "^2.26.0",
"ganache": "^7.5.0",
"hardhat": "^2.12.2",
"in3": "^3.3.3",
"jest": "^28.1.3",
"jest-extended": "^3.0.1",
"prettier": "^2.7.1",
Expand Down
122 changes: 122 additions & 0 deletions packages/web3/test/integration/external-providers.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
/*
This file is part of web3.js.

web3.js is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

web3.js is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License
along with web3.js. If not, see <http://www.gnu.org/licenses/>.
*/

// eslint-disable-next-line import/no-extraneous-dependencies
import ganache from 'ganache';
// eslint-disable-next-line import/no-extraneous-dependencies
import hardhat from 'hardhat';
import { waitWithTimeout, setRequestIdStart } from 'web3-utils';
// eslint-disable-next-line import/no-extraneous-dependencies
import In3Client from 'in3';

import Web3 from '../../src/index';

import { getSystemTestMnemonic } from '../shared_fixtures/system_tests_utils';

describe('compatibility with external providers', () => {
it('should accept a simple EIP1193 provider', () => {
interface RequestArguments {
readonly method: string;
readonly params?: readonly unknown[] | object;
}

class Provider {
// eslint-disable-next-line class-methods-use-this, @typescript-eslint/require-await, @typescript-eslint/no-unused-vars
public async request(_: RequestArguments): Promise<unknown> {
return undefined as unknown;
}
}

const testProvider = new Provider();
const { provider } = new Web3(testProvider);
expect(provider).toBeDefined();
});

it('should accept a `ganache` provider', async () => {
const { provider } = ganache.server({
wallet: {
mnemonic: getSystemTestMnemonic(),
},
});
const web3 = new Web3(provider);

const accounts = await web3.eth.getAccounts();

const tx = web3.eth.sendTransaction({
to: accounts[1],
from: accounts[0],
value: '1',
});

await expect(tx).resolves.not.toThrow();
});

it('should accept an `in3` provider', async () => {
// use the In3Client as Http-Provider for web3.js
// Note: it could takes long time to get something from `in3` because of its decentralized nature.
const web3 = new Web3(
new In3Client({
proof: 'none',
signatureCount: 0,
requestCount: 1,
chainId: 'mainnet',
}).createWeb3Provider(),
);

// TODO: remove the next line after this issue is closed: https://github.com/blockchainsllc/in3/issues/46
setRequestIdStart(0);

// get the last block number
const blockNumber = await waitWithTimeout(web3.eth.getBlockNumber(), 25000);

if (typeof blockNumber === 'undefined') {
console.warn(
'It took too long for in3 provider to get a block. The test of in3 will be skipped',
);
return;
}
expect(typeof blockNumber).toBe('bigint');
});

it('should accept a `hardhat` provider', async () => {
// use the hardhat provider for web3.js
const web3 = new Web3(hardhat.network.provider);

const accounts = await web3.eth.getAccounts();

expect(accounts).toBeDefined();
expect(accounts.length).toBeGreaterThan(0);

// get the last block number
const blockNumber0 = await web3.eth.getBlockNumber();
expect(typeof blockNumber0).toBe('bigint');

const tx = web3.eth.sendTransaction({
to: accounts[1],
from: accounts[0],
value: '1',
});
await expect(tx).resolves.not.toThrow();

// get the last block number
const blockNumber1 = await web3.eth.getBlockNumber();
expect(typeof blockNumber1).toBe('bigint');

// After sending a transaction, the blocknumber is supposed to be greater than or equal the block number before sending the transaction
expect(blockNumber1).toBeGreaterThanOrEqual(blockNumber0);
});
});
2 changes: 1 addition & 1 deletion packages/web3/test/integration/setup.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,6 @@ along with web3.js. If not, see <http://www.gnu.org/licenses/>.
// eslint-disable-next-line @typescript-eslint/no-require-imports
require('../config/setup');

const jestTimeout = 15000;
const jestTimeout = 30000; // Sometimes `in3` takes long time because of its decentralized nature.

jest.setTimeout(jestTimeout);
Loading