Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Strong type support for Eth API #4513

Merged
merged 22 commits into from
Nov 15, 2021
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
9b190e1
:sparkles: Add eth execution API types
nazarhussain Oct 29, 2021
afecf27
:art: Update base provider to use eth api types
nazarhussain Oct 29, 2021
9b5572f
:sparkles: Add json-rpc helpers
nazarhussain Oct 29, 2021
8cfcfe5
:art: Update exports for web3-common
nazarhussain Oct 29, 2021
0251498
:art: Update API types
nazarhussain Oct 30, 2021
440b34f
:art: Update http-provider types
nazarhussain Oct 30, 2021
94d9106
:art: Update web3-core types
nazarhussain Oct 30, 2021
7bd84bb
Apply suggestions from code review
nazarhussain Nov 1, 2021
40606dc
:sparkles: Add common types to utils
nazarhussain Nov 2, 2021
7900dfb
:art: Updated the types as per feedback
nazarhussain Nov 2, 2021
67ef6a1
Merge branch '4.x' into nh/eth-api-types
nazarhussain Nov 2, 2021
e8b36ee
Merge branch '4.x' into nh/eth-api-types
nazarhussain Nov 9, 2021
0ea2be8
:art: Update the code with feedback
nazarhussain Nov 9, 2021
5cfd63b
:art: Fix ReceiptInfo object
nazarhussain Nov 9, 2021
01ec03d
:art: Fix eth_submitWork spec
nazarhussain Nov 9, 2021
5012993
:art: Update eth_estimateGas spec to make all properties of tx optional
nazarhussain Nov 9, 2021
4d2ed7a
:art: Update transaction spec
nazarhussain Nov 9, 2021
26035ae
Apply suggestions from code review
nazarhussain Nov 9, 2021
cf5f781
Merge branch '4.x' into nh/eth-api-types
nazarhussain Nov 10, 2021
ff228ae
Merge branch '4.x' into nh/eth-api-types
nazarhussain Nov 11, 2021
e2b3c3c
Update packages/web3-common/src/eth_execution_api.ts
nazarhussain Nov 12, 2021
2dbcc1e
Merge branch '4.x' into nh/eth-api-types
nazarhussain Nov 15, 2021
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
298 changes: 298 additions & 0 deletions packages/web3-common/src/eth_execution_api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,298 @@
import {
Address,
HexString,
HexString256Bytes,
HexString8Bytes,
HexString32Bytes,
HexStringBytes,
HexStringSingleByte,
Uint,
Uint256,
} from 'web3-utils';

// The types are generated manually by referring to following doc
// https://github.com/ethereum/execution-apis

export interface AccessListEntry {
readonly address?: Address;
readonly storageKeys?: HexString32Bytes[];
}
export type AccessList = AccessListEntry[];
export type TransactionHash = HexString;
export type Uncles = HexString32Bytes[];
export type BlockTag = 'earliest' | 'latest' | 'pending';
export type BlockNumberOrTag = Uint | BlockTag;

export interface TransactionCall {
readonly from?: Address;
readonly to: Address;
readonly gas?: Uint;
readonly gasPrice?: Uint;
readonly value?: Uint;
readonly data?: HexString;
}

export interface BaseTransaction {
readonly to?: Address | null;
readonly type: HexStringSingleByte;
readonly nonce: Uint;
readonly gas: Uint;
spacesailor24 marked this conversation as resolved.
Show resolved Hide resolved
readonly value: Uint;
readonly input: HexStringBytes;
nazarhussain marked this conversation as resolved.
Show resolved Hide resolved
}

export interface Transaction1559Unsigned extends BaseTransaction {
readonly maxFeePerGas: Uint;
nazarhussain marked this conversation as resolved.
Show resolved Hide resolved
readonly maxPriorityFeePerGas: Uint;
readonly accessList: AccessList;
}

export interface Transaction1559Signed extends Transaction1559Unsigned {
readonly yParity: Uint;
readonly r: Uint;
readonly s: Uint;
}

export interface Transaction2930Unsigned extends BaseTransaction {
readonly gasPrice: Uint;
spacesailor24 marked this conversation as resolved.
Show resolved Hide resolved
readonly accessList: AccessList;
nazarhussain marked this conversation as resolved.
Show resolved Hide resolved
}

export interface Transaction2930Signed extends Transaction2930Unsigned {
readonly yParity: Uint;
readonly r: Uint;
readonly s: Uint;
}

