Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/flat-ants-peel.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@solana/kit-react': minor
---

Add `@solana/kit-react` — React bindings for Kit. Providers (`KitClientProvider`, `PluginProvider`, `RpcProvider`, `RpcConnectionProvider`, `LiteSvmProvider`, `PayerProvider` / `IdentityProvider` / `SignerProvider`), live-data hooks (`useBalance`, `useAccount`, `useTransactionConfirmation`), generic data hooks (`useLiveData`, `useSubscription`, `useRequest`), action hooks (`useAction`, `useSendTransaction(s)`, `usePlanTransaction(s)`), and signer hooks (`usePayer`, `useIdentity`).
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ node_modules
# misc
.DS_Store
*.pem
dist

# debug
npm-debug.log*
Expand Down
1 change: 1 addition & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ This is a monorepo managed with [pnpm](https://pnpm.io/) and [Turborepo](https:/
| [`@solana/kit-plugin-litesvm`](./packages/kit-plugin-litesvm) | LiteSVM support plugin. |
| [`@solana/kit-plugin-instruction-plan`](./packages/kit-plugin-instruction-plan) | Transaction planning and execution plugins. |
| [`@solana/kit-plugin-wallet`](./packages/kit-plugin-wallet) | Browser wallet support plugins. |
| [`@solana/kit-react`](./packages/kit-react) | React bindings for Solana Kit — providers and hooks. |

The umbrella package (`@solana/kit-plugins`) is deprecated. It re-exports everything from the individual plugin packages via `export *` statements for backward compatibility, but consumers should import from the individual `kit-plugin-*` packages directly.

Expand Down
27 changes: 27 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,33 @@ This repo provides the following individual plugin packages. You can learn more
| [`@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` |
| [`@solana/kit-plugin-wallet`](./packages/kit-plugin-wallet) | [![npm](https://img.shields.io/npm/v/@solana/kit-plugin-wallet.svg?style=flat)](https://www.npmjs.com/package/@solana/kit-plugin-wallet) | Browser wallet support | `walletSigner`, `walletIdentity`, `walletPayer`, `walletWithoutSigner` |

## React Bindings

For React applications, [`@solana/kit-react`](./packages/kit-react) provides a thin layer over Kit — providers for composing a client and hooks like `useBalance`, `useAccount`, `useSendTransaction`, `useSubscription`, and `useRequest`. See the [package README](./packages/kit-react/README.md) for the full surface.

```sh
pnpm install @solana/kit @solana/kit-react @solana/kit-plugin-rpc
```

```tsx
import { KitClientProvider, SolanaMainnetRpcProvider, useBalance } from '@solana/kit-react';

function BalanceCard({ owner }) {
const { data, status } = useBalance(owner);
return status === 'loading' ? <Spinner /> : <Lamports value={data} />;
}

export function App() {
return (
<KitClientProvider>
<SolanaMainnetRpcProvider rpcUrl="https://api.mainnet-beta.solana.com">
<BalanceCard owner={myAddress} />
</SolanaMainnetRpcProvider>
</KitClientProvider>
);
}
```

## Community Plugins

| Package | Description | Plugins | Maintainers |
Expand Down
4 changes: 4 additions & 0 deletions packages/kit-react/.prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
dist/
test-ledger/
target/
CHANGELOG.md
440 changes: 440 additions & 0 deletions packages/kit-react/README.md

Large diffs are not rendered by default.

88 changes: 88 additions & 0 deletions packages/kit-react/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
{
"name": "@solana/kit-react",
"version": "0.1.0",
"description": "React bindings for Kit: providers, signer hooks, and live on-chain data hooks",
"exports": {
"types": "./dist/types/index.d.ts",
"react-native": "./dist/index.react-native.mjs",
"browser": {
"import": "./dist/index.browser.mjs",
"require": "./dist/index.browser.cjs"
},
"node": {
"import": "./dist/index.node.mjs",
"require": "./dist/index.node.cjs"
}
},
"browser": {
"./dist/index.node.cjs": "./dist/index.browser.cjs",
"./dist/index.node.mjs": "./dist/index.browser.mjs"
},
"main": "./dist/index.node.cjs",
"module": "./dist/index.node.mjs",
"react-native": "./dist/index.react-native.mjs",
"types": "./dist/types/index.d.ts",
"type": "commonjs",
"files": [
"./dist/types",
"./dist/index.*",
"./src/"
],
"sideEffects": false,
"keywords": [
"solana",
"kit",
"react",
"hooks"
],
"scripts": {
"build": "rimraf dist && tsup && tsc -p ./tsconfig.declarations.json",
"dev": "vitest --project node",
"lint": "eslint . && prettier --check .",
"lint:fix": "eslint --fix . && prettier --write .",
"test": "pnpm test:types && pnpm test:unit",
"test:types": "tsc --noEmit",
"test:unit": "vitest run"
},
"peerDependencies": {
"@solana/kit": "^6.8.0",
"@solana/kit-plugin-instruction-plan": "workspace:*",
"@solana/kit-plugin-signer": "workspace:*",
"react": "^18.0.0 || ^19.0.0"
},
"peerDependenciesMeta": {
"@solana/kit-plugin-instruction-plan": {
"optional": true
},
"@solana/kit-plugin-signer": {
"optional": true
}
},
"dependencies": {
"@solana/kit-plugin-litesvm": "workspace:*",
"@solana/kit-plugin-rpc": "workspace:*",
"@solana/subscribable": "^6.8.0",
"@wallet-standard/base": "^1.1.0"
},
"devDependencies": {
"@solana/kit-plugin-instruction-plan": "workspace:*",
"@solana/kit-plugin-signer": "workspace:*",
"@testing-library/react": "^16.1.0",
"@types/react": "^19.0.0",
"@types/react-dom": "^19.0.0",
"react": "^19.0.0",
"react-dom": "^19.0.0"
},
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/anza-xyz/kit-plugins"
},
"bugs": {
"url": "https://github.com/anza-xyz/kit-plugins/issues"
},
"browserslist": [
"supports bigint and not dead",
"maintained node versions"
]
}
67 changes: 67 additions & 0 deletions packages/kit-react/src/client-capability.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import type { Client } from '@solana/kit';

import { useClient } from './client-context';
import { throwMissingCapability } from './errors';

/**
* Options for {@link useClientCapability}.
*/
export type UseClientCapabilityOptions = Readonly<{
/**
* The capability name, or an ordered list of capability names, that
* must be present on the client. Each is checked via `key in client`.
*/
capability: string | readonly string[];
/** Hook name shown as the subject of the error. */
hookName: string;
/** Free-text "how to fix" hint appended to the error. */
providerHint: string;
}>;

/**
* Reads the Kit client from context and asserts that the listed capability
* (or capabilities) are installed.
*
* The TypeScript narrowing is caller-declared: the hook does not infer
* `TClient` from the capability names. Mismatched casts are caller errors,
* same as with {@link useClient}. Hook authors who want a loud
* missing-provider failure at mount should prefer this over
* `useClient<T>()`.
*
* @typeParam TClient - The client shape the caller expects after narrowing.
* @param options - The capability name(s), hook name, and provider hint.
* @return The Kit client cast to `Client<TClient>`.
* @throws An `Error` describing the missing capability when any listed key
* is not present on the client.
*
* @example
* ```ts
* import type { ClientWithRpc, GetEpochInfoApi } from '@solana/kit';
*
* function useEpochInfo() {
* const client = useClientCapability<ClientWithRpc<GetEpochInfoApi>>({
* capability: 'rpc',
* hookName: 'useEpochInfo',
* providerHint: 'Mount <RpcProvider> or <RpcConnectionProvider>.',
* });
* return useRequest(() => client.rpc.getEpochInfo(), [client]);
* }
* ```
*
* @see {@link useClient}
*/
export function useClientCapability<TClient extends object>(options: UseClientCapabilityOptions): Client<TClient> {
const client = useClient<TClient>();
const keys: readonly string[] = Array.isArray(options.capability)
? options.capability
: [options.capability as string];
for (const key of keys) {
if (!(key in (client as object))) {
const formatKey = (k: string): string => '`client.' + k + '`';
const description: string =
keys.length === 1 ? formatKey(key) : 'all of ' + keys.map((k: string) => formatKey(k)).join(', ');
throwMissingCapability(options.hookName, description, options.providerHint);
}
}
return client;
}
Loading
Loading