Skip to content

Commit

Permalink
Clean up SolanaRpcApi: no longer extend RpcApiMethods (#3213)
Browse files Browse the repository at this point in the history
Extending `RpcApiMethods` removes the ability to access typed keys on `SolanaRpcApi` etc, because it's typed as `[method: string]: RpcApiMethod`. 

In this PR we change from:
```ts
interface GetAssetApi extends RpcApiMethods {
```

To:
```ts
type GetAssetApi = {
```

(Thanks @lorisleiva!)

This allows `createRpcApi` to maintain its typing, meaning that it still constrains any API methods we attempt to build an RPC for. But we now have typesafe `keyof SolanaRpcApi` etc. 

I've also removed the export of `RpcApiMethods`, since this should now be used only as a constraint internally and not a type externally.

I've added a typetest with a bit more detail, but this basically makes this work:
```ts
'getAccountInfo' satisfies keyof SolanaRpcApi;
// @ts-expect-error RPC API does not have this method
'someMadeUpMethod' satisfies keyof SolanaRpcApi;
```

Previously `someMadeUpMethod` would satisfy it, because `keyof SolanaRpcApi` was just string.
  • Loading branch information
mcintyre94 authored Sep 9, 2024
1 parent 3df153c commit 3fc388f
Show file tree
Hide file tree
Showing 68 changed files with 157 additions and 186 deletions.
8 changes: 8 additions & 0 deletions .changeset/green-eels-applaud.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
'@solana/rpc-graphql': patch
'@solana/accounts': patch
'@solana/rpc-spec': patch
'@solana/rpc-api': patch
---

Clean up SolanaRpcApi: no longer extend RpcApiMethods + remove export
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -389,8 +389,8 @@ type GetAssetApiResponse = Readonly<{
/* ...etc... */
}>;

// Set up an interface for the request method.
interface GetAssetApi extends RpcApiMethods {
// Set up a type spec for the request method.
type GetAssetApi = {
// Define the method's name, parameters and response type
getAsset(args: { id: Address }): GetAssetApiResponse;
}
Expand Down
5 changes: 2 additions & 3 deletions examples/rpc-custom-api/src/example.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import {
DEFAULT_RPC_CONFIG,
mainnet,
RpcApi,
RpcApiMethods,
SolanaRpcApiMainnet,
} from '@solana/web3.js';

Expand All @@ -31,13 +30,13 @@ type AssetMetadata = Readonly<{
name: string;
symbol: string;
}>;
interface TritonGetAssetApi extends RpcApiMethods {
type TritonGetAssetApi = {
/**
* Define the ideal developer-facing API as a TypeScript type. Doing so will enable typechecking
* and autocompletion for it on the RPC instance.
*/
getAssetMetadata(address: Address): AssetMetadata;
}
};

/**
* STEP 2: CUSTOM JSON RPC API IMPLEMENTATION
Expand Down
5 changes: 2 additions & 3 deletions packages/accounts/src/rpc-api/getAccountInfo.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import type { Address } from '@solana/addresses';
import type { RpcApiMethods } from '@solana/rpc-spec';
import type {
AccountInfoBase,
AccountInfoWithBase58Bytes,
Expand Down Expand Up @@ -31,7 +30,7 @@ type GetAccountInfoApiSliceableCommonConfig = Readonly<{
dataSlice?: DataSlice;
}>;

export interface GetAccountInfoApi extends RpcApiMethods {
export type GetAccountInfoApi = {
/**
* Returns all information associated with the account of provided public key
*/
Expand Down Expand Up @@ -70,4 +69,4 @@ export interface GetAccountInfoApi extends RpcApiMethods {
address: Address,
config?: GetAccountInfoApiCommonConfig,
): GetAccountInfoApiResponseBase & NestInRpcResponseOrNull<AccountInfoWithBase58Bytes>;
}
};
5 changes: 2 additions & 3 deletions packages/accounts/src/rpc-api/getMultipleAccounts.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import type { Address } from '@solana/addresses';
import type { RpcApiMethods } from '@solana/rpc-spec';
import type {
AccountInfoBase,
AccountInfoWithBase58EncodedData,
Expand All @@ -26,7 +25,7 @@ type GetMultipleAccountsApiSliceableCommonConfig = Readonly<{
dataSlice?: DataSlice;
}>;

export interface GetMultipleAccountsApi extends RpcApiMethods {
export type GetMultipleAccountsApi = {
/**
* Returns the account information for a list of Pubkeys.
*/
Expand Down Expand Up @@ -72,4 +71,4 @@ export interface GetMultipleAccountsApi extends RpcApiMethods {
addresses: Address[],
config?: GetMultipleAccountsApiCommonConfig,
): SolanaRpcResponse<(GetMultipleAccountsApiResponseBase & (AccountInfoWithBase64EncodedData | null))[]>;
}
};
6 changes: 3 additions & 3 deletions packages/rpc-api/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,12 @@

This package contains types that describe the [methods](https://solana.com/docs/rpc/http) of the Solana JSON RPC API, and utilities for creating a `RpcApi` implementation with sensible defaults. It can be used standalone, but it is also exported as part of the Solana JavaScript SDK [`@solana/web3.js@rc`](https://github.com/solana-labs/solana-web3.js/tree/master/packages/library).

Each RPC method is described in terms of a TypeScript interface of the following form:
Each RPC method is described in terms of a TypeScript type of the following form:

```ts
interface ExampleApi extends RpcApiMethods {
type ExampleApi = {
getSomething(address: Address): Something;
}
};
```

A `RpcApi` that implements `ExampleApi` will ultimately expose its defined methods on any `Rpc` that uses it.
Expand Down
4 changes: 2 additions & 2 deletions packages/rpc-api/src/__tests__/get-slot-leaders-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { SOLANA_ERROR__JSON_RPC__INVALID_PARAMS, SolanaError } from '@solana/err
import type { Rpc } from '@solana/rpc-spec';
import path from 'path';

import { GetSlotLeadersApi } from '../index';
import { GetSlotLeadersApi, MinimumLedgerSlotApi } from '../index';
import { createLocalhostSolanaRpc } from './__setup__';

const validatorKeypairPath = path.resolve(__dirname, '../../../../test-ledger/validator-keypair.json');
Expand All @@ -31,7 +31,7 @@ async function getValidatorAddress() {
}

describe('getSlotLeaders', () => {
let rpc: Rpc<GetSlotLeadersApi>;
let rpc: Rpc<GetSlotLeadersApi & MinimumLedgerSlotApi>;
beforeEach(() => {
rpc = createLocalhostSolanaRpc();
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@ import { SOLANA_ERROR__JSON_RPC__INVALID_PARAMS, SolanaError } from '@solana/err
import type { Rpc } from '@solana/rpc-spec';
import type { Commitment } from '@solana/rpc-types';

import { GetTokenLargestAccountsApi } from '../index';
import { GetTokenLargestAccountsApi, GetTokenSupplyApi } from '../index';
import { createLocalhostSolanaRpc } from './__setup__';

const CONTEXT_MATCHER = expect.objectContaining({
slot: expect.any(BigInt),
});

describe('getTokenLargestAccounts', () => {
let rpc: Rpc<GetTokenLargestAccountsApi>;
let rpc: Rpc<GetTokenLargestAccountsApi & GetTokenSupplyApi>;
beforeEach(() => {
rpc = createLocalhostSolanaRpc();
});
Expand Down
18 changes: 18 additions & 0 deletions packages/rpc-api/src/__typetests__/rpc-api-type-test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { SolanaRpcApi, SolanaRpcApiDevnet, SolanaRpcApiMainnet, SolanaRpcApiTestnet } from '..';

'getAccountInfo' satisfies keyof SolanaRpcApi;
// @ts-expect-error RPC API does not have this method
'someMadeUpMethod' satisfies keyof SolanaRpcApi;

// if we extend the RPC API with additional methods, we can access them on keyof
type TestRpcApi = SolanaRpcApi & {
someMadeUpMethod: () => void;
};
'someMadeUpMethod' satisfies keyof TestRpcApi;

// request airdrop is available on test networks, but not mainnet
'requestAirdrop' satisfies keyof SolanaRpcApiDevnet;
'requestAirdrop' satisfies keyof SolanaRpcApiTestnet;
'requestAirdrop' satisfies keyof SolanaRpcApi;
// @ts-expect-error requestAirdrop is not available on mainnet
'requestAirdrop' satisfies keyof SolanaRpcApiMainnet;
5 changes: 2 additions & 3 deletions packages/rpc-api/src/getAccountInfo.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import type { Address } from '@solana/addresses';
import type { RpcApiMethods } from '@solana/rpc-spec';
import type {
AccountInfoBase,
AccountInfoWithBase58Bytes,
Expand Down Expand Up @@ -27,7 +26,7 @@ type GetAccountInfoApiSliceableCommonConfig = Readonly<{
dataSlice?: DataSlice;
}>;

export interface GetAccountInfoApi extends RpcApiMethods {
export type GetAccountInfoApi = {
/**
* Returns all information associated with the account of provided public key
*/
Expand Down Expand Up @@ -66,4 +65,4 @@ export interface GetAccountInfoApi extends RpcApiMethods {
address: Address,
config?: GetAccountInfoApiCommonConfig,
): SolanaRpcResponse<GetAccountInfoApiResponse<AccountInfoWithBase58Bytes>>;
}
};
5 changes: 2 additions & 3 deletions packages/rpc-api/src/getBalance.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import type { Address } from '@solana/addresses';
import type { RpcApiMethods } from '@solana/rpc-spec';
import type { Commitment, LamportsUnsafeBeyond2Pow53Minus1, Slot, SolanaRpcResponse } from '@solana/rpc-types';

type GetBalanceApiResponse = SolanaRpcResponse<LamportsUnsafeBeyond2Pow53Minus1>;

export interface GetBalanceApi extends RpcApiMethods {
export type GetBalanceApi = {
/**
* Returns the balance of the account of provided Pubkey
*/
Expand All @@ -15,4 +14,4 @@ export interface GetBalanceApi extends RpcApiMethods {
minContextSlot?: Slot;
}>,
): GetBalanceApiResponse;
}
};
5 changes: 2 additions & 3 deletions packages/rpc-api/src/getBlock.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import type { RpcApiMethods } from '@solana/rpc-spec';
import type {
Base58EncodedBytes,
Blockhash,
Expand Down Expand Up @@ -62,7 +61,7 @@ type GetBlockEncoding = 'base58' | 'base64' | 'json' | 'jsonParsed';
// - These rules apply to both "accounts" and "full" transaction details.
type GetBlockMaxSupportedTransactionVersion = Exclude<TransactionVersion, 'legacy'>;

export interface GetBlockApi extends RpcApiMethods {
export type GetBlockApi = {
/**
* Returns identity and transaction information about a confirmed block in the ledger
*/
Expand Down Expand Up @@ -375,4 +374,4 @@ export interface GetBlockApi extends RpcApiMethods {
GetBlockApiResponseWithRewards &
GetBlockApiResponseWithTransactions<TransactionForFullJson<void>>)
| null;
}
};
5 changes: 2 additions & 3 deletions packages/rpc-api/src/getBlockCommitment.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
import type { RpcApiMethods } from '@solana/rpc-spec';
import type { LamportsUnsafeBeyond2Pow53Minus1, Slot } from '@solana/rpc-types';

type GetBlockCommitmentApiResponse = Readonly<{
commitment: LamportsUnsafeBeyond2Pow53Minus1[] | null;
totalStake: LamportsUnsafeBeyond2Pow53Minus1;
}>;

export interface GetBlockCommitmentApi extends RpcApiMethods {
export type GetBlockCommitmentApi = {
/**
* Returns the amount of cluster stake in lamports that has voted on
* a particular block, as well as the stake attributed to each vote account
*/
getBlockCommitment(slot: Slot): GetBlockCommitmentApiResponse;
}
};
5 changes: 2 additions & 3 deletions packages/rpc-api/src/getBlockHeight.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import type { RpcApiMethods } from '@solana/rpc-spec';
import type { Commitment, Slot, U64UnsafeBeyond2Pow53Minus1 } from '@solana/rpc-types';

type GetBlockHeightApiResponse = U64UnsafeBeyond2Pow53Minus1;

export interface GetBlockHeightApi extends RpcApiMethods {
export type GetBlockHeightApi = {
/**
* Returns the current block height of the node
*/
Expand All @@ -15,4 +14,4 @@ export interface GetBlockHeightApi extends RpcApiMethods {
minContextSlot?: Slot;
}>,
): GetBlockHeightApiResponse;
}
};
5 changes: 2 additions & 3 deletions packages/rpc-api/src/getBlockProduction.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import type { Address } from '@solana/addresses';
import type { RpcApiMethods } from '@solana/rpc-spec';
import type { Commitment, Slot, SolanaRpcResponse, U64UnsafeBeyond2Pow53Minus1 } from '@solana/rpc-types';

type NumberOfLeaderSlots = U64UnsafeBeyond2Pow53Minus1;
Expand Down Expand Up @@ -32,7 +31,7 @@ type GetBlockProductionApiResponse<T> = Readonly<{
range: SlotRange;
}>;

export interface GetBlockProductionApi extends RpcApiMethods {
export type GetBlockProductionApi = {
/**
* Returns recent block production information from the current or previous epoch.
*/
Expand All @@ -45,4 +44,4 @@ export interface GetBlockProductionApi extends RpcApiMethods {
getBlockProduction(
config?: GetBlockProductionApiConfigBase,
): SolanaRpcResponse<GetBlockProductionApiResponse<BlockProductionWithAllIdentities>>;
}
};
5 changes: 2 additions & 3 deletions packages/rpc-api/src/getBlockTime.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
import type { RpcApiMethods } from '@solana/rpc-spec';
import type { Slot, UnixTimestampUnsafeBeyond2Pow53Minus1 } from '@solana/rpc-types';

/** Estimated production time, as Unix timestamp (seconds since the Unix epoch) */
type GetBlockTimeApiResponse = UnixTimestampUnsafeBeyond2Pow53Minus1;

export interface GetBlockTimeApi extends RpcApiMethods {
export type GetBlockTimeApi = {
/**
* Returns the estimated production time of a block.
*/
getBlockTime(
/** block number, identified by Slot */
blockNumber: Slot,
): GetBlockTimeApiResponse;
}
};
5 changes: 2 additions & 3 deletions packages/rpc-api/src/getBlocks.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import type { RpcApiMethods } from '@solana/rpc-spec';
import type { Commitment, Slot } from '@solana/rpc-types';

type GetBlocksApiResponse = Slot[];

export interface GetBlocksApi extends RpcApiMethods {
export type GetBlocksApi = {
/**
* Returns a list of confirmed blocks between two slots
*/
Expand All @@ -15,4 +14,4 @@ export interface GetBlocksApi extends RpcApiMethods {
commitment?: Exclude<Commitment, 'processed'>;
}>,
): GetBlocksApiResponse;
}
};
5 changes: 2 additions & 3 deletions packages/rpc-api/src/getBlocksWithLimit.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import type { RpcApiMethods } from '@solana/rpc-spec';
import type { Commitment, Slot } from '@solana/rpc-types';

type GetBlocksWithLimitApiResponse = Slot[];

export interface GetBlocksWithLimitApi extends RpcApiMethods {
export type GetBlocksWithLimitApi = {
/**
* Returns a list of confirmed blocks starting at the given slot
* for up to `limit` blocks
Expand All @@ -18,4 +17,4 @@ export interface GetBlocksWithLimitApi extends RpcApiMethods {
commitment?: Exclude<Commitment, 'processed'>;
}>,
): GetBlocksWithLimitApiResponse;
}
};
5 changes: 2 additions & 3 deletions packages/rpc-api/src/getClusterNodes.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { Address } from '@solana/addresses';
import type { RpcApiMethods } from '@solana/rpc-spec';

type ClusterNode = Readonly<{
/** The unique identifier of the node's feature set */
Expand Down Expand Up @@ -40,7 +39,7 @@ type ClusterNode = Readonly<{

type GetClusterNodesApiResponse = readonly ClusterNode[];

export interface GetClusterNodesApi extends RpcApiMethods {
export type GetClusterNodesApi = {
/**
* Returns information about all the nodes participating in the cluster
* Note that the optional NO_CONFIG object is ignored. See https://github.com/solana-labs/solana-web3.js/issues/1389
Expand All @@ -49,4 +48,4 @@ export interface GetClusterNodesApi extends RpcApiMethods {
// FIXME: https://github.com/solana-labs/solana-web3.js/issues/1389
NO_CONFIG?: Record<string, never>,
): GetClusterNodesApiResponse;
}
};
5 changes: 2 additions & 3 deletions packages/rpc-api/src/getEpochInfo.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import type { RpcApiMethods } from '@solana/rpc-spec';
import type { Commitment, Slot, U64UnsafeBeyond2Pow53Minus1 } from '@solana/rpc-types';

type GetEpochInfoApiResponse = Readonly<{
Expand All @@ -16,7 +15,7 @@ type GetEpochInfoApiResponse = Readonly<{
transactionCount: U64UnsafeBeyond2Pow53Minus1 | null;
}>;

export interface GetEpochInfoApi extends RpcApiMethods {
export type GetEpochInfoApi = {
/**
* Returns the balance of the account of provided Pubkey
*/
Expand All @@ -26,4 +25,4 @@ export interface GetEpochInfoApi extends RpcApiMethods {
minContextSlot?: Slot;
}>,
): GetEpochInfoApiResponse;
}
};
5 changes: 2 additions & 3 deletions packages/rpc-api/src/getEpochSchedule.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import type { RpcApiMethods } from '@solana/rpc-spec';
import type { U64UnsafeBeyond2Pow53Minus1 } from '@solana/rpc-types';

type GetEpochScheduleApiResponse = Readonly<{
Expand All @@ -14,7 +13,7 @@ type GetEpochScheduleApiResponse = Readonly<{
warmup: boolean;
}>;

export interface GetEpochScheduleApi extends RpcApiMethods {
export type GetEpochScheduleApi = {
/**
* Returns the epoch schedule information from this cluster's genesis config
* Note that the optional NO_CONFIG object is ignored. See https://github.com/solana-labs/solana-web3.js/issues/1389
Expand All @@ -23,4 +22,4 @@ export interface GetEpochScheduleApi extends RpcApiMethods {
// FIXME: https://github.com/solana-labs/solana-web3.js/issues/1389
NO_CONFIG?: Record<string, never>,
): GetEpochScheduleApiResponse;
}
};
Loading

0 comments on commit 3fc388f

Please sign in to comment.