export interface TransactionLegacyUnsigned extends BaseTransaction {
readonly gasPrice: Uint;
spacesailor24 marked this conversation as resolved.
Show resolved Hide resolved
}

export interface TransactionLegacySigned extends TransactionLegacyUnsigned {
readonly v: Uint;
readonly r: Uint;
readonly s: Uint;
}

// https://github.com/ethereum/execution-apis/blob/main/src/schemas/transaction.json#L178
export type TransactionUnsigned =
spacesailor24 marked this conversation as resolved.
Show resolved Hide resolved
| Transaction1559Unsigned
| Transaction2930Unsigned
| TransactionLegacyUnsigned;

// https://github.com/ethereum/execution-apis/blob/main/src/schemas/transaction.json#L262
export type TransactionSigned =
| Transaction1559Signed
| Transaction2930Signed
| TransactionLegacySigned;

// https://github.com/ethereum/execution-apis/blob/main/src/schemas/transaction.json#L269
export type TransactionInfo = TransactionSigned & {
readonly blockHash: HexString32Bytes | null;
readonly blockNumber: Uint | null;
readonly from: Address;
readonly hash: HexString32Bytes;
readonly transactionIndex: Uint | null;
};

// https://github.com/ethereum/execution-apis/blob/main/src/schemas/transaction.json#L24
export type TransactionWithSender = TransactionUnsigned & { from: Address };
spacesailor24 marked this conversation as resolved.
Show resolved Hide resolved

// https://github.com/ethereum/execution-apis/blob/main/src/schemas/block.json#L2
export interface Block {
nazarhussain marked this conversation as resolved.
Show resolved Hide resolved
readonly parentHash: HexString32Bytes;
readonly sha3Uncles: HexString32Bytes;
readonly miner: HexString;
readonly stateRoot: HexString32Bytes;
readonly transactionsRoot: HexString32Bytes;
readonly receiptsRoot: HexString32Bytes;
readonly logsBloom: HexString256Bytes | null;
readonly difficulty?: Uint;
readonly number: Uint | null;
readonly gasLimit: Uint;
readonly gasUsed: Uint;
readonly timestamp: Uint;
readonly extraData: HexStringBytes;
readonly mixHash: HexString32Bytes;
readonly nonce: HexStringBytes | null;
nazarhussain marked this conversation as resolved.
Show resolved Hide resolved
readonly totalDifficulty: Uint;
readonly baseFeePerGas?: Uint;
readonly size: Uint;
readonly transactions: TransactionHash[] | TransactionSigned[];
readonly uncles: Uncles;
readonly hash: HexString32Bytes | null;
}

// https://github.com/ethereum/execution-apis/blob/main/src/schemas/filter.json#L59
export type Topic = HexString256Bytes;

// https://github.com/ethereum/execution-apis/blob/main/src/schemas/receipt.json#L2
export interface Log {
readonly removed?: boolean;
readonly logIndex?: Uint | null;
readonly transactionIndex?: Uint | null;
readonly transactionHash?: HexString32Bytes | null;
readonly blockHash?: HexString32Bytes | null;
readonly blockNumber?: Uint | null;
readonly address?: Address;
readonly data?: HexStringBytes;
readonly topics?: Topic[];
}

// https://github.com/ethereum/execution-apis/blob/main/src/schemas/receipt.json#L44
export interface ReceiptInfo {
readonly transactionHash: HexString32Bytes;
readonly transactionIndex: HexString32Bytes;
readonly blockHash: HexString32Bytes;
readonly blockNumber: Uint;
readonly from: Address;
readonly to: Address;
readonly cumulativeGasUsed: Uint;
readonly gasUsed: Uint;
readonly contractAddress: Address | null;
readonly logs: Log[];
readonly logsBloom: HexString256Bytes;
readonly root: HexString32Bytes;
readonly status: '0x1' | '0x0';
}

// https://github.com/ethereum/execution-apis/blob/main/src/schemas/client.json#L2
export type SyncingStatus =
| { startingBlock: Uint; currentBlock: Uint; highestBlock: Uint }
| boolean;

// https://github.com/ethereum/execution-apis/blob/main/src/eth/fee_market.json#L53
export interface FeeHistoryResult {
readonly oldestBlock: Uint;
readonly baseFeePerGas: Uint;
readonly reward: number[][];
}

