diff --git a/.changeset/strong-bananas-attend.md b/.changeset/strong-bananas-attend.md new file mode 100644 index 0000000..ac083ec --- /dev/null +++ b/.changeset/strong-bananas-attend.md @@ -0,0 +1,5 @@ +--- +'@solana/kit-plugin-rpc': minor +--- + +Add granular `rpcConnection`, `rpcSubscriptionsConnection`, `solanaRpcConnection`, and `solanaRpcSubscriptionsConnection` plugins. Deprecate `rpc` and `localhostRpc` in favor of the new granular alternatives. diff --git a/README.md b/README.md index 1618629..eab75bd 100644 --- a/README.md +++ b/README.md @@ -89,12 +89,19 @@ None of the ready-to-use clients fit your needs? No worries! You can **build you ```ts import { createClient } from '@solana/kit'; -import { rpc, rpcAirdrop, rpcTransactionPlanner, rpcTransactionPlanExecutor } from '@solana/kit-plugin-rpc'; +import { + solanaRpcConnection, + solanaRpcSubscriptionsConnection, + rpcAirdrop, + rpcTransactionPlanner, + rpcTransactionPlanExecutor, +} from '@solana/kit-plugin-rpc'; import { payerFromFile } from '@solana/kit-plugin-payer'; import { planAndSendTransactions } from '@solana/kit-plugin-instruction-plan'; const client = await createClient() // An empty client with a `use` method to install plugins. - .use(rpc('https://api.devnet.solana.com')) // Adds `client.rpc` and `client.rpcSubscriptions`. + .use(solanaRpcConnection('https://api.devnet.solana.com')) // Adds `client.rpc`. + .use(solanaRpcSubscriptionsConnection('wss://api.devnet.solana.com')) // Adds `client.rpcSubscriptions`. .use(payerFromFile('path/to/keypair.json')) // Adds `client.payer` using a local keypair file. .use(rpcAirdrop()) // Adds `client.airdrop` to request SOL from faucets. .use(rpcTransactionPlanner()) // Adds `client.transactionPlanner`. @@ -119,12 +126,12 @@ _Do you know any? Please open a PR to add them here!_ This repo provides the following individual plugin packages. You can learn more about each package by following the links to their READMEs below. -| Package | Version | Description | Plugins | -| ------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------- | -| [`@solana/kit-plugin-rpc`](./packages/kit-plugin-rpc) | [![npm](https://img.shields.io/npm/v/@solana/kit-plugin-rpc.svg?style=flat)](https://www.npmjs.com/package/@solana/kit-plugin-rpc) | Connect to Solana clusters | `rpc`, `localhostRpc`, `rpcAirdrop`, `rpcGetMinimumBalance`, `rpcTransactionPlanner`, `rpcTransactionPlanExecutor` | -| [`@solana/kit-plugin-payer`](./packages/kit-plugin-payer) | [![npm](https://img.shields.io/npm/v/@solana/kit-plugin-payer.svg?style=flat)](https://www.npmjs.com/package/@solana/kit-plugin-payer) | Manage transaction fee payers | `payer`, `payerFromFile`, `generatedPayer`, `generatedPayerWithSol` | -| [`@solana/kit-plugin-litesvm`](./packages/kit-plugin-litesvm) | [![npm](https://img.shields.io/npm/v/@solana/kit-plugin-litesvm.svg?style=flat)](https://www.npmjs.com/package/@solana/kit-plugin-litesvm) | LiteSVM support | `litesvm`, `litesvmAirdrop`, `litesvmGetMinimumBalance`, `litesvmTransactionPlanner`, `litesvmTransactionPlanExecutor` | -| [`@solana/kit-plugin-instruction-plan`](./packages/kit-plugin-instruction-plan) | [![npm](https://img.shields.io/npm/v/@solana/kit-plugin-instruction-plan.svg?style=flat)](https://www.npmjs.com/package/@solana/kit-plugin-instruction-plan) | Transaction planning and execution | `transactionPlanner`, `transactionPlanExecutor`, `planAndSendTransactions` | +| Package | Version | Description | Plugins | +| ------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| [`@solana/kit-plugin-rpc`](./packages/kit-plugin-rpc) | [![npm](https://img.shields.io/npm/v/@solana/kit-plugin-rpc.svg?style=flat)](https://www.npmjs.com/package/@solana/kit-plugin-rpc) | Connect to Solana clusters | `rpcConnection`, `rpcSubscriptionsConnection`, `solanaRpcConnection`, `solanaRpcSubscriptionsConnection`, `rpcAirdrop`, `rpcGetMinimumBalance`, `rpcTransactionPlanner`, `rpcTransactionPlanExecutor` | +| [`@solana/kit-plugin-payer`](./packages/kit-plugin-payer) | [![npm](https://img.shields.io/npm/v/@solana/kit-plugin-payer.svg?style=flat)](https://www.npmjs.com/package/@solana/kit-plugin-payer) | Manage transaction fee payers | `payer`, `payerFromFile`, `generatedPayer`, `generatedPayerWithSol` | +| [`@solana/kit-plugin-litesvm`](./packages/kit-plugin-litesvm) | [![npm](https://img.shields.io/npm/v/@solana/kit-plugin-litesvm.svg?style=flat)](https://www.npmjs.com/package/@solana/kit-plugin-litesvm) | LiteSVM support | `litesvm`, `litesvmAirdrop`, `litesvmGetMinimumBalance`, `litesvmTransactionPlanner`, `litesvmTransactionPlanExecutor` | +| [`@solana/kit-plugin-instruction-plan`](./packages/kit-plugin-instruction-plan) | [![npm](https://img.shields.io/npm/v/@solana/kit-plugin-instruction-plan.svg?style=flat)](https://www.npmjs.com/package/@solana/kit-plugin-instruction-plan) | Transaction planning and execution | `transactionPlanner`, `transactionPlanExecutor`, `planAndSendTransactions` | ## Community Plugins diff --git a/packages/kit-plugin-payer/README.md b/packages/kit-plugin-payer/README.md index 5d94f66..3064abd 100644 --- a/packages/kit-plugin-payer/README.md +++ b/packages/kit-plugin-payer/README.md @@ -67,12 +67,14 @@ Note that this plugin requires an airdrop plugin to be installed on the client b ```ts import { createClient } from '@solana/kit'; -import { localhostRpc, rpcAirdrop } from '@solana/kit-plugin-rpc'; +import { solanaRpcConnection, solanaRpcSubscriptionsConnection, rpcAirdrop } from '@solana/kit-plugin-rpc'; import { generatedPayerWithSol } from '@solana/kit-plugin-payer'; const client = await createClient() - .use(localhostRpc()) // or .use(litesvm()).use(litesvmAirdrop()) + .use(solanaRpcConnection('http://127.0.0.1:8899')) + .use(solanaRpcSubscriptionsConnection('ws://127.0.0.1:8900')) .use(rpcAirdrop()) + // or .use(litesvm()).use(litesvmAirdrop()) .use(generatedPayerWithSol(lamports(10_000_000_000n))); // 10 SOL ``` @@ -90,7 +92,7 @@ When no explicit payer is given, this plugin requires an airdrop plugin to be in ```ts import { createClient } from '@solana/kit'; -import { localhostRpc, rpcAirdrop } from '@solana/kit-plugin-rpc'; +import { solanaRpcConnection, solanaRpcSubscriptionsConnection, rpcAirdrop } from '@solana/kit-plugin-rpc'; import { payerOrGeneratedPayer } from '@solana/kit-plugin-payer'; // With an explicit payer — no airdrop needed. @@ -98,8 +100,10 @@ const client = await createClient().use(payerOrGeneratedPayer(mySigner)); // Without a payer — generates one and airdrops 100 SOL. const client = await createClient() - .use(localhostRpc()) // or .use(litesvm()).use(litesvmAirdrop()) + .use(solanaRpcConnection('http://127.0.0.1:8899')) + .use(solanaRpcSubscriptionsConnection('ws://127.0.0.1:8900')) .use(rpcAirdrop()) + // or .use(litesvm()).use(litesvmAirdrop()) .use(payerOrGeneratedPayer(undefined)); ``` diff --git a/packages/kit-plugin-payer/src/index.ts b/packages/kit-plugin-payer/src/index.ts index 9914eab..0830f5c 100644 --- a/packages/kit-plugin-payer/src/index.ts +++ b/packages/kit-plugin-payer/src/index.ts @@ -133,7 +133,7 @@ export function payerFromFile(path: string) { * @example * ```ts * import { createClient } from '@solana/kit'; - * import { localhostRpc, rpcAirdrop } from '@solana/kit-plugin-rpc'; + * import { solanaRpcConnection, solanaRpcSubscriptionsConnection, rpcAirdrop } from '@solana/kit-plugin-rpc'; * import { payerOrGeneratedPayer } from '@solana/kit-plugin-payer'; * * // With an explicit payer. @@ -141,7 +141,8 @@ export function payerFromFile(path: string) { * * // Without a payer — generates one and airdrops 100 SOL. * const client = await createClient() - * .use(localhostRpc()) + * .use(solanaRpcConnection('http://127.0.0.1:8899')) + * .use(solanaRpcSubscriptionsConnection('ws://127.0.0.1:8900')) * .use(rpcAirdrop()) * .use(payerOrGeneratedPayer(undefined)); * ``` diff --git a/packages/kit-plugin-rpc/README.md b/packages/kit-plugin-rpc/README.md index 967a4e5..00465c7 100644 --- a/packages/kit-plugin-rpc/README.md +++ b/packages/kit-plugin-rpc/README.md @@ -15,39 +15,70 @@ This package provides plugins that add RPC functionality to your Kit clients. pnpm install @solana/kit-plugin-rpc ``` -## `rpc` plugin +## `rpcConnection` plugin -The RPC plugin adds `rpc` and `rpcSubscriptions` objects to your Kit client, allowing you to call RPC methods and subscribe to RPC notifications. +The `rpcConnection` plugin sets a provided `Rpc` instance on the client. This is the generic variant that works with any RPC API. ### Installation -To use the `rpc` plugin, you must provide the URL of your desired Solana RPC endpoint. +```ts +import { createClient, createSolanaRpc } from '@solana/kit'; +import { rpcConnection } from '@solana/kit-plugin-rpc'; + +const myRpc = createSolanaRpc('https://api.mainnet-beta.solana.com'); +const client = createClient().use(rpcConnection(myRpc)); +``` + +### Features + +- `rpc`: Call any RPC method using type-safe methods. + ```ts + const { value: latestBlockhash } = await client.rpc.getLatestBlockhash().send(); + ``` + +## `rpcSubscriptionsConnection` plugin + +The `rpcSubscriptionsConnection` plugin sets a provided `RpcSubscriptions` instance on the client. This is the generic variant that works with any RPC Subscriptions API. + +### Installation ```ts -import { createClient } from '@solana/kit'; -import { rpc } from '@solana/kit-plugin-rpc'; +import { createClient, createSolanaRpcSubscriptions } from '@solana/kit'; +import { rpcSubscriptionsConnection } from '@solana/kit-plugin-rpc'; -const client = createClient().use(rpc('https://api.mainnet-beta.solana.com')); +const myRpcSubscriptions = createSolanaRpcSubscriptions('wss://api.mainnet-beta.solana.com'); +const client = createClient().use(rpcSubscriptionsConnection(myRpcSubscriptions)); ``` -Note that you may wrap your RPC URL using the `mainnet`, `devnet`, or `testnet` helpers from `@solana/kit`. When you do, the returned RPC API will be adjusted to match the selected cluster since some RPC features are not available on all clusters. +### Features + +- `rpcSubscriptions`: Subscribe to RPC notifications using async iterators. + ```ts + const slotNotifications = await client.rpcSubscriptions.slotNotifications({ commitment: 'confirmed' }).subscribe(); + for await (const slotNotification of slotNotifications) { + console.log('Got a slot notification', slotNotification); + } + ``` + +## `solanaRpcConnection` plugin + +The `solanaRpcConnection` plugin creates a Solana RPC from a cluster URL and sets it on the client. + +### Installation ```ts -import { mainnet } from '@solana/kit'; +import { createClient } from '@solana/kit'; +import { solanaRpcConnection } from '@solana/kit-plugin-rpc'; -const client = createClient().use(rpc(mainnet('https://api.mainnet-beta.solana.com'))); +const client = createClient().use(solanaRpcConnection('https://api.mainnet-beta.solana.com')); ``` -By default, the WebSocket URL is derived from the RPC's HTTP URL but you may configure it explicitly using the second parameter. This config object can also be used to customize other aspects of RPC Subscriptions behavior. +You may wrap your RPC URL using the `mainnet`, `devnet`, or `testnet` helpers from `@solana/kit`. When you do, the returned RPC API will be adjusted to match the selected cluster since some RPC features are not available on all clusters. ```ts -const client = createClient().use( - rpc('https://my-rpc-url.com', { - url: 'wss://my-rpc-ws-url.com', - minChannels: 5, - maxSubscriptionsPerChannel: 50, - }), -); +import { mainnet } from '@solana/kit'; + +const client = createClient().use(solanaRpcConnection(mainnet('https://api.mainnet-beta.solana.com'))); ``` ### Features @@ -56,30 +87,29 @@ const client = createClient().use( ```ts const { value: latestBlockhash } = await client.rpc.getLatestBlockhash().send(); ``` -- `rpcSubscriptions`: Subscribe to Solana RPC notifications using async iterators. - ```ts - const slotNotifications = await client.rpcSubscriptions.slotNotifications({ commitment: 'confirmed' }).subscribe(); - for await (const slotNotification of slotNotifications) { - console.log('Got a slot notification', slotNotification); - } - ``` -## `localhostRpc` plugin +## `solanaRpcSubscriptionsConnection` plugin -This plugin is an alias for the `rpc` plugin pre-configured to connect to a local Solana validator. +The `solanaRpcSubscriptionsConnection` plugin creates Solana RPC Subscriptions from a cluster URL and sets them on the client. ### Installation ```ts import { createClient } from '@solana/kit'; -import { localhostRpc } from '@solana/kit-plugin-rpc'; +import { solanaRpcSubscriptionsConnection } from '@solana/kit-plugin-rpc'; -const client = createClient().use(localhostRpc()); +const client = createClient().use(solanaRpcSubscriptionsConnection('wss://api.mainnet-beta.solana.com')); ``` ### Features -_See the `rpc` plugin for available features_. +- `rpcSubscriptions`: Subscribe to Solana RPC notifications using async iterators. + ```ts + const slotNotifications = await client.rpcSubscriptions.slotNotifications({ commitment: 'confirmed' }).subscribe(); + for await (const slotNotification of slotNotifications) { + console.log('Got a slot notification', slotNotification); + } + ``` ## `rpcAirdrop` plugin @@ -94,9 +124,12 @@ The client must have `rpc` and `rpcSubscriptions` installed before applying this ```ts import { createClient } from '@solana/kit'; -import { localhostRpc, rpcAirdrop } from '@solana/kit-plugin-rpc'; +import { solanaRpcConnection, solanaRpcSubscriptionsConnection, rpcAirdrop } from '@solana/kit-plugin-rpc'; -const client = createClient().use(localhostRpc()).use(rpcAirdrop()); +const client = createClient() + .use(solanaRpcConnection('http://127.0.0.1:8899')) + .use(solanaRpcSubscriptionsConnection('ws://127.0.0.1:8900')) + .use(rpcAirdrop()); ``` ### Features @@ -116,9 +149,11 @@ The client must have `rpc` installed before applying this plugin. ```ts import { createClient } from '@solana/kit'; -import { rpc, rpcGetMinimumBalance } from '@solana/kit-plugin-rpc'; +import { solanaRpcConnection, rpcGetMinimumBalance } from '@solana/kit-plugin-rpc'; -const client = createClient().use(rpc('https://api.mainnet-beta.solana.com')).use(rpcGetMinimumBalance()); +const client = createClient() + .use(solanaRpcConnection('https://api.mainnet-beta.solana.com')) + .use(rpcGetMinimumBalance()); ``` ### Features @@ -143,11 +178,17 @@ This plugin requires a payer to be set on the client or passed as an option. ```ts import { createClient } from '@solana/kit'; -import { rpc, rpcTransactionPlanner, rpcTransactionPlanExecutor } from '@solana/kit-plugin-rpc'; +import { + solanaRpcConnection, + solanaRpcSubscriptionsConnection, + rpcTransactionPlanner, + rpcTransactionPlanExecutor, +} from '@solana/kit-plugin-rpc'; import { generatedPayer } from '@solana/kit-plugin-payer'; const client = await createClient() - .use(rpc('https://api.mainnet-beta.solana.com')) + .use(solanaRpcConnection('https://api.mainnet-beta.solana.com')) + .use(solanaRpcSubscriptionsConnection('wss://api.mainnet-beta.solana.com')) .use(generatedPayer()) .use(rpcTransactionPlanner()) .use(rpcTransactionPlanExecutor()); @@ -175,11 +216,17 @@ This plugin requires `rpc` and `rpcSubscriptions` to be configured on the client ```ts import { createClient } from '@solana/kit'; -import { rpc, rpcTransactionPlanner, rpcTransactionPlanExecutor } from '@solana/kit-plugin-rpc'; +import { + solanaRpcConnection, + solanaRpcSubscriptionsConnection, + rpcTransactionPlanner, + rpcTransactionPlanExecutor, +} from '@solana/kit-plugin-rpc'; import { generatedPayer } from '@solana/kit-plugin-payer'; const client = await createClient() - .use(rpc('https://api.mainnet-beta.solana.com')) + .use(solanaRpcConnection('https://api.mainnet-beta.solana.com')) + .use(solanaRpcSubscriptionsConnection('wss://api.mainnet-beta.solana.com')) .use(generatedPayer()) .use(rpcTransactionPlanner()) .use(rpcTransactionPlanExecutor()); diff --git a/packages/kit-plugin-rpc/src/airdrop.ts b/packages/kit-plugin-rpc/src/airdrop.ts index 116755f..a12f756 100644 --- a/packages/kit-plugin-rpc/src/airdrop.ts +++ b/packages/kit-plugin-rpc/src/airdrop.ts @@ -10,24 +10,25 @@ type RpcClient = { * RPC and RPC Subscriptions transports. * * The client must already have `rpc` and `rpcSubscriptions` installed - * (e.g. via the {@link rpc} or {@link localhostRpc} plugins). A - * TypeScript error is raised when a mainnet RPC is used because + * (e.g. via {@link solanaRpcConnection} and {@link solanaRpcSubscriptionsConnection}). + * A TypeScript error is raised when a mainnet RPC is used because * airdrop methods are not available on mainnet. * * @example * ```ts * import { createClient } from '@solana/kit'; - * import { localhostRpc, rpcAirdrop } from '@solana/kit-plugin-rpc'; + * import { solanaRpcConnection, solanaRpcSubscriptionsConnection, rpcAirdrop } from '@solana/kit-plugin-rpc'; * * const client = createClient() - * .use(localhostRpc()) + * .use(solanaRpcConnection('http://127.0.0.1:8899')) + * .use(solanaRpcSubscriptionsConnection('ws://127.0.0.1:8900')) * .use(rpcAirdrop()); * * await client.airdrop(myAddress, lamports(1_000_000_000n)); * ``` * - * @see {@link rpc} - * @see {@link localhostRpc} + * @see {@link solanaRpcConnection} + * @see {@link solanaRpcSubscriptionsConnection} */ export function rpcAirdrop() { return (client: T) => { diff --git a/packages/kit-plugin-rpc/src/get-minimum-balance.ts b/packages/kit-plugin-rpc/src/get-minimum-balance.ts index 684dd92..0db987c 100644 --- a/packages/kit-plugin-rpc/src/get-minimum-balance.ts +++ b/packages/kit-plugin-rpc/src/get-minimum-balance.ts @@ -12,23 +12,21 @@ import { * A plugin that adds a `getMinimumBalance` method to the client using * the `getMinimumBalanceForRentExemption` RPC method. * - * The client must already have `rpc` installed (e.g. via the {@link rpc} - * or {@link localhostRpc} plugins). + * The client must already have `rpc` installed (e.g. via {@link solanaRpcConnection}). * * @example * ```ts * import { createClient } from '@solana/kit'; - * import { rpc, rpcGetMinimumBalance } from '@solana/kit-plugin-rpc'; + * import { solanaRpcConnection, rpcGetMinimumBalance } from '@solana/kit-plugin-rpc'; * * const client = createClient() - * .use(rpc('https://api.mainnet-beta.solana.com')) + * .use(solanaRpcConnection('https://api.mainnet-beta.solana.com')) * .use(rpcGetMinimumBalance()); * * const balance = await client.getMinimumBalance(100); * ``` * - * @see {@link rpc} - * @see {@link localhostRpc} + * @see {@link solanaRpcConnection} */ export function rpcGetMinimumBalance() { return >(client: T) => { diff --git a/packages/kit-plugin-rpc/src/index.ts b/packages/kit-plugin-rpc/src/index.ts index 6a93b90..42fc195 100644 --- a/packages/kit-plugin-rpc/src/index.ts +++ b/packages/kit-plugin-rpc/src/index.ts @@ -1,5 +1,6 @@ export * from './airdrop'; export * from './get-minimum-balance'; export * from './rpc'; +export * from './solana-rpc'; export * from './transaction-plan-executor'; export * from './transaction-planner'; diff --git a/packages/kit-plugin-rpc/src/rpc.ts b/packages/kit-plugin-rpc/src/rpc.ts index bb83af0..513d896 100644 --- a/packages/kit-plugin-rpc/src/rpc.ts +++ b/packages/kit-plugin-rpc/src/rpc.ts @@ -1,74 +1,54 @@ -import { - ClusterUrl, - createSolanaRpc, - createSolanaRpcSubscriptions, - DefaultRpcSubscriptionsChannelConfig, - extendClient, -} from '@solana/kit'; +import { extendClient, Rpc, RpcSubscriptions } from '@solana/kit'; /** - * Enhances a client with Solana RPC and RPC Subscriptions capabilities. + * Enhances a client with an RPC connection from a provided {@link Rpc} instance. * - * @param url - The URL of the Solana cluster. - * @param rpcSubscriptionsConfig - Optional configuration for RPC subscriptions. + * This is a generic plugin that works with any RPC API. For a Solana-specific + * alternative that creates the RPC from a URL, see {@link solanaRpcConnection}. + * + * @param rpc - The RPC instance to install on the client. + * @return A plugin that adds `client.rpc`. * * @example * ```ts - * import { createClient } from '@solana/kit'; - * import { rpc } from '@solana/kit-plugin-rpc'; - * - * // Install the RPC plugin. - * const client = createClient().use(rpc('https://api.mainnet-beta.solana.com')); + * import { createClient, createSolanaRpc } from '@solana/kit'; + * import { rpcConnection } from '@solana/kit-plugin-rpc'; * - * // Make RPC calls. + * const myRpc = createSolanaRpc('https://api.mainnet-beta.solana.com'); + * const client = createClient().use(rpcConnection(myRpc)); * const { value: latestBlockhash } = await client.rpc.getLatestBlockhash().send(); - * - * // Subscribe to RPC notifications. - * const slotNotifications = await client.rpcSubscriptions.slotNotifications({ commitment: 'confirmed' }).subscribe(); - * for await (const slotNotification of slotNotifications) { - * console.log('Got a slot notification', slotNotification); - * } * ``` * - * @see {@link localhostRpc} + * @see {@link solanaRpcConnection} + * @see {@link rpcSubscriptionsConnection} */ -export function rpc( - url: TClusterUrl, - rpcSubscriptionsConfig?: DefaultRpcSubscriptionsChannelConfig, -) { - const rpc = createSolanaRpc(url); - const rpcSubscriptionsUrl = rpcSubscriptionsConfig?.url ?? url.replace(/^http/, 'ws'); - const rpcSubscriptions = createSolanaRpcSubscriptions(rpcSubscriptionsUrl, rpcSubscriptionsConfig); - return (client: T) => extendClient(client, { rpc, rpcSubscriptions }); +export function rpcConnection(rpc: Rpc) { + return (client: T) => extendClient(client, { rpc }); } /** - * Enhances a client with Solana RPC and RPC Subscriptions capabilities - * using to a local validator. + * Enhances a client with an RPC Subscriptions connection from a provided + * {@link RpcSubscriptions} instance. + * + * This is a generic plugin that works with any RPC Subscriptions API. For a + * Solana-specific alternative that creates the subscriptions from a URL, + * see {@link solanaRpcSubscriptionsConnection}. * - * @param url - Custom RPC URL. Defaults to `http://127.0.0.1:8899`. - * @param rpcSubscriptionsConfig - Configuration for RPC subscriptions. Defaults to `{ url: 'ws://127.0.0.1:8900' }`. + * @param rpcSubscriptions - The RPC Subscriptions instance to install on the client. + * @return A plugin that adds `client.rpcSubscriptions`. * * @example * ```ts - * import { createClient } from '@solana/kit'; - * import { localhostRpc } from '@solana/kit-plugin-rpc'; - * - * // Install the Localhost RPC plugin. - * const client = createClient().use(localhostRpc()); - * - * // Make RPC calls. - * const { value: latestBlockhash } = await client.rpc.getLatestBlockhash().send(); + * import { createClient, createSolanaRpcSubscriptions } from '@solana/kit'; + * import { rpcSubscriptionsConnection } from '@solana/kit-plugin-rpc'; * - * // Subscribe to RPC notifications. - * const slotNotifications = await client.rpcSubscriptions.slotNotifications({ commitment: 'confirmed' }).subscribe(); - * for await (const slotNotification of slotNotifications) { - * console.log('Got a slot notification', slotNotification); - * } + * const myRpcSubscriptions = createSolanaRpcSubscriptions('wss://api.mainnet-beta.solana.com'); + * const client = createClient().use(rpcSubscriptionsConnection(myRpcSubscriptions)); * ``` * - * @see {@link rpc} + * @see {@link solanaRpcSubscriptionsConnection} + * @see {@link rpcConnection} */ -export function localhostRpc(url?: string, rpcSubscriptionsConfig?: DefaultRpcSubscriptionsChannelConfig) { - return rpc(url ?? 'http://127.0.0.1:8899', rpcSubscriptionsConfig ?? { url: 'ws://127.0.0.1:8900' }); +export function rpcSubscriptionsConnection(rpcSubscriptions: RpcSubscriptions) { + return (client: T) => extendClient(client, { rpcSubscriptions }); } diff --git a/packages/kit-plugin-rpc/src/solana-rpc.ts b/packages/kit-plugin-rpc/src/solana-rpc.ts new file mode 100644 index 0000000..23c578c --- /dev/null +++ b/packages/kit-plugin-rpc/src/solana-rpc.ts @@ -0,0 +1,110 @@ +import { + ClusterUrl, + createSolanaRpc, + createSolanaRpcSubscriptions, + DefaultRpcSubscriptionsChannelConfig, + extendClient, +} from '@solana/kit'; + +import { rpcConnection, rpcSubscriptionsConnection } from './rpc'; + +/** + * Enhances a client with a Solana RPC connection created from a cluster URL. + * + * This plugin creates a Solana RPC using {@link createSolanaRpc} and installs it + * on the client via {@link rpcConnection}. + * + * @param url - The URL of the Solana cluster. + * @param config - Optional configuration forwarded to {@link createSolanaRpc}. + * @return A plugin that adds `client.rpc`. + * + * @example + * ```ts + * import { createClient } from '@solana/kit'; + * import { solanaRpcConnection } from '@solana/kit-plugin-rpc'; + * + * const client = createClient().use(solanaRpcConnection('https://api.mainnet-beta.solana.com')); + * const { value: latestBlockhash } = await client.rpc.getLatestBlockhash().send(); + * ``` + * + * @see {@link solanaRpcSubscriptionsConnection} + * @see {@link rpcConnection} + */ +export function solanaRpcConnection( + url: TClusterUrl, + config?: Parameters>[1], +) { + return rpcConnection(createSolanaRpc(url, config)); +} + +/** + * Enhances a client with a Solana RPC Subscriptions connection created from a cluster URL. + * + * This plugin creates Solana RPC Subscriptions using {@link createSolanaRpcSubscriptions} + * and installs them on the client via {@link rpcSubscriptionsConnection}. + * + * @param url - The URL of the Solana cluster. + * @param config - Optional configuration forwarded to {@link createSolanaRpcSubscriptions}. + * @return A plugin that adds `client.rpcSubscriptions`. + * + * @example + * ```ts + * import { createClient } from '@solana/kit'; + * import { solanaRpcSubscriptionsConnection } from '@solana/kit-plugin-rpc'; + * + * const client = createClient() + * .use(solanaRpcSubscriptionsConnection('wss://api.mainnet-beta.solana.com')); + * ``` + * + * @see {@link solanaRpcConnection} + * @see {@link rpcSubscriptionsConnection} + */ +export function solanaRpcSubscriptionsConnection( + url: TClusterUrl, + config?: Parameters>[1], +) { + return rpcSubscriptionsConnection(createSolanaRpcSubscriptions(url, config)); +} + +/** + * Enhances a client with Solana RPC and RPC Subscriptions capabilities. + * + * @deprecated Use {@link solanaRpcConnection} and {@link solanaRpcSubscriptionsConnection} instead. + * ```ts + * // Before + * const client = createClient().use(rpc('https://api.mainnet-beta.solana.com')); + * + * // After + * const client = createClient() + * .use(solanaRpcConnection('https://api.mainnet-beta.solana.com')) + * .use(solanaRpcSubscriptionsConnection('wss://api.mainnet-beta.solana.com')); + * ``` + */ +export function rpc( + url: TClusterUrl, + rpcSubscriptionsConfig?: DefaultRpcSubscriptionsChannelConfig, +) { + const rpc = createSolanaRpc(url); + const rpcSubscriptionsUrl = rpcSubscriptionsConfig?.url ?? url.replace(/^http/, 'ws'); + const rpcSubscriptions = createSolanaRpcSubscriptions(rpcSubscriptionsUrl, rpcSubscriptionsConfig); + return (client: T) => extendClient(client, { rpc, rpcSubscriptions }); +} + +/** + * Enhances a client with Solana RPC and RPC Subscriptions capabilities + * using a local validator. + * + * @deprecated Use {@link solanaRpcConnection} and {@link solanaRpcSubscriptionsConnection} instead. + * ```ts + * // Before + * const client = createClient().use(localhostRpc()); + * + * // After + * const client = createClient() + * .use(solanaRpcConnection('http://127.0.0.1:8899')) + * .use(solanaRpcSubscriptionsConnection('ws://127.0.0.1:8900')); + * ``` + */ +export function localhostRpc(url?: string, rpcSubscriptionsConfig?: DefaultRpcSubscriptionsChannelConfig) { + return rpc(url ?? 'http://127.0.0.1:8899', rpcSubscriptionsConfig ?? { url: 'ws://127.0.0.1:8900' }); +} diff --git a/packages/kit-plugin-rpc/src/transaction-plan-executor.ts b/packages/kit-plugin-rpc/src/transaction-plan-executor.ts index 1b4468a..53f43a9 100644 --- a/packages/kit-plugin-rpc/src/transaction-plan-executor.ts +++ b/packages/kit-plugin-rpc/src/transaction-plan-executor.ts @@ -38,11 +38,12 @@ const MAX_COMPUTE_UNIT_LIMIT = 1_400_000; * @example * ```ts * import { createClient } from '@solana/kit'; - * import { rpc, rpcTransactionPlanner, rpcTransactionPlanExecutor } from '@solana/kit-plugin-rpc'; + * import { solanaRpcConnection, solanaRpcSubscriptionsConnection, rpcTransactionPlanner, rpcTransactionPlanExecutor } from '@solana/kit-plugin-rpc'; * import { generatedPayer } from '@solana/kit-plugin-payer'; * * const client = await createClient() - * .use(rpc('https://api.mainnet-beta.solana.com')) + * .use(solanaRpcConnection('https://api.mainnet-beta.solana.com')) + * .use(solanaRpcSubscriptionsConnection('wss://api.mainnet-beta.solana.com')) * .use(generatedPayer()) * .use(rpcTransactionPlanner()) * .use(rpcTransactionPlanExecutor()); diff --git a/packages/kit-plugin-rpc/src/transaction-planner.ts b/packages/kit-plugin-rpc/src/transaction-planner.ts index a24367d..4a49cc2 100644 --- a/packages/kit-plugin-rpc/src/transaction-planner.ts +++ b/packages/kit-plugin-rpc/src/transaction-planner.ts @@ -25,11 +25,12 @@ import { * @example * ```ts * import { createClient } from '@solana/kit'; - * import { rpc, rpcTransactionPlanner, rpcTransactionPlanExecutor } from '@solana/kit-plugin-rpc'; + * import { solanaRpcConnection, solanaRpcSubscriptionsConnection, rpcTransactionPlanner, rpcTransactionPlanExecutor } from '@solana/kit-plugin-rpc'; * import { generatedPayer } from '@solana/kit-plugin-payer'; * * const client = await createClient() - * .use(rpc('https://api.mainnet-beta.solana.com')) + * .use(solanaRpcConnection('https://api.mainnet-beta.solana.com')) + * .use(solanaRpcSubscriptionsConnection('wss://api.mainnet-beta.solana.com')) * .use(generatedPayer()) * .use(rpcTransactionPlanner()) * .use(rpcTransactionPlanExecutor()); diff --git a/packages/kit-plugin-rpc/test/airdrop.test.ts b/packages/kit-plugin-rpc/test/airdrop.test.ts index de0c897..f8b3414 100644 --- a/packages/kit-plugin-rpc/test/airdrop.test.ts +++ b/packages/kit-plugin-rpc/test/airdrop.test.ts @@ -1,7 +1,7 @@ import { createClient, mainnet } from '@solana/kit'; import { describe, expect, it, vi } from 'vitest'; -import { localhostRpc, rpc, rpcAirdrop } from '../src'; +import { rpcAirdrop, solanaRpcConnection, solanaRpcSubscriptionsConnection } from '../src'; describe('rpcAirdrop', () => { it('provides an airdrop function that relies on RPCs', () => { @@ -18,19 +18,18 @@ describe('rpcAirdrop', () => { expect(client.airdrop).toBeTypeOf('function'); }); - it('works with an arbitrary RPC', () => { - const client = createClient().use(rpc('https://my-rpc.com')).use(rpcAirdrop()); - expect(client).toHaveProperty('airdrop'); - }); - - it('works with a localhost RPC', () => { - const client = createClient().use(localhostRpc()).use(rpcAirdrop()); + it('works with a Solana RPC connection', () => { + const client = createClient() + .use(solanaRpcConnection('https://my-rpc.com')) + .use(solanaRpcSubscriptionsConnection('wss://my-rpc.com')) + .use(rpcAirdrop()); expect(client).toHaveProperty('airdrop'); }); it('throws a TypeScript error with a mainnet RPC', () => { const client = createClient() - .use(rpc(mainnet('https://my-rpc.com'))) + .use(solanaRpcConnection(mainnet('https://my-rpc.com'))) + .use(solanaRpcSubscriptionsConnection(mainnet('wss://my-rpc.com'))) // @ts-expect-error Airdrop RPC methods are not available on mainnet. .use(rpcAirdrop()); expect(client).toHaveProperty('airdrop'); diff --git a/packages/kit-plugin-rpc/test/get-minimum-balance.test.ts b/packages/kit-plugin-rpc/test/get-minimum-balance.test.ts index e6d0df3..28c3fd9 100644 --- a/packages/kit-plugin-rpc/test/get-minimum-balance.test.ts +++ b/packages/kit-plugin-rpc/test/get-minimum-balance.test.ts @@ -1,7 +1,7 @@ import { BASE_ACCOUNT_SIZE, createClient, lamports } from '@solana/kit'; import { describe, expect, it, vi } from 'vitest'; -import { localhostRpc, rpc, rpcGetMinimumBalance } from '../src'; +import { rpcGetMinimumBalance, solanaRpcConnection } from '../src'; // Default Solana rent: 3_480 lamports/byte/year * 2 years exemption threshold = 6_960 lamports/byte. const LAMPORTS_PER_BYTE = 6_960n; @@ -18,13 +18,8 @@ describe('rpcGetMinimumBalance', () => { expect(client.getMinimumBalance).toBeTypeOf('function'); }); - it('works with an arbitrary RPC', () => { - const client = createClient().use(rpc('https://my-rpc.com')).use(rpcGetMinimumBalance()); - expect(client).toHaveProperty('getMinimumBalance'); - }); - - it('works with a localhost RPC', () => { - const client = createClient().use(localhostRpc()).use(rpcGetMinimumBalance()); + it('works with a Solana RPC connection', () => { + const client = createClient().use(solanaRpcConnection('https://my-rpc.com')).use(rpcGetMinimumBalance()); expect(client).toHaveProperty('getMinimumBalance'); }); diff --git a/packages/kit-plugin-rpc/test/index.test.ts b/packages/kit-plugin-rpc/test/index.test.ts index 144f3d1..00facec 100644 --- a/packages/kit-plugin-rpc/test/index.test.ts +++ b/packages/kit-plugin-rpc/test/index.test.ts @@ -1,34 +1,48 @@ -import { createClient, mainnet } from '@solana/kit'; +import { createClient, createSolanaRpc, createSolanaRpcSubscriptions, mainnet } from '@solana/kit'; import { describe, expect, expectTypeOf, it } from 'vitest'; -import { localhostRpc, rpc } from '../src'; +import { + rpcConnection, + rpcSubscriptionsConnection, + solanaRpcConnection, + solanaRpcSubscriptionsConnection, +} from '../src'; -describe('rpc', () => { - it('initializes the rpc and rpcSubscriptions properties', () => { - const client = createClient().use(rpc('https://api.mainnet-beta.solana.com')); +describe('rpcConnection', () => { + it('sets the provided rpc instance on the client', () => { + const myRpc = createSolanaRpc('https://api.mainnet-beta.solana.com'); + const client = createClient().use(rpcConnection(myRpc)); expect(client).toHaveProperty('rpc'); + expect(client.rpc).toBe(myRpc); + }); +}); + +describe('rpcSubscriptionsConnection', () => { + it('sets the provided rpcSubscriptions instance on the client', () => { + const myRpcSubscriptions = createSolanaRpcSubscriptions('wss://api.mainnet-beta.solana.com'); + const client = createClient().use(rpcSubscriptionsConnection(myRpcSubscriptions)); expect(client).toHaveProperty('rpcSubscriptions'); + expect(client.rpcSubscriptions).toBe(myRpcSubscriptions); + }); +}); + +describe('solanaRpcConnection', () => { + it('creates and sets a Solana RPC from a URL', () => { + const client = createClient().use(solanaRpcConnection('https://api.mainnet-beta.solana.com')); + expect(client).toHaveProperty('rpc'); expect(client.rpc.sendTransaction).toBeTypeOf('function'); - expect(client.rpcSubscriptions.accountNotifications).toBeTypeOf('function'); }); it('narrows the RPC API based on the cluster', () => { - const client = createClient().use(rpc(mainnet('https://api.mainnet-beta.solana.com'))); + const client = createClient().use(solanaRpcConnection(mainnet('https://api.mainnet-beta.solana.com'))); expectTypeOf(client.rpc).not.toHaveProperty('requestAirdrop'); }); }); -describe('localhostRpc', () => { - it('initializes the rpc and rpcSubscriptions properties', () => { - const client = createClient().use(localhostRpc()); - expect(client).toHaveProperty('rpc'); +describe('solanaRpcSubscriptionsConnection', () => { + it('creates and sets Solana RPC Subscriptions from a URL', () => { + const client = createClient().use(solanaRpcSubscriptionsConnection('wss://api.mainnet-beta.solana.com')); expect(client).toHaveProperty('rpcSubscriptions'); - expect(client.rpc.sendTransaction).toBeTypeOf('function'); expect(client.rpcSubscriptions.accountNotifications).toBeTypeOf('function'); }); - - it('has access to airdrop methods', () => { - const client = createClient().use(localhostRpc()); - expectTypeOf(client.rpc).toHaveProperty('requestAirdrop'); - }); }); diff --git a/packages/kit-plugins/README.md b/packages/kit-plugins/README.md index 18734f8..037f67b 100644 --- a/packages/kit-plugins/README.md +++ b/packages/kit-plugins/README.md @@ -10,13 +10,13 @@ > [!WARNING] > This package is deprecated. Install individual plugin packages directly instead, or use the pre-configured client packages for a quick start. > -> | Deprecated import from `@solana/kit-plugins` | Use instead | -> | -------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ | -> | `rpc`, `localhostRpc`, `rpcAirdrop`, `rpcTransactionPlanner`, `rpcTransactionPlanExecutor` | [`@solana/kit-plugin-rpc`](../kit-plugin-rpc) | -> | `payer`, `payerFromFile`, `generatedPayer`, `generatedPayerWithSol`, `payerOrGeneratedPayer` | [`@solana/kit-plugin-payer`](../kit-plugin-payer) | -> | `litesvm`, `litesvmAirdrop`, `litesvmTransactionPlanner`, `litesvmTransactionPlanExecutor` | [`@solana/kit-plugin-litesvm`](../kit-plugin-litesvm) | -> | `transactionPlanner`, `transactionPlanExecutor`, `planAndSendTransactions` | [`@solana/kit-plugin-instruction-plan`](../kit-plugin-instruction-plan) | -> | `airdrop` | [`@solana/kit-plugin-rpc`](../kit-plugin-rpc) or [`@solana/kit-plugin-litesvm`](../kit-plugin-litesvm) | -> | `createDefaultRpcClient` | `createClient` from [`@solana/kit-client-rpc`](../kit-client-rpc) | -> | `createDefaultLocalhostRpcClient` | `createLocalClient` from [`@solana/kit-client-rpc`](../kit-client-rpc) | -> | `createDefaultLiteSVMClient` | `createClient` from [`@solana/kit-client-litesvm`](../kit-client-litesvm) | +> | Deprecated import from `@solana/kit-plugins` | Use instead | +> | -------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | +> | `rpc`, `localhostRpc`, `rpcAirdrop`, `rpcTransactionPlanner`, `rpcTransactionPlanExecutor` | `solanaRpcConnection`, `solanaRpcSubscriptionsConnection`, `rpcAirdrop`, etc. from [`@solana/kit-plugin-rpc`](../kit-plugin-rpc) | +> | `payer`, `payerFromFile`, `generatedPayer`, `generatedPayerWithSol`, `payerOrGeneratedPayer` | [`@solana/kit-plugin-payer`](../kit-plugin-payer) | +> | `litesvm`, `litesvmAirdrop`, `litesvmTransactionPlanner`, `litesvmTransactionPlanExecutor` | [`@solana/kit-plugin-litesvm`](../kit-plugin-litesvm) | +> | `transactionPlanner`, `transactionPlanExecutor`, `planAndSendTransactions` | [`@solana/kit-plugin-instruction-plan`](../kit-plugin-instruction-plan) | +> | `airdrop` | [`@solana/kit-plugin-rpc`](../kit-plugin-rpc) or [`@solana/kit-plugin-litesvm`](../kit-plugin-litesvm) | +> | `createDefaultRpcClient` | `createClient` from [`@solana/kit-client-rpc`](../kit-client-rpc) | +> | `createDefaultLocalhostRpcClient` | `createLocalClient` from [`@solana/kit-client-rpc`](../kit-client-rpc) | +> | `createDefaultLiteSVMClient` | `createClient` from [`@solana/kit-client-litesvm`](../kit-client-litesvm) |