From 82bbf6f6715fd17dd2d3532b98187870dd4a286e Mon Sep 17 00:00:00 2001 From: Joe C Date: Wed, 13 Sep 2023 18:47:22 -0400 Subject: [PATCH] refactor(experimental): add `getBlock` RPC method (#1552) * refactor(experimental): add `getBlock` RPC method * refactor(experimental): revised getBlock * refactor(experimental): revised getBlock again --- ...response-patcher-allowed-numeric-values.ts | 89 ++ .../rpc-methods/__tests__/get-block-test.ts | 39 + .../__typetests__/get-block-type-test.ts | 1258 +++++++++++++++++ packages/rpc-core/src/rpc-methods/common.ts | 45 + packages/rpc-core/src/rpc-methods/getBlock.ts | 663 +++++++++ .../src/rpc-methods/getTransaction.ts | 47 +- packages/rpc-core/src/rpc-methods/index.ts | 2 + packages/rpc-transport/src/json-rpc-types.ts | 548 ++++++- 8 files changed, 2647 insertions(+), 44 deletions(-) create mode 100644 packages/rpc-core/src/rpc-methods/__tests__/get-block-test.ts create mode 100644 packages/rpc-core/src/rpc-methods/__typetests__/get-block-type-test.ts create mode 100644 packages/rpc-core/src/rpc-methods/getBlock.ts diff --git a/packages/rpc-core/src/response-patcher-allowed-numeric-values.ts b/packages/rpc-core/src/response-patcher-allowed-numeric-values.ts index 14543bc955e7..809a1f0922d1 100644 --- a/packages/rpc-core/src/response-patcher-allowed-numeric-values.ts +++ b/packages/rpc-core/src/response-patcher-allowed-numeric-values.ts @@ -67,6 +67,95 @@ export function getAllowedNumericKeypaths(): AllowedNumericKeypaths { ]; memoizedKeypaths = { getAccountInfo: jsonParsedAccountsConfigs.map(c => ['value', ...c]), + getBlock: [ + ['blockTime'], + ['transactions', KEYPATH_WILDCARD, 'meta', 'preTokenBalances', KEYPATH_WILDCARD, 'accountIndex'], + [ + 'transactions', + KEYPATH_WILDCARD, + 'meta', + 'preTokenBalances', + KEYPATH_WILDCARD, + 'uiTokenAmount', + 'decimals', + ], + ['transactions', KEYPATH_WILDCARD, 'meta', 'postTokenBalances', KEYPATH_WILDCARD, 'accountIndex'], + [ + 'transactions', + KEYPATH_WILDCARD, + 'meta', + 'postTokenBalances', + KEYPATH_WILDCARD, + 'uiTokenAmount', + 'decimals', + ], + ['transactions', KEYPATH_WILDCARD, 'meta', 'rewards', KEYPATH_WILDCARD, 'commission'], + ['transactions', KEYPATH_WILDCARD, 'meta', 'innerInstructions', KEYPATH_WILDCARD, 'index'], + [ + 'transactions', + KEYPATH_WILDCARD, + 'meta', + 'innerInstructions', + KEYPATH_WILDCARD, + 'instructions', + KEYPATH_WILDCARD, + 'programIdIndex', + ], + [ + 'transactions', + KEYPATH_WILDCARD, + 'meta', + 'innerInstructions', + KEYPATH_WILDCARD, + 'instructions', + KEYPATH_WILDCARD, + 'accounts', + KEYPATH_WILDCARD, + ], + [ + 'transactions', + KEYPATH_WILDCARD, + 'transaction', + 'message', + 'addressTableLookups', + KEYPATH_WILDCARD, + 'writableIndexes', + KEYPATH_WILDCARD, + ], + [ + 'transactions', + KEYPATH_WILDCARD, + 'transaction', + 'message', + 'addressTableLookups', + KEYPATH_WILDCARD, + 'readonlyIndexes', + KEYPATH_WILDCARD, + ], + [ + 'transactions', + KEYPATH_WILDCARD, + 'transaction', + 'message', + 'instructions', + KEYPATH_WILDCARD, + 'programIdIndex', + ], + [ + 'transactions', + KEYPATH_WILDCARD, + 'transaction', + 'message', + 'instructions', + KEYPATH_WILDCARD, + 'accounts', + KEYPATH_WILDCARD, + ], + ['transactions', KEYPATH_WILDCARD, 'transaction', 'message', 'header', 'numReadonlySignedAccounts'], + ['transactions', KEYPATH_WILDCARD, 'transaction', 'message', 'header', 'numReadonlyUnsignedAccounts'], + ['transactions', KEYPATH_WILDCARD, 'transaction', 'message', 'header', 'numRequiredSignatures'], + ['rewards', KEYPATH_WILDCARD, 'commission'], + ], getBlockTime: [[]], getClusterNodes: [ [KEYPATH_WILDCARD, 'featureSet'], diff --git a/packages/rpc-core/src/rpc-methods/__tests__/get-block-test.ts b/packages/rpc-core/src/rpc-methods/__tests__/get-block-test.ts new file mode 100644 index 000000000000..f8b18c97a58e --- /dev/null +++ b/packages/rpc-core/src/rpc-methods/__tests__/get-block-test.ts @@ -0,0 +1,39 @@ +describe('when `transactionDetails` is set to `full`', () => { + describe('returns transactions with the correct shape', () => { + it.todo('when called with json encoding and maxSupportedTransactionVersion is set'); + it.todo('when called with jsonParsed encoding and maxSupportedTransactionVersion is set'); + it.todo('when called with base58 encoding and maxSupportedTransactionVersion is set'); + it.todo('when called with base64 encoding and maxSupportedTransactionVersion is set'); + it.todo('when called with json encoding and maxSupportedTransactionVersion is not set'); + it.todo('when called with jsonParsed encoding and maxSupportedTransactionVersion is not set'); + it.todo('when called with base58 encoding and maxSupportedTransactionVersion is not set'); + it.todo('when called with base64 encoding and maxSupportedTransactionVersion is not set'); + it.todo('when called without additional config'); + }); +}); + +describe('when `transactionDetails` is set to `accounts`', () => { + describe('returns transactions with only signatures and an annotated list of accounts', () => { + it.todo('when maxSupportedTransactionVersion is set'); + it.todo('when maxSupportedTransactionVersion is not set'); + it.todo('when called without additional config'); + }); +}); + +describe('when `transactionDetails` is set to `signatures`', () => { + describe('returns transactions with only signatures', () => { + it.todo('when maxSupportedTransactionVersion is set'); + it.todo('when maxSupportedTransactionVersion is not set'); + it.todo('when called without additional config'); + }); +}); + +describe('when `transactionDetails` is set to `none`', () => { + describe('when called with rewards set to false', () => { + it.todo('returns an empty array for transactions and an empty array for rewards'); + }); + + describe('when called with rewards set to true', () => { + it.todo('returns no transactions and an array of rewards for rewards'); + }); +}); diff --git a/packages/rpc-core/src/rpc-methods/__typetests__/get-block-type-test.ts b/packages/rpc-core/src/rpc-methods/__typetests__/get-block-type-test.ts new file mode 100644 index 000000000000..05e922adad22 --- /dev/null +++ b/packages/rpc-core/src/rpc-methods/__typetests__/get-block-type-test.ts @@ -0,0 +1,1258 @@ +import { Base58EncodedAddress } from '@solana/addresses'; +import { Rpc } from '@solana/rpc-transport/dist/types/json-rpc-types'; +import { Blockhash, TransactionVersion } from '@solana/transactions'; + +import { TransactionError } from '../../transaction-error'; +import { SolanaRpcMethods } from '..'; +import { + Base58EncodedBytes, + Base58EncodedDataResponse, + Base64EncodedDataResponse, + LamportsUnsafeBeyond2Pow53Minus1, + Reward, + TokenBalance, + TransactionStatus, + U64UnsafeBeyond2Pow53Minus1, +} from '../common'; + +function assertNotAProperty( + _: { [Prop in keyof T]: Prop extends TPropName ? never : T[Prop] }, + _propName: TPropName +): void {} + +const rpc = null as unknown as Rpc; + +function assertBase( + response: { + blockhash: string; + blockHeight: bigint; + blockTime: number; + parentSlot: bigint; + previousBlockhash: string; + } & { [key: string]: unknown } +) { + response.blockhash satisfies string; + response.blockHeight satisfies bigint; + response.blockTime satisfies number; + response.parentSlot satisfies bigint; + response.previousBlockhash satisfies string; +} + +async () => { + // First overload + // Rewards set to `false` + { + const response = await rpc + .getBlock(0n, { + // No extra configs + rewards: false, + transactionDetails: 'none', + }) + .send(); + if (response) { + assertBase(response); + assertNotAProperty(response, 'transactions'); + assertNotAProperty(response, 'rewards'); + } + } + + // First overload with configs + // Rewards set to `false` + { + const response = await rpc + .getBlock(0n, { + commitment: 'processed', + encoding: 'base64', + maxSupportedTransactionVersion: 0, + rewards: false, + transactionDetails: 'none', + }) + .send(); + if (response) { + assertBase(response); + assertNotAProperty(response, 'transactions'); + assertNotAProperty(response, 'rewards'); + } + } + + // Second overload + // Rewards defaults to `true` + { + const response = await rpc + .getBlock(0n, { + // No extra configs + transactionDetails: 'none', + }) + .send(); + if (response) { + assertBase(response); + assertNotAProperty(response, 'transactions'); + response.rewards satisfies readonly Reward[]; + } + } + + // Second overload with configs + // Rewards defaults to `true` + { + const response = await rpc + .getBlock(0n, { + commitment: 'confirmed', + encoding: 'base58', + maxSupportedTransactionVersion: 0, + transactionDetails: 'none', + }) + .send(); + if (response) { + assertBase(response); + assertNotAProperty(response, 'transactions'); + response.rewards satisfies readonly Reward[]; + } + } + + // Second overload + // Rewards set to `true` + { + const response = await rpc + .getBlock(0n, { + // No extra configs + rewards: true, + transactionDetails: 'none', + }) + .send(); + if (response) { + assertBase(response); + assertNotAProperty(response, 'transactions'); + response.rewards satisfies readonly Reward[]; + } + } + + // Second overload with configs + // Rewards set to `true` + { + const response = await rpc + .getBlock(0n, { + commitment: 'confirmed', + encoding: 'base58', + maxSupportedTransactionVersion: 0, + rewards: true, + transactionDetails: 'none', + }) + .send(); + if (response) { + assertBase(response); + assertNotAProperty(response, 'transactions'); + response.rewards satisfies readonly Reward[]; + } + } + + // Third overload + // Rewards set to `false` + { + const response = await rpc + .getBlock(0n, { + // No extra configs + rewards: false, + transactionDetails: 'signatures', + }) + .send(); + if (response) { + assertBase(response); + assertNotAProperty(response, 'transactions'); + assertNotAProperty(response, 'rewards'); + response.signatures satisfies readonly Base58EncodedBytes[]; + } + } + + // Third overload with configs + // Rewards set to `false` + { + const response = await rpc + .getBlock(0n, { + commitment: 'confirmed', + encoding: 'json', + maxSupportedTransactionVersion: 0, + rewards: false, + transactionDetails: 'signatures', + }) + .send(); + if (response) { + assertBase(response); + assertNotAProperty(response, 'transactions'); + assertNotAProperty(response, 'rewards'); + response.signatures satisfies readonly Base58EncodedBytes[]; + } + } + + // Fourth overload + // Rewards defaults to `true` + { + const response = await rpc + .getBlock(0n, { + // No extra configs + transactionDetails: 'signatures', + }) + .send(); + if (response) { + assertBase(response); + assertNotAProperty(response, 'transactions'); + response.signatures satisfies readonly Base58EncodedBytes[]; + response.rewards satisfies readonly Reward[]; + } + } + + // Fourth overload with configs + // Rewards defaults to `true` + { + const response = await rpc + .getBlock(0n, { + commitment: 'confirmed', + encoding: 'jsonParsed', + maxSupportedTransactionVersion: 0, + transactionDetails: 'signatures', + }) + .send(); + if (response) { + assertBase(response); + assertNotAProperty(response, 'transactions'); + response.signatures satisfies readonly Base58EncodedBytes[]; + response.rewards satisfies readonly Reward[]; + } + } + + // Fourth overload + // Rewards set to `true` + { + const response = await rpc + .getBlock(0n, { + // No extra configs + rewards: true, + transactionDetails: 'signatures', + }) + .send(); + if (response) { + assertBase(response); + assertNotAProperty(response, 'transactions'); + response.signatures satisfies readonly Base58EncodedBytes[]; + response.rewards satisfies readonly Reward[]; + } + } + + // Fourth overload with configs + // Rewards set to `true` + { + const response = await rpc + .getBlock(0n, { + commitment: 'confirmed', + encoding: 'jsonParsed', + maxSupportedTransactionVersion: 0, + rewards: true, + transactionDetails: 'signatures', + }) + .send(); + if (response) { + assertBase(response); + assertNotAProperty(response, 'transactions'); + response.signatures satisfies readonly Base58EncodedBytes[]; + response.rewards satisfies readonly Reward[]; + } + } + + type ExpectedMetaForAccountsBase = { + err: TransactionError | null; + fee: LamportsUnsafeBeyond2Pow53Minus1; + preBalances: readonly LamportsUnsafeBeyond2Pow53Minus1[]; + postBalances: readonly LamportsUnsafeBeyond2Pow53Minus1[]; + preTokenBalances?: readonly TokenBalance[]; + postTokenBalances?: readonly TokenBalance[]; + status: TransactionStatus; + }; + + type ExpectedTransactionForAccountsBaseLegacy = { + meta: ExpectedMetaForAccountsBase | null; + transaction: { + accountKeys: readonly Readonly<{ + pubkey: string; + signer: boolean; + source: 'transaction'; + writable: boolean; + }>[]; + signatures: readonly Base58EncodedBytes[]; + }; + }; + + type ExpectedTransactionForAccountsBaseVersioned = { + meta: ExpectedMetaForAccountsBase | null; + transaction: { + accountKeys: readonly Readonly<{ + pubkey: string; + signer: boolean; + source: 'transaction' | 'lookupTable'; + writable: boolean; + }>[]; + signatures: readonly Base58EncodedBytes[]; + }; + version: TransactionVersion; + }; + + // Fifth overload + // Rewards set to `false` + // Max supported transaction version set to 0 + { + const response = await rpc + .getBlock(0n, { + // No extra configs + maxSupportedTransactionVersion: 0, + rewards: false, + transactionDetails: 'accounts', + }) + .send(); + if (response) { + assertBase(response); + assertNotAProperty(response, 'rewards'); + response.transactions satisfies readonly ExpectedTransactionForAccountsBaseVersioned[]; + } + } + + // Fifth overload with configs + // Rewards set to `false` + // Max supported transaction version set to 0 + { + const response = await rpc + .getBlock(0n, { + commitment: 'confirmed', + encoding: 'base64', + maxSupportedTransactionVersion: 0, + rewards: false, + transactionDetails: 'accounts', + }) + .send(); + if (response) { + assertBase(response); + assertNotAProperty(response, 'rewards'); + response.transactions satisfies readonly ExpectedTransactionForAccountsBaseVersioned[]; + } + } + + // Sixth overload + // Rewards set to `false` + // Max supported transaction version defaults to `legacy` + { + const response = await rpc + .getBlock(0n, { + // No extra configs + rewards: false, + transactionDetails: 'accounts', + }) + .send(); + if (response) { + assertBase(response); + assertNotAProperty(response, 'rewards'); + response.transactions satisfies readonly ExpectedTransactionForAccountsBaseLegacy[]; + // @ts-expect-error `version` should be undefined + response.transactions satisfies Array< + ExpectedTransactionForAccountsBaseLegacy & { + version: 'legacy'; + } + >; + } + } + + // Sixth overload with configs + // Rewards set to `false` + // Max supported transaction version defaults to `legacy` + { + const response = await rpc + .getBlock(0n, { + commitment: 'confirmed', + encoding: 'base64', + rewards: false, + transactionDetails: 'accounts', + }) + .send(); + if (response) { + assertBase(response); + assertNotAProperty(response, 'rewards'); + response.transactions satisfies readonly ExpectedTransactionForAccountsBaseLegacy[]; + // @ts-expect-error `version` should be undefined + response.transactions satisfies Array< + ExpectedTransactionForAccountsBaseLegacy & { + version: 'legacy'; + } + >; + } + } + + // Seventh overload + // Rewards defaults to `true` + // Max supported transaction version set to 0 + { + const response = await rpc + .getBlock(0n, { + // No extra configs + maxSupportedTransactionVersion: 0, + transactionDetails: 'accounts', + }) + .send(); + if (response) { + assertBase(response); + response.transactions satisfies readonly ExpectedTransactionForAccountsBaseVersioned[]; + response.rewards satisfies readonly Reward[]; + } + } + + // Seventh overload with configs + // Rewards defaults to `true` + // Max supported transaction version set to 0 + { + const response = await rpc + .getBlock(0n, { + commitment: 'confirmed', + encoding: 'base64', + maxSupportedTransactionVersion: 0, + transactionDetails: 'accounts', + }) + .send(); + if (response) { + assertBase(response); + response.transactions satisfies readonly ExpectedTransactionForAccountsBaseVersioned[]; + response.rewards satisfies readonly Reward[]; + } + } + + // Seventh overload + // Rewards set to `true` + // Max supported transaction version set to 0 + { + const response = await rpc + .getBlock(0n, { + // No extra configs + maxSupportedTransactionVersion: 0, + rewards: true, + transactionDetails: 'accounts', + }) + .send(); + if (response) { + assertBase(response); + response.transactions satisfies readonly ExpectedTransactionForAccountsBaseVersioned[]; + response.rewards satisfies readonly Reward[]; + } + } + + // Seventh overload with configs + // Rewards set to `true` + // Max supported transaction version set to 0 + { + const response = await rpc + .getBlock(0n, { + commitment: 'confirmed', + encoding: 'base64', + maxSupportedTransactionVersion: 0, + rewards: true, + transactionDetails: 'accounts', + }) + .send(); + if (response) { + assertBase(response); + response.transactions satisfies readonly ExpectedTransactionForAccountsBaseVersioned[]; + response.rewards satisfies readonly Reward[]; + } + } + + // Eighth overload + // Rewards defaults to `true` + // Max supported transaction version defaults to `legacy` + { + const response = await rpc + .getBlock(0n, { + // No extra configs + transactionDetails: 'accounts', + }) + .send(); + if (response) { + assertBase(response); + response.transactions satisfies readonly ExpectedTransactionForAccountsBaseLegacy[]; + // @ts-expect-error `version` should be undefined + response.transactions satisfies readonly (ExpectedTransactionForAccountsBaseVersioned & { + version: 'legacy'; + })[]; + response.rewards satisfies readonly Reward[]; + } + } + + // Eighth overload + // Rewards set to `true` + // Max supported transaction version defaults to `legacy` + { + const response = await rpc + .getBlock(0n, { + // No extra configs + rewards: true, + transactionDetails: 'accounts', + }) + .send(); + if (response) { + assertBase(response); + response.transactions satisfies readonly ExpectedTransactionForAccountsBaseLegacy[]; + // @ts-expect-error `version` should be undefined + response.transactions satisfies readonly (ExpectedTransactionForAccountsBaseVersioned & { + version: 'legacy'; + })[]; + response.rewards satisfies readonly Reward[]; + } + } + + type ExpectedMetaForFullBase58 = { + computeUnitsConsumed?: U64UnsafeBeyond2Pow53Minus1; + err: TransactionError | null; + fee: LamportsUnsafeBeyond2Pow53Minus1; + innerInstructions: readonly Readonly<{ + index: number; + instructions: readonly Readonly<{ + accounts: readonly number[]; + data: Base58EncodedBytes; + programIdIndex: number; + }>[]; + }>[]; + logMessages: readonly string[] | null; + preBalances: readonly LamportsUnsafeBeyond2Pow53Minus1[]; + postBalances: readonly LamportsUnsafeBeyond2Pow53Minus1[]; + preTokenBalances?: readonly TokenBalance[]; + postTokenBalances?: readonly TokenBalance[]; + rewards: readonly Reward[] | null; + returnData?: Readonly<{ + programId: Base58EncodedAddress; + data: Base64EncodedDataResponse; + }>; + status: TransactionStatus; + }; + + type ExpectedTransactionForFullBase58Legacy = { + meta: ExpectedMetaForFullBase58 | null; + transaction: Base58EncodedDataResponse; + }; + + type ExpectedTransactionForFullBase58Versioned = { + meta: + | (ExpectedMetaForFullBase58 & + Readonly<{ + loadedAddresses: { + writable: readonly Base58EncodedAddress[]; + readonly: readonly Base58EncodedAddress[]; + }; + }>) + | null; + transaction: Base58EncodedDataResponse; + version: TransactionVersion; + }; + + // Ninth overload + // Rewards set to `false` + // Max supported transaction version set to 0 + // Encoding set to `base58` + // Transaction details default to `full` + { + const response = await rpc + .getBlock(0n, { + // No extra configs + encoding: 'base58', + maxSupportedTransactionVersion: 0, + rewards: false, + }) + .send(); + if (response) { + assertBase(response); + assertNotAProperty(response, 'rewards'); + response.transactions satisfies readonly ExpectedTransactionForFullBase58Versioned[]; + } + } + + // Ninth overload + // Rewards set to `false` + // Max supported transaction version set to 0 + // Encoding set to `base58` + // Transaction details set to `full` + { + const response = await rpc + .getBlock(0n, { + // No extra configs + encoding: 'base58', + maxSupportedTransactionVersion: 0, + rewards: false, + transactionDetails: 'full', + }) + .send(); + if (response) { + assertBase(response); + assertNotAProperty(response, 'rewards'); + response.transactions satisfies readonly ExpectedTransactionForFullBase58Versioned[]; + } + } + + // Tenth overload + // Rewards set to `false` + // Max supported transaction defaults to `legacy` + // Encoding set to `base58` + // Transaction details defaults to `full` + { + const response = await rpc + .getBlock(0n, { + // No extra configs + encoding: 'base58', + rewards: false, + }) + .send(); + if (response) { + assertBase(response); + assertNotAProperty(response, 'rewards'); + response.transactions satisfies readonly ExpectedTransactionForFullBase58Legacy[]; + // @ts-expect-error `version` should be undefined + response.transactions satisfies readonly (ExpectedTransactionForFullBase58Legacy & { + version: 'legacy'; + })[]; + } + } + + // Tenth overload + // Rewards set to `false` + // Max supported transaction defaults to `legacy` + // Encoding set to `base58` + // Transaction details set to `full` + { + const response = await rpc + .getBlock(0n, { + // No extra configs + encoding: 'base58', + rewards: false, + transactionDetails: 'full', + }) + .send(); + if (response) { + assertBase(response); + assertNotAProperty(response, 'rewards'); + response.transactions satisfies readonly ExpectedTransactionForFullBase58Legacy[]; + // @ts-expect-error `version` should be undefined + response.transactions satisfies readonly (ExpectedTransactionForFullBase58Legacy & { + version: 'legacy'; + })[]; + } + } + + // Eleventh overload + // Rewards defaults to `true` + // Max supported transaction version set to 0 + // Encoding set to `base58` + // Transaction details defaults to `full` + { + const response = await rpc + .getBlock(0n, { + // No extra configs + encoding: 'base58', + maxSupportedTransactionVersion: 0, + }) + .send(); + if (response) { + assertBase(response); + response.transactions satisfies readonly ExpectedTransactionForFullBase58Versioned[]; + response.rewards satisfies readonly Reward[]; + } + } + + // Eleventh overload + // Rewards set to `true` + // Max supported transaction version set to 0 + // Encoding set to `base58` + // Transaction details defaults to `full` + { + const response = await rpc + .getBlock(0n, { + // No extra configs + encoding: 'base58', + maxSupportedTransactionVersion: 0, + rewards: true, + }) + .send(); + if (response) { + assertBase(response); + response.transactions satisfies readonly ExpectedTransactionForFullBase58Versioned[]; + response.rewards satisfies readonly Reward[]; + } + } + + // Twelfth overload + // Rewards defaults to `true` + // Max supported transaction defaults to `legacy` + // Encoding set to `base58` + // Transaction details defaults to `full` + { + const response = await rpc + .getBlock(0n, { + // No extra configs + encoding: 'base58', + }) + .send(); + if (response) { + assertBase(response); + response.transactions satisfies readonly ExpectedTransactionForFullBase58Legacy[]; + // @ts-expect-error `version` should be undefined + response.transactions satisfies readonly (ExpectedTransactionForFullBase58Legacy & { + version: 'legacy'; + })[]; + response.rewards satisfies readonly Reward[]; + } + } + + // Twelfth overload + // Rewards set to `true` + // Max supported transaction defaults to `legacy` + // Encoding set to `base58` + // Transaction details defaults to `full` + { + const response = await rpc + .getBlock(0n, { + // No extra configs + encoding: 'base58', + rewards: true, + }) + .send(); + if (response) { + assertBase(response); + response.transactions satisfies readonly ExpectedTransactionForFullBase58Legacy[]; + // @ts-expect-error `version` should be undefined + response.transactions satisfies readonly (ExpectedTransactionForFullBase58Legacy & { + version: 'legacy'; + })[]; + response.rewards satisfies readonly Reward[]; + } + } + + type ExpectedMetaForFullBase64 = { + computeUnitsConsumed?: U64UnsafeBeyond2Pow53Minus1; + err: TransactionError | null; + fee: LamportsUnsafeBeyond2Pow53Minus1; + innerInstructions: readonly Readonly<{ + index: number; + instructions: readonly Readonly<{ + accounts: readonly number[]; + data: Base58EncodedBytes; + programIdIndex: number; + }>[]; + }>[]; + logMessages: readonly string[] | null; + preBalances: readonly LamportsUnsafeBeyond2Pow53Minus1[]; + postBalances: readonly LamportsUnsafeBeyond2Pow53Minus1[]; + preTokenBalances?: readonly TokenBalance[]; + postTokenBalances?: readonly TokenBalance[]; + rewards: readonly Reward[] | null; + returnData?: Readonly<{ + programId: Base58EncodedAddress; + data: Base64EncodedDataResponse; + }>; + status: TransactionStatus; + }; + + type ExpectedTransactionForFullBase64Legacy = { + meta: ExpectedMetaForFullBase64 | null; + transaction: Base64EncodedDataResponse; + }; + + type ExpectedTransactionForFullBase64Versioned = { + meta: + | (ExpectedMetaForFullBase64 & + Readonly<{ + loadedAddresses: { + writable: readonly Base58EncodedAddress[]; + readonly: readonly Base58EncodedAddress[]; + }; + }>) + | null; + transaction: Base64EncodedDataResponse; + version: TransactionVersion; + }; + + // Thirteenth overload + // Rewards set to `false` + // Max supported transaction version set to 0 + // Encoding set to `base64` + // Transaction details default to `full` + { + const response = await rpc + .getBlock(0n, { + // No extra configs + encoding: 'base64', + maxSupportedTransactionVersion: 0, + rewards: false, + }) + .send(); + if (response) { + assertBase(response); + assertNotAProperty(response, 'rewards'); + response.transactions satisfies readonly ExpectedTransactionForFullBase64Versioned[]; + } + } + + // Thirteenth overload + // Rewards set to `false` + // Max supported transaction version set to 0 + // Encoding set to `base64` + // Transaction details set to `full` + { + const response = await rpc + .getBlock(0n, { + // No extra configs + encoding: 'base64', + maxSupportedTransactionVersion: 0, + rewards: false, + transactionDetails: 'full', + }) + .send(); + if (response) { + assertBase(response); + assertNotAProperty(response, 'rewards'); + response.transactions satisfies readonly ExpectedTransactionForFullBase64Versioned[]; + } + } + + // Fourteenth overload + // Rewards set to `false` + // Max supported transaction defaults to `legacy` + // Encoding set to `base64` + // Transaction details defaults to `full` + { + const response = await rpc + .getBlock(0n, { + // No extra configs + encoding: 'base64', + rewards: false, + }) + .send(); + if (response) { + assertBase(response); + assertNotAProperty(response, 'rewards'); + response.transactions satisfies readonly ExpectedTransactionForFullBase64Legacy[]; + // @ts-expect-error `version` should be undefined + response.transactions satisfies readonly (ExpectedTransactionForFullBase64Legacy & { + version: 'legacy'; + })[]; + } + } + + // Fourteenth overload + // Rewards set to `false` + // Max supported transaction defaults to `legacy` + // Encoding set to `base64` + // Transaction details set to `full` + { + const response = await rpc + .getBlock(0n, { + // No extra configs + encoding: 'base64', + rewards: false, + transactionDetails: 'full', + }) + .send(); + if (response) { + assertBase(response); + assertNotAProperty(response, 'rewards'); + response.transactions satisfies readonly ExpectedTransactionForFullBase64Legacy[]; + // @ts-expect-error `version` should be undefined + response.transactions satisfies readonly (ExpectedTransactionForFullBase64Legacy & { + version: 'legacy'; + })[]; + } + } + + // Fifteenth overload + // Rewards defaults to `true` + // Max supported transaction version set to 0 + // Encoding set to `base64` + // Transaction details defaults to `full` + { + const response = await rpc + .getBlock(0n, { + // No extra configs + encoding: 'base64', + maxSupportedTransactionVersion: 0, + }) + .send(); + if (response) { + assertBase(response); + response.transactions satisfies readonly ExpectedTransactionForFullBase64Versioned[]; + response.rewards satisfies readonly Reward[]; + } + } + + // Sixteenth overload + // Rewards defaults to `true` + // Max supported transaction defaults to `legacy` + // Encoding set to `base64` + // Transaction details defaults to `full` + { + const response = await rpc + .getBlock(0n, { + // No extra configs + encoding: 'base64', + }) + .send(); + if (response) { + assertBase(response); + response.transactions satisfies readonly ExpectedTransactionForFullBase64Legacy[]; + // @ts-expect-error `version` should be undefined + response.transactions satisfies readonly (ExpectedTransactionForFullBase64Legacy & { + version: 'legacy'; + })[]; + response.rewards satisfies readonly Reward[]; + } + } + + type ExpectedParsedTransactionInstruction = Readonly<{ + parsed: { + type: string; + info?: object; + }; + program: string; + programId: Base58EncodedAddress; + }>; + + type ExpectedPartiallyDecodedTransactionInstruction = Readonly<{ + accounts: readonly Base58EncodedAddress[]; + data: Base58EncodedBytes; + programId: Base58EncodedAddress; + }>; + + type ExpectedTransactionInstructionForFullJsonParsed = + | ExpectedParsedTransactionInstruction + | ExpectedPartiallyDecodedTransactionInstruction; + + type ExpectedMetaForFullJsonParsedBase = { + computeUnitsConsumed?: U64UnsafeBeyond2Pow53Minus1; + err: TransactionError | null; + fee: LamportsUnsafeBeyond2Pow53Minus1; + innerInstructions: readonly Readonly<{ + index: number; + instructions: readonly ExpectedTransactionInstructionForFullJsonParsed[]; + }>[]; + logMessages: readonly string[] | null; + preBalances: readonly LamportsUnsafeBeyond2Pow53Minus1[]; + postBalances: readonly LamportsUnsafeBeyond2Pow53Minus1[]; + preTokenBalances?: readonly TokenBalance[]; + postTokenBalances?: readonly TokenBalance[]; + rewards: readonly Reward[] | null; + returnData?: Readonly<{ + programId: Base58EncodedAddress; + data: Base64EncodedDataResponse; + }>; + status: TransactionStatus; + }; + + type ExpectedMetaForFullJsonParsedLoadedAddresses = Readonly<{ + loadedAddresses: { + writable: readonly Base58EncodedAddress[]; + readonly: readonly Base58EncodedAddress[]; + }; + }>; + + type ExpectedTransactionForFullJsonParsedBase = { + message: { + header: { + numReadonlySignedAccounts: number; + numReadonlyUnsignedAccounts: number; + numRequiredSignatures: number; + }; + instructions: readonly ExpectedTransactionInstructionForFullJsonParsed[]; + recentBlockhash: Blockhash; + }; + signatures: readonly Base58EncodedBytes[]; + }; + + type ExpectedTransactionForFullJsonParsedLegacy = { + meta: ExpectedMetaForFullJsonParsedBase | null; + transaction: ExpectedTransactionForFullJsonParsedBase & { + message: Readonly<{ + accountKeys: readonly Readonly<{ + pubkey: string; + signer: boolean; + source: 'transaction'; + writable: boolean; + }>[]; + }>; + }; + }; + + type ExpectedTransactionForFullJsonParsedVersioned = { + meta: (ExpectedMetaForFullJsonParsedBase & ExpectedMetaForFullJsonParsedLoadedAddresses) | null; + transaction: ExpectedTransactionForFullJsonParsedBase & { + message: Readonly<{ + accountKeys: readonly Readonly<{ + pubkey: string; + signer: boolean; + source: 'lookupTable' | 'transaction'; + writable: boolean; + }>[]; + }>; + }; + version: TransactionVersion; + }; + + // Seventeenth overload + // Rewards set to `false` + // Max supported transaction version set to 0 + // Encoding set to `jsonParsed` + // Transaction details default to `full` + { + const response = await rpc + .getBlock(0n, { + // No extra configs + encoding: 'jsonParsed', + maxSupportedTransactionVersion: 0, + rewards: false, + }) + .send(); + if (response) { + assertBase(response); + assertNotAProperty(response, 'rewards'); + response.transactions satisfies readonly ExpectedTransactionForFullJsonParsedVersioned[]; + } + } + + // Eighteenth overload + // Rewards set to `false` + // Max supported transaction defaults to `legacy` + // Encoding set to `jsonParsed` + // Transaction details defaults to `full` + { + const response = await rpc + .getBlock(0n, { + // No extra configs + encoding: 'jsonParsed', + rewards: false, + }) + .send(); + if (response) { + assertBase(response); + assertNotAProperty(response, 'rewards'); + response.transactions satisfies readonly ExpectedTransactionForFullJsonParsedLegacy[]; + // @ts-expect-error `version` should be undefined + response.transactions satisfies readonly (ExpectedTransactionForFullJsonParsedLegacy & { + version: 'legacy'; + })[]; + } + } + + // Nineteenth overload + // Rewards defaults to `true` + // Max supported transaction version set to 0 + // Encoding set to `jsonParsed` + // Transaction details defaults to `full` + { + const response = await rpc + .getBlock(0n, { + // No extra configs + encoding: 'jsonParsed', + maxSupportedTransactionVersion: 0, + }) + .send(); + if (response) { + assertBase(response); + response.transactions[0].transaction; + response.transactions satisfies readonly ExpectedTransactionForFullJsonParsedVersioned[]; + response.rewards satisfies readonly Reward[]; + } + } + + // Twentieth overload + // Rewards defaults to `true` + // Max supported transaction defaults to `legacy` + // Encoding set to `jsonParsed` + // Transaction details defaults to `full` + { + const response = await rpc + .getBlock(0n, { + // No extra configs + encoding: 'jsonParsed', + }) + .send(); + if (response) { + assertBase(response); + response.transactions satisfies readonly ExpectedTransactionForFullJsonParsedLegacy[]; + // @ts-expect-error `version` should be undefined + response.transactions satisfies readonly (ExpectedTransactionForFullJsonParsedLegacy & { + version: 'legacy'; + })[]; + response.rewards satisfies readonly Reward[]; + } + } + + type ExpectedTransactionInstructionForFullJson = { + programIdIndex: number; + accounts: readonly number[]; + data: Base58EncodedBytes; + }; + + type ExpectedMetaForFullJsonBase = { + computeUnitsConsumed?: U64UnsafeBeyond2Pow53Minus1; + err: TransactionError | null; + fee: LamportsUnsafeBeyond2Pow53Minus1; + innerInstructions: readonly Readonly<{ + index: number; + instructions: readonly ExpectedTransactionInstructionForFullJson[]; + }>[]; + logMessages: readonly string[] | null; + preBalances: readonly LamportsUnsafeBeyond2Pow53Minus1[]; + postBalances: readonly LamportsUnsafeBeyond2Pow53Minus1[]; + preTokenBalances?: readonly TokenBalance[]; + postTokenBalances?: readonly TokenBalance[]; + rewards: readonly Reward[] | null; + returnData?: Readonly<{ + programId: Base58EncodedAddress; + data: Base64EncodedDataResponse; + }>; + status: TransactionStatus; + }; + + type ExpectedMetaForFullJsonLoadedAddresses = Readonly<{ + loadedAddresses: { + writable: readonly Base58EncodedAddress[]; + readonly: readonly Base58EncodedAddress[]; + }; + }>; + + type ExpectedTransactionForFullJsonBase = { + message: { + accountKeys: readonly Base58EncodedAddress[]; + header: { + numReadonlySignedAccounts: number; + numReadonlyUnsignedAccounts: number; + numRequiredSignatures: number; + }; + instructions: readonly ExpectedTransactionInstructionForFullJson[]; + recentBlockhash: Blockhash; + }; + signatures: readonly Base58EncodedBytes[]; + }; + + type ExpectedTransactionForFullJsonLegacy = { + meta: ExpectedMetaForFullJsonBase | null; + transaction: ExpectedTransactionForFullJsonBase; + }; + + type ExpectedTransactionForFullJsonVersioned = { + meta: (ExpectedMetaForFullJsonBase & ExpectedMetaForFullJsonLoadedAddresses) | null; + transaction: ExpectedTransactionForFullJsonBase; + version: TransactionVersion; + }; + + // Twenty-first overload + // Rewards set to `false` + // Max supported transaction version set to 0 + // Encoding defaults to `json` + // Transaction details default to `full` + { + const response = await rpc + .getBlock(0n, { + // No extra configs + maxSupportedTransactionVersion: 0, + rewards: false, + }) + .send(); + if (response) { + assertBase(response); + assertNotAProperty(response, 'rewards'); + response.transactions satisfies readonly ExpectedTransactionForFullJsonVersioned[]; + } + } + + // Twenty-first overload + // Rewards set to `false` + // Max supported transaction version set to 0 + // Encoding set to `json` + // Transaction details defaults to `full` + { + const response = await rpc + .getBlock(0n, { + // No extra configs + encoding: 'json', + maxSupportedTransactionVersion: 0, + rewards: false, + }) + .send(); + if (response) { + assertBase(response); + assertNotAProperty(response, 'rewards'); + response.transactions satisfies readonly ExpectedTransactionForFullJsonVersioned[]; + } + } + + // Twenty-second overload + // Rewards set to `false` + // Max supported transaction defaults to `legacy` + // Encoding defaults to `json` + // Transaction details defaults to `full` + { + const response = await rpc + .getBlock(0n, { + // No extra configs + rewards: false, + }) + .send(); + if (response) { + assertBase(response); + assertNotAProperty(response, 'rewards'); + response.transactions satisfies readonly ExpectedTransactionForFullJsonLegacy[]; + // @ts-expect-error `version` should be undefined + response.transactions satisfies readonly (ExpectedTransactionForFullJsonLegacy & { + version: 'legacy'; + })[]; + } + } + + // Twenty-third overload + // Rewards defaults to `true` + // Max supported transaction version set to 0 + // Encoding defaults to `json` + // Transaction details defaults to `full` + { + const response = await rpc + .getBlock(0n, { + // No extra configs + maxSupportedTransactionVersion: 0, + }) + .send(); + if (response) { + assertBase(response); + response.transactions satisfies readonly ExpectedTransactionForFullJsonVersioned[]; + response.rewards satisfies readonly Reward[]; + } + } + + // Twenty-fourth overload + // Rewards defaults to `true` + // Max supported transaction defaults to `legacy` + // Encoding defaults to `json` + // Transaction details defaults to `full` + { + const response = await rpc.getBlock(0n).send(); + if (response) { + assertBase(response); + response.transactions satisfies readonly ExpectedTransactionForFullJsonLegacy[]; + // @ts-expect-error `version` should be undefined + response.transactions satisfies readonly (ExpectedTransactionForFullJsonLegacy & { + version: 'legacy'; + })[]; + response.rewards satisfies readonly Reward[]; + } + } + + // Twenty-fourth overload with configs + { + const response = await rpc.getBlock(0n, { commitment: 'confirmed' }).send(); + if (response) { + assertBase(response); + response.transactions satisfies readonly ExpectedTransactionForFullJsonLegacy[]; + // @ts-expect-error `version` should be undefined + response.transactions satisfies readonly (ExpectedTransactionForFullJsonLegacy & { + version: 'legacy'; + })[]; + response.rewards satisfies readonly Reward[]; + } + } +}; diff --git a/packages/rpc-core/src/rpc-methods/common.ts b/packages/rpc-core/src/rpc-methods/common.ts index 031827a236e4..ac752a8d8348 100644 --- a/packages/rpc-core/src/rpc-methods/common.ts +++ b/packages/rpc-core/src/rpc-methods/common.ts @@ -2,6 +2,7 @@ import { Base58EncodedAddress } from '@solana/addresses'; import { StringifiedBigInt } from '../stringified-bigint'; import { StringifiedNumber } from '../stringified-number'; +import { TransactionError } from '../transaction-error'; export type Commitment = 'confirmed' | 'finalized' | 'processed'; @@ -29,6 +30,11 @@ export type Slot = U64UnsafeBeyond2Pow53Minus1; // the JSON-RPC transport. export type U64UnsafeBeyond2Pow53Minus1 = bigint; +// FIXME(solana-labs/solana/issues/30341) Beware that any value outside of range +// +/- 9007199254740991 may be truncated or rounded because of a downcast to JavaScript `number` +// between your calling code and the JSON-RPC transport. +export type SignedLamportsAsI64Unsafe = bigint; + // FIXME(solana-labs/solana/issues/30341) // // Beware that floating-point value precision can vary widely: @@ -109,6 +115,18 @@ export type TokenAmount = Readonly<{ uiAmountString: StringifiedNumber; }>; +export type TokenBalance = Readonly<{ + /** Index of the account in which the token balance is provided for. */ + accountIndex: number; + /** Pubkey of the token's mint. */ + mint: Base58EncodedAddress; + /** Pubkey of token balance's owner. */ + owner?: Base58EncodedAddress; + /** Pubkey of the Token program that owns the account. */ + programId?: Base58EncodedAddress; + uiTokenAmount: TokenAmount; +}>; + type TokenAccountState = 'initialized' | 'uninitialized' | 'frozen'; export type TokenAccount = Readonly<{ @@ -123,3 +141,30 @@ export type TokenAccount = Readonly<{ closeAuthority?: Base58EncodedAddress; extensions?: unknown[]; }>; + +type RewardBase = Readonly<{ + /** The public key of the account that received the reward */ + pubkey: Base58EncodedAddress; + /** number of reward lamports credited or debited by the account */ + lamports: SignedLamportsAsI64Unsafe; + /** account balance in lamports after the reward was applied */ + postBalance: LamportsUnsafeBeyond2Pow53Minus1; +}>; + +export type Reward = + | (RewardBase & + Readonly<{ + /** type of reward */ + rewardType: 'fee' | 'rent'; + }>) + /** Commission is present only for voting and staking rewards */ + | (RewardBase & + Readonly<{ + /** type of reward */ + rewardType: 'voting' | 'staking'; + /** vote account commission when the reward was credited */ + commission: number; + }>); + +/** @deprecated */ +export type TransactionStatus = { Ok: null } | { Err: TransactionError }; diff --git a/packages/rpc-core/src/rpc-methods/getBlock.ts b/packages/rpc-core/src/rpc-methods/getBlock.ts new file mode 100644 index 000000000000..8f4427bf75b8 --- /dev/null +++ b/packages/rpc-core/src/rpc-methods/getBlock.ts @@ -0,0 +1,663 @@ +import { Base58EncodedAddress } from '@solana/addresses'; +import { Blockhash, TransactionVersion } from '@solana/transactions'; + +import { TransactionError } from '../transaction-error'; +import { UnixTimestamp } from '../unix-timestamp'; +import { + Base58EncodedBytes, + Base58EncodedDataResponse, + Base64EncodedDataResponse, + Commitment, + LamportsUnsafeBeyond2Pow53Minus1, + Reward, + Slot, + TokenBalance, + TransactionStatus, + U64UnsafeBeyond2Pow53Minus1, +} from './common'; + +// Shared transaction components + +type AddressTableLookup = Readonly<{ + /** public key for an address lookup table account. */ + accountKey: Base58EncodedAddress; + /** List of indices used to load addresses of writable accounts from a lookup table. */ + writableIndexes: readonly number[]; + /** List of indices used to load addresses of readonly accounts from a lookup table. */ + readableIndexes: readonly number[]; +}>; + +type ParsedTransactionInstruction = Readonly<{ + parsed: { + type: string; + info?: object; + }; + program: string; + programId: Base58EncodedAddress; +}>; + +type PartiallyDecodedTransactionInstruction = Readonly<{ + accounts: readonly Base58EncodedAddress[]; + data: Base58EncodedBytes; + programId: Base58EncodedAddress; +}>; + +type ReturnData = { + /** the program that generated the return data */ + programId: Base58EncodedAddress; + /** the return data itself */ + data: Base64EncodedDataResponse; +}; + +type TransactionInstruction = Readonly<{ + accounts: readonly number[]; + data: Base58EncodedBytes; + programIdIndex: number; +}>; + +type TransactionParsedAccountLegacy = Readonly<{ + pubkey: Base58EncodedAddress; + signer: boolean; + source: 'transaction'; + writable: boolean; +}>; +type TransactionParsedAccountVersioned = Readonly<{ + pubkey: Base58EncodedAddress; + signer: boolean; + source: 'lookupTable' | 'transaction'; + writable: boolean; +}>; + +// Types for `accounts` transactionDetails + +// Only a partial version of the `TransactionMetaBase` type for when `transactionDetails: accounts` is provided. +type TransactionForAccountsMetaBase = Readonly<{ + /** Error if transaction failed, null if transaction succeeded. */ + err: TransactionError | null; + /** fee this transaction was charged */ + fee: LamportsUnsafeBeyond2Pow53Minus1; + /** array of account balances from before the transaction was processed */ + preBalances: readonly LamportsUnsafeBeyond2Pow53Minus1[]; + /** array of account balances after the transaction was processed */ + postBalances: readonly LamportsUnsafeBeyond2Pow53Minus1[]; + /** List of token balances from before the transaction was processed or omitted if token balance recording was not yet enabled during this transaction */ + preTokenBalances?: readonly TokenBalance[]; + /** List of token balances from after the transaction was processed or omitted if token balance recording was not yet enabled during this transaction */ + postTokenBalances?: readonly TokenBalance[]; + /** + * Transaction status + * @deprecated + */ + status: TransactionStatus; +}>; + +// Accounts + +type TransactionForAccounts = + TMaxSupportedTransactionVersion extends void + ? Readonly<{ + /** Transaction partial meta */ + meta: TransactionForAccountsMetaBase | null; + /** Partial transactions */ + transaction: Readonly<{ + /** Parsed accounts */ + accountKeys: readonly TransactionParsedAccountLegacy[]; + /** Account signatures */ + signatures: readonly Base58EncodedBytes[]; + }>; + }> + : Readonly<{ + /** Transaction partial meta */ + meta: TransactionForAccountsMetaBase | null; + /** Partial transactions */ + transaction: Readonly<{ + /** Parsed accounts */ + accountKeys: readonly TransactionParsedAccountVersioned[]; + /** Account signatures */ + signatures: readonly Base58EncodedBytes[]; + }>; + /** The transaction version */ + version: TransactionVersion; + }>; + +// Types for `full` transactionDetails + +type TransactionForFullMetaBase = Readonly<{ + /** number of compute units consumed by the transaction */ + computeUnitsConsumed?: U64UnsafeBeyond2Pow53Minus1; + /** Error if transaction failed, null if transaction succeeded. */ + err: TransactionError | null; + /** fee this transaction was charged */ + fee: LamportsUnsafeBeyond2Pow53Minus1; + /** array of string log messages or null if log message recording was not enabled during this transaction */ + logMessages: readonly string[] | null; + /** array of account balances from before the transaction was processed */ + preBalances: readonly LamportsUnsafeBeyond2Pow53Minus1[]; + /** array of account balances after the transaction was processed */ + postBalances: readonly LamportsUnsafeBeyond2Pow53Minus1[]; + /** List of token balances from before the transaction was processed or omitted if token balance recording was not yet enabled during this transaction */ + preTokenBalances?: readonly TokenBalance[]; + /** List of token balances from after the transaction was processed or omitted if token balance recording was not yet enabled during this transaction */ + postTokenBalances?: readonly TokenBalance[]; + /** the most-recent return data generated by an instruction in the transaction */ + returnData?: ReturnData; + /** transaction-level rewards */ + rewards: readonly Reward[] | null; + /** + * Transaction status + * @deprecated + */ + status: TransactionStatus; +}>; + +type TransactionForFullMetaInnerInstructionsUnparsed = Readonly<{ + innerInstructions: readonly Readonly<{ + /** The index of the instruction in the transaction */ + index: number; + /** The instruction */ + instructions: readonly TransactionInstruction[]; + }>[]; +}>; + +type TransactionForFullMetaInnerInstructionsParsed = Readonly<{ + innerInstructions: readonly Readonly<{ + /** The index of the instruction in the transaction */ + index: number; + /** The instruction */ + instructions: readonly (ParsedTransactionInstruction | PartiallyDecodedTransactionInstruction)[]; + }>[]; +}>; + +// According to the RPC docs: "Transaction addresses loaded from address lookup tables. +// Undefined if maxSupportedTransactionVersion is not set in request params, or if jsonParsed +// encoding is set in request params." +type TransactionForFullMetaLoadedAddresses = Readonly<{ + /** Addresses loaded from lookup tables */ + loadedAddresses: { + writable: readonly Base58EncodedAddress[]; + readonly: readonly Base58EncodedAddress[]; + }; +}>; + +type TransactionForFullTransactionAddressTableLookups = Readonly<{ + message: { + addressTableLookups?: readonly AddressTableLookup[] | null; + }; +}>; + +// Base58 + +type TransactionForFullBase58 = + TMaxSupportedTransactionVersion extends void + ? Readonly<{ + /** Transaction meta */ + meta: (TransactionForFullMetaBase & TransactionForFullMetaInnerInstructionsUnparsed) | null; + /** Partial transactions */ + transaction: Base58EncodedDataResponse; + }> + : Readonly<{ + /** Transaction meta */ + meta: + | (TransactionForFullMetaBase & + TransactionForFullMetaInnerInstructionsUnparsed & + TransactionForFullMetaLoadedAddresses) + | null; + /** Partial transactions */ + transaction: Base58EncodedDataResponse; + /** The transaction version */ + version: TransactionVersion; + }>; + +// Base64 + +type TransactionForFullBase64 = + TMaxSupportedTransactionVersion extends void + ? Readonly<{ + /** Transaction meta */ + meta: (TransactionForFullMetaBase & TransactionForFullMetaInnerInstructionsUnparsed) | null; + /** Partial transactions */ + transaction: Base64EncodedDataResponse; + }> + : Readonly<{ + /** Transaction meta */ + meta: + | (TransactionForFullMetaBase & + TransactionForFullMetaInnerInstructionsUnparsed & + TransactionForFullMetaLoadedAddresses) + | null; + /** Partial transactions */ + transaction: Base64EncodedDataResponse; + /** The transaction version */ + version: TransactionVersion; + }>; + +// JsonParsed + +type TransactionForFullTransactionJsonParsedBase = Readonly<{ + message: { + header: { + numReadonlySignedAccounts: number; + numReadonlyUnsignedAccounts: number; + numRequiredSignatures: number; + }; + instructions: readonly (ParsedTransactionInstruction | PartiallyDecodedTransactionInstruction)[]; + recentBlockhash: Blockhash; + }; + signatures: readonly Base58EncodedBytes[]; +}>; +type TransactionForFullJsonParsed = + TMaxSupportedTransactionVersion extends void + ? Readonly<{ + meta: (TransactionForFullMetaBase & TransactionForFullMetaInnerInstructionsParsed) | null; + transaction: TransactionForFullTransactionJsonParsedBase & { + message: Readonly<{ + accountKeys: readonly TransactionParsedAccountLegacy[]; + }>; + }; + }> + : Readonly<{ + meta: + | (TransactionForFullMetaBase & + TransactionForFullMetaInnerInstructionsParsed & + TransactionForFullMetaLoadedAddresses) + | null; + transaction: TransactionForFullTransactionJsonParsedBase & { + message: Readonly<{ + accountKeys: readonly TransactionParsedAccountLegacy[]; + }>; + }; + version: TransactionVersion; + }>; + +// Json + +type TransactionForFullTransactionJsonBase = Readonly<{ + message: { + accountKeys: readonly Base58EncodedAddress[]; + header: { + numReadonlySignedAccounts: number; + numReadonlyUnsignedAccounts: number; + numRequiredSignatures: number; + }; + instructions: readonly TransactionInstruction[]; + recentBlockhash: Blockhash; + }; + signatures: readonly Base58EncodedBytes[]; +}>; +type TransactionForFullJson = + TMaxSupportedTransactionVersion extends void + ? Readonly<{ + meta: (TransactionForFullMetaBase & TransactionForFullMetaInnerInstructionsUnparsed) | null; + transaction: TransactionForFullTransactionJsonBase; + }> + : Readonly<{ + meta: + | (TransactionForFullMetaBase & + TransactionForFullMetaInnerInstructionsUnparsed & + TransactionForFullMetaLoadedAddresses) + | null; + transaction: TransactionForFullTransactionJsonBase & TransactionForFullTransactionAddressTableLookups; + version: TransactionVersion; + }>; + +// API response types + +type GetBlockApiResponseBase = Readonly<{ + /** the blockhash of this block */ + blockhash: Blockhash; + /** The number of blocks beneath this block */ + blockHeight: U64UnsafeBeyond2Pow53Minus1; + /** The number of blocks beneath this block */ + blockTime: UnixTimestamp; + /** The slot index of this block's parent */ + parentSlot: Slot; + /** The blockhash of this block's parent */ + previousBlockhash: Blockhash; +}>; + +type GetBlockApiResponseWithRewards = Readonly<{ + /** Block-level rewards */ + rewards: readonly Reward[]; +}>; + +type GetBlockApiResponseWithSignatures = Readonly<{ + /** List of signatures applied to transactions in this block */ + signatures: readonly Base58EncodedBytes[]; +}>; + +type GetBlockApiResponseWithTransactions = Readonly<{ + transactions: readonly TTransaction[]; +}>; + +// API parameter types + +type GetBlockCommonConfig = Readonly<{ + /** @defaultValue finalized */ + commitment?: Omit; +}>; + +type GetBlockEncoding = 'base58' | 'base64' | 'json' | 'jsonParsed'; + +// Max supported transaction version parameter: +// - `maxSupportedTransactionVersion` can only be provided with a number value. "legacy" is not a valid argument. +// This will throw a parse error (code -32602). +// - If `maxSupportedTransactionVersion` is not provided, the default value is "legacy". +// This will error if the block contains any transactions with a version greater than "legacy" (code -32015). +// - Also, If `maxSupportedTransactionVersion` is not provided, the `version` field of each transaction is omitted. +// - These rules apply to both "accounts" and "full" transaction details. +type GetBlockMaxSupportedTransactionVersion = Exclude; + +export interface GetBlockApi { + /** + * Returns identity and transaction information about a confirmed block in the ledger + */ + // transactionDetails=none, rewards=false, encoding + maxSupportedTransactionVersion irrelevant + getBlock( + slot: Slot, + config: GetBlockCommonConfig & + Readonly<{ + encoding?: GetBlockEncoding; + maxSupportedTransactionVersion?: GetBlockMaxSupportedTransactionVersion; + rewards: false; + transactionDetails: 'none'; + }> + ): GetBlockApiResponseBase | null; + // transactionDetails=none, rewards=missing/true, encoding + maxSupportedTransactionVersion irrelevant + getBlock( + slot: Slot, + config: GetBlockCommonConfig & + Readonly<{ + encoding?: GetBlockEncoding; + maxSupportedTransactionVersion?: GetBlockMaxSupportedTransactionVersion; + rewards?: true; + transactionDetails: 'none'; + }> + ): (GetBlockApiResponseBase & GetBlockApiResponseWithRewards) | null; + // transactionDetails=signatures, rewards=false, encoding + maxSupportedTransactionVersion irrelevant + getBlock( + slot: Slot, + config: GetBlockCommonConfig & + Readonly<{ + encoding?: GetBlockEncoding; + maxSupportedTransactionVersion?: GetBlockMaxSupportedTransactionVersion; + rewards: false; + transactionDetails: 'signatures'; + }> + ): (GetBlockApiResponseBase & GetBlockApiResponseWithSignatures) | null; + // transactionDetails=signatures, rewards=missing/true, encoding + maxSupportedTransactionVersion irrelevant + getBlock( + slot: Slot, + config: GetBlockCommonConfig & + Readonly<{ + encoding?: GetBlockEncoding; + maxSupportedTransactionVersion?: GetBlockMaxSupportedTransactionVersion; + rewards?: true; + transactionDetails: 'signatures'; + }> + ): (GetBlockApiResponseBase & GetBlockApiResponseWithRewards & GetBlockApiResponseWithSignatures) | null; + // transactionDetails=accounts, rewards=false, maxSupportedTransactionVersion=0, encoding irrelevant + getBlock( + slot: Slot, + config: GetBlockCommonConfig & + Readonly<{ + encoding?: GetBlockEncoding; + maxSupportedTransactionVersion: GetBlockMaxSupportedTransactionVersion; + rewards: false; + transactionDetails: 'accounts'; + }> + ): + | (GetBlockApiResponseBase & + GetBlockApiResponseWithTransactions>) + | null; + // // transactionDetails=accounts, rewards=false, maxSupportedTransactionVersion=missing, encoding irrelevant + getBlock( + slot: Slot, + config: GetBlockCommonConfig & + Readonly<{ + encoding?: GetBlockEncoding; + rewards: false; + transactionDetails: 'accounts'; + }> + ): (GetBlockApiResponseBase & GetBlockApiResponseWithTransactions>) | null; + // transactionDetails=accounts, rewards=missing/true, maxSupportedTransactionVersion=0, encoding irrelevant + getBlock( + slot: Slot, + config: GetBlockCommonConfig & + Readonly<{ + encoding?: GetBlockEncoding; + maxSupportedTransactionVersion: GetBlockMaxSupportedTransactionVersion; + rewards?: true; + transactionDetails: 'accounts'; + }> + ): + | (GetBlockApiResponseBase & + GetBlockApiResponseWithRewards & + GetBlockApiResponseWithTransactions>) + | null; + // transactionDetails=accounts, rewards=missing/true, maxSupportedTransactionVersion=missing, encoding irrelevant + getBlock( + slot: Slot, + config: GetBlockCommonConfig & + Readonly<{ + encoding?: GetBlockEncoding; + rewards?: true; + transactionDetails: 'accounts'; + }> + ): + | (GetBlockApiResponseBase & + GetBlockApiResponseWithRewards & + GetBlockApiResponseWithTransactions>) + | null; + // transactionDetails=full (default), encoding=base58, rewards=false, maxSupportedTransactionVersion=0 + getBlock( + slot: Slot, + config: GetBlockCommonConfig & + Readonly<{ + encoding: 'base58'; + maxSupportedTransactionVersion: GetBlockMaxSupportedTransactionVersion; + rewards: false; + transactionDetails?: 'full'; + }> + ): + | (GetBlockApiResponseBase & + GetBlockApiResponseWithTransactions>) + | null; + // transactionDetails=full (default), encoding=base58, rewards=false, maxSupportedTransactionVersion=missing + getBlock( + slot: Slot, + config: GetBlockCommonConfig & + Readonly<{ + encoding: 'base58'; + rewards: false; + transactionDetails?: 'full'; + }> + ): (GetBlockApiResponseBase & GetBlockApiResponseWithTransactions>) | null; + // transactionDetails=full (default), encoding=base58, rewards=missing/true, maxSupportedTransactionVersion=0 + getBlock( + slot: Slot, + config: GetBlockCommonConfig & + Readonly<{ + encoding: 'base58'; + maxSupportedTransactionVersion: GetBlockMaxSupportedTransactionVersion; + rewards?: true; + transactionDetails?: 'full'; + }> + ): + | (GetBlockApiResponseBase & + GetBlockApiResponseWithRewards & + GetBlockApiResponseWithTransactions>) + | null; + // transactionDetails=full (default), encoding=base58, rewards=missing/true, maxSupportedTransactionVersion=missing + getBlock( + slot: Slot, + config: GetBlockCommonConfig & + Readonly<{ + encoding: 'base58'; + rewards?: true; + transactionDetails?: 'full'; + }> + ): + | (GetBlockApiResponseBase & + GetBlockApiResponseWithRewards & + GetBlockApiResponseWithTransactions>) + | null; + // transactionDetails=full (default), encoding=base64, rewards=false, maxSupportedTransactionVersion=0 + getBlock( + slot: Slot, + config: GetBlockCommonConfig & + Readonly<{ + encoding: 'base64'; + maxSupportedTransactionVersion: GetBlockMaxSupportedTransactionVersion; + rewards: false; + transactionDetails?: 'full'; + }> + ): + | (GetBlockApiResponseBase & + GetBlockApiResponseWithTransactions>) + | null; + // transactionDetails=full (default), encoding=base64, rewards=false, maxSupportedTransactionVersion=missing + getBlock( + slot: Slot, + config: GetBlockCommonConfig & + Readonly<{ + encoding: 'base64'; + rewards: false; + transactionDetails?: 'full'; + }> + ): (GetBlockApiResponseBase & GetBlockApiResponseWithTransactions>) | null; + // transactionDetails=full (default), encoding=base64, rewards=missing/true, maxSupportedTransactionVersion=0 + getBlock( + slot: Slot, + config: GetBlockCommonConfig & + Readonly<{ + encoding: 'base64'; + maxSupportedTransactionVersion: GetBlockMaxSupportedTransactionVersion; + rewards?: true; + transactionDetails?: 'full'; + }> + ): + | (GetBlockApiResponseBase & + GetBlockApiResponseWithRewards & + GetBlockApiResponseWithTransactions>) + | null; + // transactionDetails=full (default), encoding=base64, rewards=missing/true, maxSupportedTransactionVersion=missing + getBlock( + slot: Slot, + config: GetBlockCommonConfig & + Readonly<{ + encoding: 'base64'; + rewards?: true; + transactionDetails?: 'full'; + }> + ): + | (GetBlockApiResponseBase & + GetBlockApiResponseWithRewards & + GetBlockApiResponseWithTransactions>) + | null; + // transactionDetails=full (default), encoding=jsonParsed, rewards=false, maxSupportedTransactionVersion=0 + getBlock( + slot: Slot, + config: GetBlockCommonConfig & + Readonly<{ + encoding: 'jsonParsed'; + maxSupportedTransactionVersion: GetBlockMaxSupportedTransactionVersion; + rewards: false; + transactionDetails?: 'full'; + }> + ): + | (GetBlockApiResponseBase & + GetBlockApiResponseWithTransactions>) + | null; + // transactionDetails=full (default), encoding=jsonParsed, rewards=false, maxSupportedTransactionVersion=missing + getBlock( + slot: Slot, + config: GetBlockCommonConfig & + Readonly<{ + encoding: 'jsonParsed'; + rewards: false; + transactionDetails?: 'full'; + }> + ): (GetBlockApiResponseBase & GetBlockApiResponseWithTransactions>) | null; + // transactionDetails=full (default), encoding=jsonParsed, rewards=missing/true, maxSupportedTransactionVersion=0 + getBlock( + slot: Slot, + config: GetBlockCommonConfig & + Readonly<{ + encoding: 'jsonParsed'; + maxSupportedTransactionVersion: GetBlockMaxSupportedTransactionVersion; + rewards?: boolean; + transactionDetails?: 'full'; + }> + ): + | (GetBlockApiResponseBase & + GetBlockApiResponseWithRewards & + GetBlockApiResponseWithTransactions>) + | null; + // transactionDetails=full (default), encoding=jsonParsed, rewards=missing/true, maxSupportedTransactionVersion=missing + getBlock( + slot: Slot, + config: GetBlockCommonConfig & + Readonly<{ + encoding: 'jsonParsed'; + rewards?: boolean; + transactionDetails?: 'full'; + }> + ): + | (GetBlockApiResponseBase & + GetBlockApiResponseWithRewards & + GetBlockApiResponseWithTransactions>) + | null; + // transactionDetails=full (default), encoding=json (default), rewards=false, maxSupportedTransactionVersion=0 + getBlock( + slot: Slot, + config: GetBlockCommonConfig & + Readonly<{ + encoding?: 'json'; + maxSupportedTransactionVersion: GetBlockMaxSupportedTransactionVersion; + rewards: false; + transactionDetails?: 'full'; + }> + ): + | (GetBlockApiResponseBase & + GetBlockApiResponseWithTransactions>) + | null; + // transactionDetails=full (default), encoding=json (default), rewards=false, maxSupportedTransactionVersion=missing + getBlock( + slot: Slot, + config: GetBlockCommonConfig & + Readonly<{ + encoding?: 'json'; + rewards: false; + transactionDetails?: 'full'; + }> + ): (GetBlockApiResponseBase & GetBlockApiResponseWithTransactions>) | null; + // transactionDetails=full (default), encoding=json (default), rewards=missing/true, maxSupportedTransactionVersion=0 + getBlock( + slot: Slot, + config: GetBlockCommonConfig & + Readonly<{ + encoding?: 'json'; + maxSupportedTransactionVersion: GetBlockMaxSupportedTransactionVersion; + rewards?: boolean; + transactionDetails?: 'full'; + }> + ): + | (GetBlockApiResponseBase & + GetBlockApiResponseWithRewards & + GetBlockApiResponseWithTransactions>) + | null; + // transactionDetails=full (default), encoding=json (default), rewards=missing/true, maxSupportedTransactionVersion=missing + getBlock( + slot: Slot, + config?: GetBlockCommonConfig & + Readonly<{ + encoding?: 'json'; + rewards?: boolean; + transactionDetails?: 'full'; + }> + ): + | (GetBlockApiResponseBase & + GetBlockApiResponseWithRewards & + GetBlockApiResponseWithTransactions>) + | null; +} diff --git a/packages/rpc-core/src/rpc-methods/getTransaction.ts b/packages/rpc-core/src/rpc-methods/getTransaction.ts index d49adc27dddb..a4adb2e5f5bd 100644 --- a/packages/rpc-core/src/rpc-methods/getTransaction.ts +++ b/packages/rpc-core/src/rpc-methods/getTransaction.ts @@ -9,52 +9,13 @@ import { Base64EncodedDataResponse, Commitment, LamportsUnsafeBeyond2Pow53Minus1, + Reward, Slot, - TokenAmount, + TokenBalance, + TransactionStatus, U64UnsafeBeyond2Pow53Minus1, } from './common'; -type TokenBalance = Readonly<{ - /** Index of the account in which the token balance is provided for. */ - accountIndex: number; - /** Pubkey of the token's mint. */ - mint: Base58EncodedAddress; - /** Pubkey of token balance's owner. */ - owner?: Base58EncodedAddress; - /** Pubkey of the Token program that owns the account. */ - programId?: Base58EncodedAddress; - uiTokenAmount: TokenAmount; -}>; - -type TransactionRewardBase = Readonly<{ - /** The public key of the account that received the reward */ - pubkey: Base58EncodedAddress; - /** number of reward lamports credited or debited by the account */ - lamports: LamportsUnsafeBeyond2Pow53Minus1; - /** account balance in lamports after the reward was applied */ - postBalance: LamportsUnsafeBeyond2Pow53Minus1; -}>; - -type TransactionRewardWithoutCommission = TransactionRewardBase & - Readonly<{ - /** type of reward */ - rewardType: 'fee' | 'rent'; - }>; - -/** Commission is present only for voting and staking rewards */ -type TransactionRewardWithCommission = TransactionRewardBase & - Readonly<{ - /** type of reward */ - rewardType: 'voting' | 'staking'; - /** vote account commission when the reward was credited */ - commission: number; - }>; - -type TransactionReward = TransactionRewardWithoutCommission | TransactionRewardWithCommission; - -/** @deprecated */ -type TransactionStatus = { Ok: null } | { Err: TransactionError }; - type ReturnData = { /** the program that generated the return data */ programId: Base58EncodedAddress; @@ -78,7 +39,7 @@ type TransactionMetaBase = Readonly<{ /** array of string log messages or null if log message recording was not enabled during this transaction */ logMessages: readonly string[] | null; /** transaction-level rewards */ - rewards: readonly TransactionReward[] | null; + rewards: readonly Reward[] | null; /** * Transaction status * @deprecated diff --git a/packages/rpc-core/src/rpc-methods/index.ts b/packages/rpc-core/src/rpc-methods/index.ts index 4b55675189f0..44970f378cad 100644 --- a/packages/rpc-core/src/rpc-methods/index.ts +++ b/packages/rpc-core/src/rpc-methods/index.ts @@ -4,6 +4,7 @@ import { patchParamsForSolanaLabsRpc } from '../params-patcher'; import { patchResponseForSolanaLabsRpc } from '../response-patcher'; import { GetAccountInfoApi } from './getAccountInfo'; import { GetBalanceApi } from './getBalance'; +import { GetBlockApi } from './getBlock'; import { GetBlockCommitmentApi } from './getBlockCommitment'; import { GetBlockHeightApi } from './getBlockHeight'; import { GetBlockProductionApi } from './getBlockProduction'; @@ -61,6 +62,7 @@ type Config = Readonly<{ export type SolanaRpcMethods = GetAccountInfoApi & GetBalanceApi & + GetBlockApi & GetBlockCommitmentApi & GetBlockHeightApi & GetBlockProductionApi & diff --git a/packages/rpc-transport/src/json-rpc-types.ts b/packages/rpc-transport/src/json-rpc-types.ts index 6cf2988667f0..b0fb3449421e 100644 --- a/packages/rpc-transport/src/json-rpc-types.ts +++ b/packages/rpc-transport/src/json-rpc-types.ts @@ -54,7 +54,7 @@ type PendingRpcRequestBuilder = UnionToIntersection< type Callable = (...args: any[]) => any; type Flatten = T extends (infer Item)[] ? Item : never; type Overloads = - // Have an RPC method with more than 10 overloads? Add another section and update this comment + // Have an RPC method with more than 24 overloads? Add another section and update this comment T extends { (...args: infer A1): infer R1; (...args: infer A2): infer R2; @@ -66,7 +66,553 @@ type Overloads = (...args: infer A8): infer R8; (...args: infer A9): infer R9; (...args: infer A10): infer R10; + (...args: infer A11): infer R11; + (...args: infer A12): infer R12; + (...args: infer A13): infer R13; + (...args: infer A14): infer R14; + (...args: infer A15): infer R15; + (...args: infer A16): infer R16; + (...args: infer A17): infer R17; + (...args: infer A18): infer R18; + (...args: infer A19): infer R19; + (...args: infer A20): infer R20; + (...args: infer A21): infer R21; + (...args: infer A22): infer R22; + (...args: infer A23): infer R23; + (...args: infer A24): infer R24; } + ? [ + (...args: A1) => R1, + (...args: A2) => R2, + (...args: A3) => R3, + (...args: A4) => R4, + (...args: A5) => R5, + (...args: A6) => R6, + (...args: A7) => R7, + (...args: A8) => R8, + (...args: A9) => R9, + (...args: A10) => R10, + (...args: A11) => R11, + (...args: A12) => R12, + (...args: A13) => R13, + (...args: A14) => R14, + (...args: A15) => R15, + (...args: A16) => R16, + (...args: A17) => R17, + (...args: A18) => R18, + (...args: A19) => R19, + (...args: A20) => R20, + (...args: A21) => R21, + (...args: A22) => R22, + (...args: A23) => R23, + (...args: A24) => R24 + ] + : T extends { + (...args: infer A1): infer R1; + (...args: infer A2): infer R2; + (...args: infer A3): infer R3; + (...args: infer A4): infer R4; + (...args: infer A5): infer R5; + (...args: infer A6): infer R6; + (...args: infer A7): infer R7; + (...args: infer A8): infer R8; + (...args: infer A9): infer R9; + (...args: infer A10): infer R10; + (...args: infer A11): infer R11; + (...args: infer A12): infer R12; + (...args: infer A13): infer R13; + (...args: infer A14): infer R14; + (...args: infer A15): infer R15; + (...args: infer A16): infer R16; + (...args: infer A17): infer R17; + (...args: infer A18): infer R18; + (...args: infer A19): infer R19; + (...args: infer A20): infer R20; + (...args: infer A21): infer R21; + (...args: infer A22): infer R22; + (...args: infer A23): infer R23; + } + ? [ + (...args: A1) => R1, + (...args: A2) => R2, + (...args: A3) => R3, + (...args: A4) => R4, + (...args: A5) => R5, + (...args: A6) => R6, + (...args: A7) => R7, + (...args: A8) => R8, + (...args: A9) => R9, + (...args: A10) => R10, + (...args: A11) => R11, + (...args: A12) => R12, + (...args: A13) => R13, + (...args: A14) => R14, + (...args: A15) => R15, + (...args: A16) => R16, + (...args: A17) => R17, + (...args: A18) => R18, + (...args: A19) => R19, + (...args: A20) => R20, + (...args: A21) => R21, + (...args: A22) => R22, + (...args: A23) => R23 + ] + : T extends { + (...args: infer A1): infer R1; + (...args: infer A2): infer R2; + (...args: infer A3): infer R3; + (...args: infer A4): infer R4; + (...args: infer A5): infer R5; + (...args: infer A6): infer R6; + (...args: infer A7): infer R7; + (...args: infer A8): infer R8; + (...args: infer A9): infer R9; + (...args: infer A10): infer R10; + (...args: infer A11): infer R11; + (...args: infer A12): infer R12; + (...args: infer A13): infer R13; + (...args: infer A14): infer R14; + (...args: infer A15): infer R15; + (...args: infer A16): infer R16; + (...args: infer A17): infer R17; + (...args: infer A18): infer R18; + (...args: infer A19): infer R19; + (...args: infer A20): infer R20; + (...args: infer A21): infer R21; + (...args: infer A22): infer R22; + } + ? [ + (...args: A1) => R1, + (...args: A2) => R2, + (...args: A3) => R3, + (...args: A4) => R4, + (...args: A5) => R5, + (...args: A6) => R6, + (...args: A7) => R7, + (...args: A8) => R8, + (...args: A9) => R9, + (...args: A10) => R10, + (...args: A11) => R11, + (...args: A12) => R12, + (...args: A13) => R13, + (...args: A14) => R14, + (...args: A15) => R15, + (...args: A16) => R16, + (...args: A17) => R17, + (...args: A18) => R18, + (...args: A19) => R19, + (...args: A20) => R20, + (...args: A21) => R21, + (...args: A22) => R22 + ] + : T extends { + (...args: infer A1): infer R1; + (...args: infer A2): infer R2; + (...args: infer A3): infer R3; + (...args: infer A4): infer R4; + (...args: infer A5): infer R5; + (...args: infer A6): infer R6; + (...args: infer A7): infer R7; + (...args: infer A8): infer R8; + (...args: infer A9): infer R9; + (...args: infer A10): infer R10; + (...args: infer A11): infer R11; + (...args: infer A12): infer R12; + (...args: infer A13): infer R13; + (...args: infer A14): infer R14; + (...args: infer A15): infer R15; + (...args: infer A16): infer R16; + (...args: infer A17): infer R17; + (...args: infer A18): infer R18; + (...args: infer A19): infer R19; + (...args: infer A20): infer R20; + (...args: infer A21): infer R21; + } + ? [ + (...args: A1) => R1, + (...args: A2) => R2, + (...args: A3) => R3, + (...args: A4) => R4, + (...args: A5) => R5, + (...args: A6) => R6, + (...args: A7) => R7, + (...args: A8) => R8, + (...args: A9) => R9, + (...args: A10) => R10, + (...args: A11) => R11, + (...args: A12) => R12, + (...args: A13) => R13, + (...args: A14) => R14, + (...args: A15) => R15, + (...args: A16) => R16, + (...args: A17) => R17, + (...args: A18) => R18, + (...args: A19) => R19, + (...args: A20) => R20, + (...args: A21) => R21 + ] + : T extends { + (...args: infer A1): infer R1; + (...args: infer A2): infer R2; + (...args: infer A3): infer R3; + (...args: infer A4): infer R4; + (...args: infer A5): infer R5; + (...args: infer A6): infer R6; + (...args: infer A7): infer R7; + (...args: infer A8): infer R8; + (...args: infer A9): infer R9; + (...args: infer A10): infer R10; + (...args: infer A11): infer R11; + (...args: infer A12): infer R12; + (...args: infer A13): infer R13; + (...args: infer A14): infer R14; + (...args: infer A15): infer R15; + (...args: infer A16): infer R16; + (...args: infer A17): infer R17; + (...args: infer A18): infer R18; + (...args: infer A19): infer R19; + (...args: infer A20): infer R20; + } + ? [ + (...args: A1) => R1, + (...args: A2) => R2, + (...args: A3) => R3, + (...args: A4) => R4, + (...args: A5) => R5, + (...args: A6) => R6, + (...args: A7) => R7, + (...args: A8) => R8, + (...args: A9) => R9, + (...args: A10) => R10, + (...args: A11) => R11, + (...args: A12) => R12, + (...args: A13) => R13, + (...args: A14) => R14, + (...args: A15) => R15, + (...args: A16) => R16, + (...args: A17) => R17, + (...args: A18) => R18, + (...args: A19) => R19, + (...args: A20) => R20 + ] + : T extends { + (...args: infer A1): infer R1; + (...args: infer A2): infer R2; + (...args: infer A3): infer R3; + (...args: infer A4): infer R4; + (...args: infer A5): infer R5; + (...args: infer A6): infer R6; + (...args: infer A7): infer R7; + (...args: infer A8): infer R8; + (...args: infer A9): infer R9; + (...args: infer A10): infer R10; + (...args: infer A11): infer R11; + (...args: infer A12): infer R12; + (...args: infer A13): infer R13; + (...args: infer A14): infer R14; + (...args: infer A15): infer R15; + (...args: infer A16): infer R16; + (...args: infer A17): infer R17; + (...args: infer A18): infer R18; + (...args: infer A19): infer R19; + } + ? [ + (...args: A1) => R1, + (...args: A2) => R2, + (...args: A3) => R3, + (...args: A4) => R4, + (...args: A5) => R5, + (...args: A6) => R6, + (...args: A7) => R7, + (...args: A8) => R8, + (...args: A9) => R9, + (...args: A10) => R10, + (...args: A11) => R11, + (...args: A12) => R12, + (...args: A13) => R13, + (...args: A14) => R14, + (...args: A15) => R15, + (...args: A16) => R16, + (...args: A17) => R17, + (...args: A18) => R18, + (...args: A19) => R19 + ] + : T extends { + (...args: infer A1): infer R1; + (...args: infer A2): infer R2; + (...args: infer A3): infer R3; + (...args: infer A4): infer R4; + (...args: infer A5): infer R5; + (...args: infer A6): infer R6; + (...args: infer A7): infer R7; + (...args: infer A8): infer R8; + (...args: infer A9): infer R9; + (...args: infer A10): infer R10; + (...args: infer A11): infer R11; + (...args: infer A12): infer R12; + (...args: infer A13): infer R13; + (...args: infer A14): infer R14; + (...args: infer A15): infer R15; + (...args: infer A16): infer R16; + (...args: infer A17): infer R17; + (...args: infer A18): infer R18; + } + ? [ + (...args: A1) => R1, + (...args: A2) => R2, + (...args: A3) => R3, + (...args: A4) => R4, + (...args: A5) => R5, + (...args: A6) => R6, + (...args: A7) => R7, + (...args: A8) => R8, + (...args: A9) => R9, + (...args: A10) => R10, + (...args: A11) => R11, + (...args: A12) => R12, + (...args: A13) => R13, + (...args: A14) => R14, + (...args: A15) => R15, + (...args: A16) => R16, + (...args: A17) => R17, + (...args: A18) => R18 + ] + : T extends { + (...args: infer A1): infer R1; + (...args: infer A2): infer R2; + (...args: infer A3): infer R3; + (...args: infer A4): infer R4; + (...args: infer A5): infer R5; + (...args: infer A6): infer R6; + (...args: infer A7): infer R7; + (...args: infer A8): infer R8; + (...args: infer A9): infer R9; + (...args: infer A10): infer R10; + (...args: infer A11): infer R11; + (...args: infer A12): infer R12; + (...args: infer A13): infer R13; + (...args: infer A14): infer R14; + (...args: infer A15): infer R15; + (...args: infer A16): infer R16; + (...args: infer A17): infer R17; + } + ? [ + (...args: A1) => R1, + (...args: A2) => R2, + (...args: A3) => R3, + (...args: A4) => R4, + (...args: A5) => R5, + (...args: A6) => R6, + (...args: A7) => R7, + (...args: A8) => R8, + (...args: A9) => R9, + (...args: A10) => R10, + (...args: A11) => R11, + (...args: A12) => R12, + (...args: A13) => R13, + (...args: A14) => R14, + (...args: A15) => R15, + (...args: A16) => R16, + (...args: A17) => R17 + ] + : T extends { + (...args: infer A1): infer R1; + (...args: infer A2): infer R2; + (...args: infer A3): infer R3; + (...args: infer A4): infer R4; + (...args: infer A5): infer R5; + (...args: infer A6): infer R6; + (...args: infer A7): infer R7; + (...args: infer A8): infer R8; + (...args: infer A9): infer R9; + (...args: infer A10): infer R10; + (...args: infer A11): infer R11; + (...args: infer A12): infer R12; + (...args: infer A13): infer R13; + (...args: infer A14): infer R14; + (...args: infer A15): infer R15; + (...args: infer A16): infer R16; + } + ? [ + (...args: A1) => R1, + (...args: A2) => R2, + (...args: A3) => R3, + (...args: A4) => R4, + (...args: A5) => R5, + (...args: A6) => R6, + (...args: A7) => R7, + (...args: A8) => R8, + (...args: A9) => R9, + (...args: A10) => R10, + (...args: A11) => R11, + (...args: A12) => R12, + (...args: A13) => R13, + (...args: A14) => R14, + (...args: A15) => R15, + (...args: A16) => R16 + ] + : T extends { + (...args: infer A1): infer R1; + (...args: infer A2): infer R2; + (...args: infer A3): infer R3; + (...args: infer A4): infer R4; + (...args: infer A5): infer R5; + (...args: infer A6): infer R6; + (...args: infer A7): infer R7; + (...args: infer A8): infer R8; + (...args: infer A9): infer R9; + (...args: infer A10): infer R10; + (...args: infer A11): infer R11; + (...args: infer A12): infer R12; + (...args: infer A13): infer R13; + (...args: infer A14): infer R14; + (...args: infer A15): infer R15; + } + ? [ + (...args: A1) => R1, + (...args: A2) => R2, + (...args: A3) => R3, + (...args: A4) => R4, + (...args: A5) => R5, + (...args: A6) => R6, + (...args: A7) => R7, + (...args: A8) => R8, + (...args: A9) => R9, + (...args: A10) => R10, + (...args: A11) => R11, + (...args: A12) => R12, + (...args: A13) => R13, + (...args: A14) => R14, + (...args: A15) => R15 + ] + : T extends { + (...args: infer A1): infer R1; + (...args: infer A2): infer R2; + (...args: infer A3): infer R3; + (...args: infer A4): infer R4; + (...args: infer A5): infer R5; + (...args: infer A6): infer R6; + (...args: infer A7): infer R7; + (...args: infer A8): infer R8; + (...args: infer A9): infer R9; + (...args: infer A10): infer R10; + (...args: infer A11): infer R11; + (...args: infer A12): infer R12; + (...args: infer A13): infer R13; + (...args: infer A14): infer R14; + } + ? [ + (...args: A1) => R1, + (...args: A2) => R2, + (...args: A3) => R3, + (...args: A4) => R4, + (...args: A5) => R5, + (...args: A6) => R6, + (...args: A7) => R7, + (...args: A8) => R8, + (...args: A9) => R9, + (...args: A10) => R10, + (...args: A11) => R11, + (...args: A12) => R12, + (...args: A13) => R13, + (...args: A14) => R14 + ] + : T extends { + (...args: infer A1): infer R1; + (...args: infer A2): infer R2; + (...args: infer A3): infer R3; + (...args: infer A4): infer R4; + (...args: infer A5): infer R5; + (...args: infer A6): infer R6; + (...args: infer A7): infer R7; + (...args: infer A8): infer R8; + (...args: infer A9): infer R9; + (...args: infer A10): infer R10; + (...args: infer A11): infer R11; + (...args: infer A12): infer R12; + (...args: infer A13): infer R13; + } + ? [ + (...args: A1) => R1, + (...args: A2) => R2, + (...args: A3) => R3, + (...args: A4) => R4, + (...args: A5) => R5, + (...args: A6) => R6, + (...args: A7) => R7, + (...args: A8) => R8, + (...args: A9) => R9, + (...args: A10) => R10, + (...args: A11) => R11, + (...args: A12) => R12, + (...args: A13) => R13 + ] + : T extends { + (...args: infer A1): infer R1; + (...args: infer A2): infer R2; + (...args: infer A3): infer R3; + (...args: infer A4): infer R4; + (...args: infer A5): infer R5; + (...args: infer A6): infer R6; + (...args: infer A7): infer R7; + (...args: infer A8): infer R8; + (...args: infer A9): infer R9; + (...args: infer A10): infer R10; + (...args: infer A11): infer R11; + (...args: infer A12): infer R12; + } + ? [ + (...args: A1) => R1, + (...args: A2) => R2, + (...args: A3) => R3, + (...args: A4) => R4, + (...args: A5) => R5, + (...args: A6) => R6, + (...args: A7) => R7, + (...args: A8) => R8, + (...args: A9) => R9, + (...args: A10) => R10, + (...args: A11) => R11, + (...args: A12) => R12 + ] + : T extends { + (...args: infer A1): infer R1; + (...args: infer A2): infer R2; + (...args: infer A3): infer R3; + (...args: infer A4): infer R4; + (...args: infer A5): infer R5; + (...args: infer A6): infer R6; + (...args: infer A7): infer R7; + (...args: infer A8): infer R8; + (...args: infer A9): infer R9; + (...args: infer A10): infer R10; + (...args: infer A11): infer R11; + } + ? [ + (...args: A1) => R1, + (...args: A2) => R2, + (...args: A3) => R3, + (...args: A4) => R4, + (...args: A5) => R5, + (...args: A6) => R6, + (...args: A7) => R7, + (...args: A8) => R8, + (...args: A9) => R9, + (...args: A10) => R10, + (...args: A11) => R11 + ] + : T extends { + (...args: infer A1): infer R1; + (...args: infer A2): infer R2; + (...args: infer A3): infer R3; + (...args: infer A4): infer R4; + (...args: infer A5): infer R5; + (...args: infer A6): infer R6; + (...args: infer A7): infer R7; + (...args: infer A8): infer R8; + (...args: infer A9): infer R9; + (...args: infer A10): infer R10; + } ? [ (...args: A1) => R1, (...args: A2) => R2,