// https://github.com/ethereum/execution-apis/blob/main/src/schemas/filter.json#L28
export interface Filter {
readonly fromBlock?: BlockNumberOrTag;
nazarhussain marked this conversation as resolved.
Show resolved Hide resolved
readonly toBlock?: BlockNumberOrTag;
readonly address?: Address | Address[];
readonly topics?: (Topic | Topic[] | null)[];
}

// https://github.com/ethereum/execution-apis/blob/main/src/schemas/filter.json#L2
export type FilterResults = HexString32Bytes[] | Log[];

export interface CompileResult {
readonly code: HexStringBytes;
readonly info: {
readonly source: string;
readonly language: string;
readonly languageVersion: string;
readonly compilerVersion: string;
readonly abiDefinition: Record<string, unknown>[];
readonly userDoc: {
readonly methods: Record<string, unknown>;
};
readonly developerDoc: {
readonly methods: Record<string, unknown>;
};
};
}

/* eslint-disable camelcase */
export type EthExecutionAPI = {
// https://github.com/ethereum/execution-apis/blob/main/src/eth/block.json
eth_getBlockByHash: (blockHash: HexString32Bytes, hydrated: boolean) => Block;
eth_getBlockByNumber: (blockNumber: BlockNumberOrTag, hydrated: boolean) => Block;
eth_getBlockTransactionCountByHash: (blockHash: HexString32Bytes) => Uint;
eth_getBlockTransactionCountByNumber: (blockNumber: BlockNumberOrTag) => Uint;
eth_getUncleCountByBlockHash: (blockHash: HexString32Bytes) => Uint;
eth_getUncleCountByBlockNumber: (blockNumber: BlockNumberOrTag) => Uint;
eth_getUncleByBlockHashAndIndex: (blockHash: HexString32Bytes, uncleIndex: Uint) => Block;
eth_getUncleByBlockNumberAndIndex: (blockNumber: BlockNumberOrTag, uncleIndex: Uint) => Block;

// https://github.com/ethereum/execution-apis/blob/main/src/eth/transaction.json
eth_getTransactionByHash: (transactionHash: HexString32Bytes) => TransactionInfo | null;
eth_getTransactionByBlockHashAndIndex: (
blockHash: HexString32Bytes,
transactionIndex: Uint,
) => TransactionInfo | null;
eth_getTransactionByBlockNumberAndIndex: (
blockNumber: BlockNumberOrTag,
transactionIndex: Uint,
) => TransactionInfo | null;
eth_getTransactionReceipt: (transactionHash: HexString32Bytes) => ReceiptInfo | null;

// https://github.com/ethereum/execution-apis/blob/main/src/eth/client.json
eth_protocolVersion: () => string;
eth_syncing: () => SyncingStatus;
eth_coinbase: () => Address;
eth_accounts: () => Address[];
eth_blockNumber: () => Uint;

// https://github.com/ethereum/execution-apis/blob/main/src/eth/execute.json
eth_call: (transaction: TransactionCall, blockNumber: BlockNumberOrTag) => HexStringBytes;
eth_estimateGas: (
transaction: Partial<TransactionWithSender>,
blockNumber: BlockNumberOrTag,
) => Uint;

// https://github.com/ethereum/execution-apis/blob/main/src/eth/fee_market.json
eth_gasPrice: () => Uint;
eth_feeHistory: (
blockCount: Uint,
newestBlock: BlockNumberOrTag,
rewardPercentiles: number[],
) => FeeHistoryResult;

// https://github.com/ethereum/execution-apis/blob/main/src/eth/filter.json
eth_newFilter: (filter: Filter) => Uint;
eth_newBlockFilter: () => Uint;
eth_newPendingTransactionFilter: () => Uint;
eth_uninstallFilter: (filterIdentifier: Uint) => boolean;
eth_getFilterChanges: (filterIdentifier: Uint) => FilterResults;
eth_getFilterLogs: (filterIdentifier: Uint) => FilterResults;
eth_getLogs: (filter: Filter) => FilterResults;

// https://github.com/ethereum/execution-apis/blob/main/src/eth/mining.json
eth_mining: () => boolean;
eth_hashrate: () => Uint;
eth_getWork: () => [HexString32Bytes, HexString32Bytes, HexString32Bytes];
eth_submitWork: (
nonce: HexString8Bytes,
seedHash: HexString32Bytes,
difficulty: HexString32Bytes,
) => boolean;
eth_submitHashrate: (hashRate: HexString32Bytes, id: HexString32Bytes) => boolean;

// https://github.com/ethereum/execution-apis/blob/main/src/eth/sign.json
eth_sign: (address: Address, message: HexStringBytes) => HexString256Bytes;
eth_signTransaction: (transaction: TransactionWithSender) => HexStringBytes;

// https://github.com/ethereum/execution-apis/blob/main/src/eth/state.json
eth_getBalance: (address: Address, blockNumber: BlockNumberOrTag) => Uint;
eth_getStorageAt: (
address: Address,
storageSlot: Uint256,
blockNumber: BlockNumberOrTag,
) => HexStringBytes;
eth_getTransactionCount: (address: Address, blockNumber: BlockNumberOrTag) => Uint;
eth_getCode: (address: Address, blockNumber: BlockNumberOrTag) => HexStringBytes;

// https://github.com/ethereum/execution-apis/blob/main/src/eth/submit.json
eth_sendTransaction: (transaction: TransactionWithSender) => HexString32Bytes;
eth_sendRawTransaction: (transaction: HexStringBytes) => HexString32Bytes;

// https://geth.ethereum.org/docs/rpc/pubsub
eth_subscribe: (
...params:
| ['newHeads']
| ['newPendingTransactions']
| ['syncing']
| ['logs', { address: HexString; topic: HexString[] }]
) => HexString;
eth_unsubscribe: (subscriptionId: HexString) => HexString;

// Non-supported by execution-apis specs
eth_getCompilers: () => string[];
eth_compileSolidity: (code: string) => CompileResult;
eth_compileLLL: (code: string) => HexStringBytes;
eth_compileSerpent: (code: string) => HexStringBytes;
};
2 changes: 2 additions & 0 deletions packages/web3-common/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,5 @@ export * from './types';
export * from './web3_base_provider';
export * from './web3_base_wallet';
export * from './web3_event_emitter';
export * from './eth_execution_api';
export * from './json_rpc';
30 changes: 30 additions & 0 deletions packages/web3-common/src/json_rpc.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { JsonRpcRequest, JsonRpcPayload, JsonRpcResponse } from './types';

