diff --git a/web3.js/src/connection.ts b/web3.js/src/connection.ts index 7da3fa3a6b6599..9a746f30731998 100644 --- a/web3.js/src/connection.ts +++ b/web3.js/src/connection.ts @@ -23,6 +23,7 @@ import { import type {Struct} from 'superstruct'; import {Client as RpcWebSocketClient} from 'rpc-websockets'; import RpcClient from 'jayson/lib/client/browser'; +import {JSONRPCError} from 'jayson'; import {AgentManager} from './agent-manager'; import {EpochSchedule} from './epoch-schedule'; @@ -1196,6 +1197,16 @@ export type BlockResponse = { blockTime: number | null; }; +/** + * A processed block fetched from the RPC API where the `transactionDetails` mode is `accounts` + */ +export type AccountsModeBlockResponse = VersionedAccountsModeBlockResponse; + +/** + * A processed block fetched from the RPC API where the `transactionDetails` mode is `none` + */ +export type NoneModeBlockResponse = VersionedNoneModeBlockResponse; + /** * A block with parsed transactions */ @@ -1232,6 +1243,33 @@ export type ParsedBlockResponse = { blockHeight: number | null; }; +/** + * A block with parsed transactions where the `transactionDetails` mode is `accounts` + */ +export type ParsedAccountsModeBlockResponse = Omit< + ParsedBlockResponse, + 'transactions' +> & { + transactions: Array< + Omit & { + transaction: Pick< + ParsedBlockResponse['transactions'][number]['transaction'], + 'signatures' + > & { + accountKeys: ParsedMessageAccount[]; + }; + } + >; +}; + +/** + * A block with parsed transactions where the `transactionDetails` mode is `none` + */ +export type ParsedNoneModeBlockResponse = Omit< + ParsedBlockResponse, + 'transactions' +>; + /** * A processed block fetched from the RPC API */ @@ -1271,6 +1309,33 @@ export type VersionedBlockResponse = { blockTime: number | null; }; +/** + * A processed block fetched from the RPC API where the `transactionDetails` mode is `accounts` + */ +export type VersionedAccountsModeBlockResponse = Omit< + VersionedBlockResponse, + 'transactions' +> & { + transactions: Array< + Omit & { + transaction: Pick< + VersionedBlockResponse['transactions'][number]['transaction'], + 'signatures' + > & { + accountKeys: ParsedMessageAccount[]; + }; + } + >; +}; + +/** + * A processed block fetched from the RPC API where the `transactionDetails` mode is `none` + */ +export type VersionedNoneModeBlockResponse = Omit< + VersionedBlockResponse, + 'transactions' +>; + /** * A confirmed block on the ledger * @@ -2011,6 +2076,11 @@ const AnnotatedAccountKey = pick({ source: optional(union([literal('transaction'), literal('lookupTable')])), }); +const ConfirmedTransactionAccountsModeResult = pick({ + accountKeys: array(AnnotatedAccountKey), + signatures: array(string()), +}); + const ParsedInstructionResult = pick({ parsed: unknown(), program: string(), @@ -2136,6 +2206,14 @@ const ParsedConfirmedTransactionMetaResult = pick({ const TransactionVersionStruct = union([literal(0), literal('legacy')]); +/** @internal */ +const RewardsResult = pick({ + pubkey: string(), + lamports: number(), + postBalance: nullable(number()), + rewardType: nullable(string()), +}); + /** * Expected JSON RPC response for the "getBlock" message */ @@ -2152,16 +2230,46 @@ const GetBlockRpcResult = jsonRpcResult( version: optional(TransactionVersionStruct), }), ), - rewards: optional( - array( - pick({ - pubkey: string(), - lamports: number(), - postBalance: nullable(number()), - rewardType: nullable(string()), - }), - ), + rewards: optional(array(RewardsResult)), + blockTime: nullable(number()), + blockHeight: nullable(number()), + }), + ), +); + +/** + * Expected JSON RPC response for the "getBlock" message when `transactionDetails` is `none` + */ +const GetNoneModeBlockRpcResult = jsonRpcResult( + nullable( + pick({ + blockhash: string(), + previousBlockhash: string(), + parentSlot: number(), + rewards: optional(array(RewardsResult)), + blockTime: nullable(number()), + blockHeight: nullable(number()), + }), + ), +); + +/** + * Expected JSON RPC response for the "getBlock" message when `transactionDetails` is `accounts` + */ +const GetAccountsModeBlockRpcResult = jsonRpcResult( + nullable( + pick({ + blockhash: string(), + previousBlockhash: string(), + parentSlot: number(), + transactions: array( + pick({ + transaction: ConfirmedTransactionAccountsModeResult, + meta: nullable(ConfirmedTransactionMetaResult), + version: optional(TransactionVersionStruct), + }), ), + rewards: optional(array(RewardsResult)), blockTime: nullable(number()), blockHeight: nullable(number()), }), @@ -2184,16 +2292,46 @@ const GetParsedBlockRpcResult = jsonRpcResult( version: optional(TransactionVersionStruct), }), ), - rewards: optional( - array( - pick({ - pubkey: string(), - lamports: number(), - postBalance: nullable(number()), - rewardType: nullable(string()), - }), - ), + rewards: optional(array(RewardsResult)), + blockTime: nullable(number()), + blockHeight: nullable(number()), + }), + ), +); + +/** + * Expected parsed JSON RPC response for the "getBlock" message when `transactionDetails` is `accounts` + */ +const GetParsedAccountsModeBlockRpcResult = jsonRpcResult( + nullable( + pick({ + blockhash: string(), + previousBlockhash: string(), + parentSlot: number(), + transactions: array( + pick({ + transaction: ConfirmedTransactionAccountsModeResult, + meta: nullable(ParsedConfirmedTransactionMetaResult), + version: optional(TransactionVersionStruct), + }), ), + rewards: optional(array(RewardsResult)), + blockTime: nullable(number()), + blockHeight: nullable(number()), + }), + ), +); + +/** + * Expected parsed JSON RPC response for the "getBlock" message when `transactionDetails` is `none` + */ +const GetParsedNoneModeBlockRpcResult = jsonRpcResult( + nullable( + pick({ + blockhash: string(), + previousBlockhash: string(), + parentSlot: number(), + rewards: optional(array(RewardsResult)), blockTime: nullable(number()), blockHeight: nullable(number()), }), @@ -2217,16 +2355,7 @@ const GetConfirmedBlockRpcResult = jsonRpcResult( meta: nullable(ConfirmedTransactionMetaResult), }), ), - rewards: optional( - array( - pick({ - pubkey: string(), - lamports: number(), - postBalance: nullable(number()), - rewardType: nullable(string()), - }), - ), - ), + rewards: optional(array(RewardsResult)), blockTime: nullable(number()), }), ), @@ -4283,6 +4412,26 @@ export class Connection { rawConfig?: GetBlockConfig, ): Promise; + /** + * @deprecated Instead, call `getBlock` using a `GetVersionedBlockConfig` by + * setting the `maxSupportedTransactionVersion` property. + */ + // eslint-disable-next-line no-dupe-class-members + async getBlock( + slot: number, + rawConfig: GetBlockConfig & {transactionDetails: 'accounts'}, + ): Promise; + + /** + * @deprecated Instead, call `getBlock` using a `GetVersionedBlockConfig` by + * setting the `maxSupportedTransactionVersion` property. + */ + // eslint-disable-next-line no-dupe-class-members + async getBlock( + slot: number, + rawConfig: GetBlockConfig & {transactionDetails: 'none'}, + ): Promise; + /** * Fetch a processed block from the cluster. */ @@ -4292,6 +4441,18 @@ export class Connection { rawConfig?: GetVersionedBlockConfig, ): Promise; + // eslint-disable-next-line no-dupe-class-members + async getBlock( + slot: number, + rawConfig: GetVersionedBlockConfig & {transactionDetails: 'accounts'}, + ): Promise; + + // eslint-disable-next-line no-dupe-class-members + async getBlock( + slot: number, + rawConfig: GetVersionedBlockConfig & {transactionDetails: 'none'}, + ): Promise; + /** * Fetch a processed block from the cluster. */ @@ -4299,7 +4460,12 @@ export class Connection { async getBlock( slot: number, rawConfig?: GetVersionedBlockConfig, - ): Promise { + ): Promise< + | VersionedBlockResponse + | VersionedAccountsModeBlockResponse + | VersionedNoneModeBlockResponse + | null + > { const {commitment, config} = extractCommitmentFromConfig(rawConfig); const args = this._buildArgsAtLeastConfirmed( [slot], @@ -4308,26 +4474,54 @@ export class Connection { config, ); const unsafeRes = await this._rpcRequest('getBlock', args); - const res = create(unsafeRes, GetBlockRpcResult); - - if ('error' in res) { - throw new SolanaJSONRPCError(res.error, 'failed to get confirmed block'); + try { + switch (config?.transactionDetails) { + case 'accounts': { + const res = create(unsafeRes, GetAccountsModeBlockRpcResult); + if ('error' in res) { + throw res.error; + } + return res.result; + } + case 'none': { + const res = create(unsafeRes, GetNoneModeBlockRpcResult); + if ('error' in res) { + throw res.error; + } + return res.result; + } + default: { + const res = create(unsafeRes, GetBlockRpcResult); + if ('error' in res) { + throw res.error; + } + const {result} = res; + return result + ? { + ...result, + transactions: result.transactions.map( + ({transaction, meta, version}) => ({ + meta, + transaction: { + ...transaction, + message: versionedMessageFromResponse( + version, + transaction.message, + ), + }, + version, + }), + ), + } + : null; + } + } + } catch (e) { + throw new SolanaJSONRPCError( + e as JSONRPCError, + 'failed to get confirmed block', + ); } - - const result = res.result; - if (!result) return result; - - return { - ...result, - transactions: result.transactions.map(({transaction, meta, version}) => ({ - meta, - transaction: { - ...transaction, - message: versionedMessageFromResponse(version, transaction.message), - }, - version, - })), - }; } /** @@ -4336,7 +4530,29 @@ export class Connection { async getParsedBlock( slot: number, rawConfig?: GetVersionedBlockConfig, - ): Promise { + ): Promise; + + // eslint-disable-next-line no-dupe-class-members + async getParsedBlock( + slot: number, + rawConfig: GetVersionedBlockConfig & {transactionDetails: 'accounts'}, + ): Promise; + + // eslint-disable-next-line no-dupe-class-members + async getParsedBlock( + slot: number, + rawConfig: GetVersionedBlockConfig & {transactionDetails: 'none'}, + ): Promise; + // eslint-disable-next-line no-dupe-class-members + async getParsedBlock( + slot: number, + rawConfig?: GetVersionedBlockConfig, + ): Promise< + | ParsedBlockResponse + | ParsedAccountsModeBlockResponse + | ParsedNoneModeBlockResponse + | null + > { const {commitment, config} = extractCommitmentFromConfig(rawConfig); const args = this._buildArgsAtLeastConfirmed( [slot], @@ -4345,11 +4561,33 @@ export class Connection { config, ); const unsafeRes = await this._rpcRequest('getBlock', args); - const res = create(unsafeRes, GetParsedBlockRpcResult); - if ('error' in res) { - throw new SolanaJSONRPCError(res.error, 'failed to get block'); + try { + switch (config?.transactionDetails) { + case 'accounts': { + const res = create(unsafeRes, GetParsedAccountsModeBlockRpcResult); + if ('error' in res) { + throw res.error; + } + return res.result; + } + case 'none': { + const res = create(unsafeRes, GetParsedNoneModeBlockRpcResult); + if ('error' in res) { + throw res.error; + } + return res.result; + } + default: { + const res = create(unsafeRes, GetParsedBlockRpcResult); + if ('error' in res) { + throw res.error; + } + return res.result; + } + } + } catch (e) { + throw new SolanaJSONRPCError(e as JSONRPCError, 'failed to get block'); } - return res.result; } /*