Skip to content

Commit

Permalink
Strong type support for Eth API (#4513)
Browse files Browse the repository at this point in the history
* ✨ Add eth execution API types

* 🎨 Update base provider to use eth api types

* ✨ Add json-rpc helpers

* 🎨 Update exports for web3-common

* 🎨 Update API types

* 🎨 Update http-provider types

* 🎨 Update web3-core types

* Apply suggestions from code review

Co-authored-by: Wyatt Barnes <me@wyatt.email>

* ✨ Add common types to utils

* 🎨 Updated the types as per feedback

* 🎨 Update the code with feedback

* 🎨 Fix ReceiptInfo object

* 🎨 Fix eth_submitWork spec

* 🎨 Update eth_estimateGas spec to make all properties of tx optional

* 🎨 Update transaction spec

* Apply suggestions from code review

Co-authored-by: Wyatt Barnes <me@wyatt.email>

* Update packages/web3-common/src/eth_execution_api.ts

Co-authored-by: Wyatt Barnes <me@wyatt.email>

Co-authored-by: Wyatt Barnes <me@wyatt.email>
  • Loading branch information
nazarhussain and spacesailor24 committed Nov 15, 2021
1 parent 2d85ff0 commit d0a8493
Show file tree
Hide file tree
Showing 21 changed files with 556 additions and 135 deletions.
1 change: 1 addition & 0 deletions packages/web3-common/src/deferred_promise.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ export class DeferredPromise<T> implements Promise<T> {
}

public async catch<TResult>(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
onrejected?: (reason: any) => TResult | PromiseLike<TResult>,
): Promise<T | TResult> {
return this._promise.catch(onrejected);
Expand Down
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;
readonly value: Uint;
readonly input: HexStringBytes;
}

export interface Transaction1559Unsigned extends BaseTransaction {
readonly maxFeePerGas: Uint;
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;
readonly accessList: AccessList;
}

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

export interface TransactionLegacyUnsigned extends BaseTransaction {
readonly gasPrice: Uint;
}

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 =
| 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 };

// https://github.com/ethereum/execution-apis/blob/main/src/schemas/block.json#L2
export interface Block {
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: Uint | null;
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;
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;
};
1 change: 1 addition & 0 deletions packages/web3-common/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@ 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 './deferred_promise';
export * as jsonRpc from './json_rpc';
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 @@ -213,6 +213,29 @@ export enum PredefinedBlockNumbers {
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]>;

export type ConnectionEvent = {
code: number;
reason: string;
Expand Down
23 changes: 17 additions & 6 deletions packages/web3-common/src/web3_base_provider.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,12 @@
import { JsonRpcPayload, JsonRpcResponse, JsonRpcResult } from './types';
import { EthExecutionAPI } from './eth_execution_api';
import {
Web3APIPayload,
Web3APIReturnType,
JsonRpcResponse,
JsonRpcResult,
Web3APIMethod,
Web3APISpec,
} from './types';

export interface ProviderMessage<T = JsonRpcResult> {
type: string;
Expand All @@ -23,7 +31,7 @@ const symbol = Symbol.for('web3/base-provider');

// Provider interface compatible with EIP-1193
// https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1193.md
export abstract class Web3BaseProvider {
export abstract class Web3BaseProvider<API extends Web3APISpec = EthExecutionAPI> {
public static isWeb3Provider(provider: unknown) {
return (
provider instanceof Web3BaseProvider ||
Expand All @@ -44,10 +52,13 @@ export abstract class Web3BaseProvider {
abstract supportsSubscriptions(): boolean;

// https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1193.md#request
abstract request<T = JsonRpcResponse, T2 = unknown[], T3 = unknown>(
payload: JsonRpcPayload<T2>,
providerOptions?: T3,
): Promise<JsonRpcResponse<T>>;
abstract request<
Method extends Web3APIMethod<API>,
ResponseType = Web3APIReturnType<API, Method>,
>(
request: Web3APIPayload<API, Method>,
requestOptions?: unknown,
): Promise<JsonRpcResponse<ResponseType>>;

// https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1193.md#events
abstract on<T = JsonRpcResult>(
Expand Down
Loading

0 comments on commit d0a8493

Please sign in to comment.