let messageId = 0;

export const toPayload = <ParamType = unknown[]>(
method: string,
params: ParamType,
): JsonRpcPayload<ParamType> => {
messageId += 1;

return {
jsonrpc: '2.0',
id: messageId,
method,
params: params ?? undefined,
};
};

export const validateResponse = (response: JsonRpcResponse): boolean =>
!!response &&
!response.error &&
response.jsonrpc === '2.0' &&
(typeof response.id === 'number' || typeof response.id === 'string') &&
response.result !== undefined; // only undefined is not valid json object

export const isValidResponse = (response: JsonRpcResponse | JsonRpcResponse[]): boolean =>
Array.isArray(response) ? response.every(validateResponse) : validateResponse(response);

export const toBatchPayload = (requests: JsonRpcRequest[]) =>
requests.map(request => toPayload(request.method, request.params));
25 changes: 24 additions & 1 deletion packages/web3-common/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { HexString } from 'web3-utils';

export type JsonRpcId = string | number | null;
export type JsonRpcResult = string | number | boolean | Record<string, unknown>;
export type JsonRpcIdentifier = '2.0' | '1.0';
export type JsonRpcIdentifier = string & ('2.0' | '1.0');

// Make each attribute mutable by removing `readonly`
export type Mutable<T> = {
Expand Down Expand Up @@ -190,3 +190,26 @@ export enum PredefinedBlockNumbers {
PENDING = 'pending',
EARLIEST = 'earliest',
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
export type Web3APISpec = Record<string, (...params: any) => any>;
export type Web3APIMethod<T extends Web3APISpec> = string & keyof T;
export type Web3APIParams<API extends Web3APISpec, Method extends Web3APIMethod<API>> = Parameters<
API[Method]
>;

export interface Web3APIRequest<API extends Web3APISpec, Method extends Web3APIMethod<API>> {
method: Method;
params: Web3APIParams<API, Method> extends [] ? [] : Web3APIParams<API, Method>;
}

export interface Web3APIPayload<API extends Web3APISpec, Method extends Web3APIMethod<API>>
extends Web3APIRequest<API, Method> {
readonly jsonrpc?: JsonRpcIdentifier;
readonly id?: JsonRpcId;
}

export type Web3APIReturnType<
API extends Web3APISpec,
Method extends Web3APIMethod<API>,
> = ReturnType<API[Method]>;
Loading