@solana/kit-plugin-wallet 0.0.0 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,6 @@
1
+ declare const __BROWSER__: boolean;
2
+ declare const __ESM__: boolean;
3
+ declare const __NODEJS__: boolean;
4
+ declare const __REACTNATIVE__: boolean;
5
+ declare const __TEST__: boolean;
6
+ declare const __VERSION__: string;
package/src/types.ts ADDED
@@ -0,0 +1,358 @@
1
+ import type { MessageSigner, SignatureBytes, TransactionSigner } from '@solana/kit';
2
+ import type { SolanaChain } from '@solana/wallet-standard-chains';
3
+ import type { SolanaSignInInput, SolanaSignInOutput } from '@solana/wallet-standard-features';
4
+ import type { IdentifierString } from '@wallet-standard/base';
5
+ import type { UiWallet, UiWalletAccount } from '@wallet-standard/ui';
6
+
7
+ /**
8
+ * The signer type for a connected wallet account.
9
+ *
10
+ * Always satisfies `TransactionSigner`. Additionally implements `MessageSigner`
11
+ * when the wallet supports `solana:signMessage`.
12
+ */
13
+ export type WalletSigner = TransactionSigner | (MessageSigner & TransactionSigner);
14
+
15
+ // -- Public types -----------------------------------------------------------
16
+
17
+ /**
18
+ * The connection status of the wallet plugin.
19
+ *
20
+ * - `pending` — not yet initialized. Initial state on both server and browser.
21
+ * On the server — and in React Native, which is treated the same way — this
22
+ * state is permanent. In the browser it resolves to `disconnected` or
23
+ * `reconnecting` once the storage check completes.
24
+ * - `disconnected` — initialized, no wallet connected.
25
+ * - `connecting` — a user-initiated connection request is in progress. When
26
+ * connecting to a different wallet while one is already connected, the
27
+ * existing connection is preserved until the new one succeeds — so
28
+ * {@link WalletState.connected} can be non-null during this state, and a
29
+ * failed attempt reverts to the previous connection.
30
+ * - `connected` — a wallet is connected.
31
+ * - `disconnecting` — a user-initiated disconnection request is in progress.
32
+ * - `reconnecting` — auto-connect in progress (connecting to persisted wallet).
33
+ */
34
+ export type WalletStatus = 'connected' | 'connecting' | 'disconnected' | 'disconnecting' | 'pending' | 'reconnecting';
35
+
36
+ /**
37
+ * A snapshot of the wallet plugin state at a point in time.
38
+ *
39
+ * Returned by {@link WalletNamespace.getState}. The same object reference
40
+ * is returned on successive calls as long as nothing has changed — a new
41
+ * object is only created when a field actually changes. This ensures
42
+ * `useSyncExternalStore` only triggers re-renders on meaningful state changes.
43
+ *
44
+ * @see {@link WalletNamespace.getState}
45
+ */
46
+ export type WalletState = {
47
+ /**
48
+ * The active connection, or `null` when disconnected.
49
+ *
50
+ * `signer` is `null` for read-only / watch-only wallets that do not
51
+ * support any signing feature.
52
+ *
53
+ * Independent of {@link status}: when {@link connect} or {@link signIn} is
54
+ * called for a different wallet while one is already connected, `connected`
55
+ * keeps describing the existing connection while `status` is `'connecting'`,
56
+ * and a failed attempt leaves it in place.
57
+ */
58
+ readonly connected: {
59
+ readonly account: UiWalletAccount;
60
+ /** The signer for the active account, or `null` for read-only wallets. */
61
+ readonly signer: WalletSigner | null;
62
+ readonly wallet: UiWallet;
63
+ } | null;
64
+ /** The current connection status. */
65
+ readonly status: WalletStatus;
66
+ /** All discovered wallets matching the configured chain and filter. */
67
+ readonly wallets: readonly UiWallet[];
68
+ };
69
+
70
+ /**
71
+ * Options accepted by each async wallet action.
72
+ *
73
+ * Currently only carries an `abortSignal`, but is kept as an object for
74
+ * consistency with the rest of the Kit ecosystem and to allow future
75
+ * additions without breaking the call-site shape.
76
+ *
77
+ * @see {@link WalletNamespace.connect}
78
+ * @see {@link WalletNamespace.disconnect}
79
+ * @see {@link WalletNamespace.signMessage}
80
+ * @see {@link WalletNamespace.signIn}
81
+ */
82
+ export type WalletActionOptions = {
83
+ /**
84
+ * An optional `AbortSignal` used to cancel the operation.
85
+ *
86
+ * Cancellation is pre-call only: the plugin calls
87
+ * `abortSignal.throwIfAborted()` at the start of each action and bails
88
+ * out before invoking the wallet. Once the underlying wallet-standard
89
+ * call has been dispatched, its result is returned even if the signal
90
+ * is aborted mid-flight — the wallet's side effect (an approved
91
+ * signature, a live connection, a broadcast transaction) is the source
92
+ * of truth, and throwing here would discard real user work without
93
+ * undoing what the wallet already did.
94
+ */
95
+ abortSignal?: AbortSignal;
96
+ };
97
+
98
+ /**
99
+ * A pluggable storage adapter for persisting the selected wallet account.
100
+ *
101
+ * Follows the Web Storage API shape (`getItem`/`setItem`/`removeItem`).
102
+ * `localStorage` and `sessionStorage` satisfy this interface directly.
103
+ * Async backends (IndexedDB, encrypted storage) may return `Promise`s.
104
+ *
105
+ * Writes are fire-and-forget: the plugin does not await `setItem`/`removeItem`
106
+ * and swallows any rejection (the live wallet connection is the source of
107
+ * truth, so a failed persist is non-fatal). A resolved action therefore does
108
+ * not guarantee the write has landed, and rapid successive writes are not
109
+ * ordered. This is intentional — persistence only records which account to
110
+ * silently reconnect to on the next load.
111
+ *
112
+ * @example
113
+ * ```ts
114
+ * // Use sessionStorage
115
+ * walletSigner({ chain: 'solana:mainnet', storage: sessionStorage });
116
+ *
117
+ * // Custom async adapter
118
+ * walletSigner({
119
+ * chain: 'solana:mainnet',
120
+ * storage: {
121
+ * getItem: (key) => myStore.get(key),
122
+ * setItem: (key, value) => myStore.set(key, value),
123
+ * removeItem: (key) => myStore.delete(key),
124
+ * },
125
+ * });
126
+ * ```
127
+ */
128
+ export type WalletStorage = {
129
+ getItem(key: string): Promise<string | null> | string | null;
130
+ removeItem(key: string): Promise<void> | void;
131
+ setItem(key: string, value: string): Promise<void> | void;
132
+ };
133
+
134
+ /**
135
+ * Configuration for the wallet plugins.
136
+ *
137
+ * @see {@link walletSigner}
138
+ * @see {@link walletIdentity}
139
+ * @see {@link walletPayer}
140
+ * @see {@link walletWithoutSigner}
141
+ */
142
+ export type WalletPluginConfig = {
143
+ /**
144
+ * Whether to attempt silent reconnection on startup using the persisted
145
+ * wallet account from `storage`.
146
+ *
147
+ * Has no effect if `storage` is `null`.
148
+ *
149
+ * @default true
150
+ */
151
+ autoConnect?: boolean;
152
+
153
+ /**
154
+ * The chain this client targets (e.g. `'solana:mainnet'`).
155
+ *
156
+ * Accepts any {@link SolanaChain} (with literal autocomplete) and, as an
157
+ * escape hatch, any wallet-standard {@link IdentifierString} shape
158
+ * (`${string}:${string}`) for custom chains or non-Solana L2s. The plugin's
159
+ * runtime behavior is chain-agnostic — it passes the identifier to
160
+ * wallet-standard discovery (`uiWallet.chains.includes(chain)`) and to
161
+ * `createSignerFromWalletAccount`. Wallets that don't advertise the chain
162
+ * are filtered out, and accounts that can't produce a signer for the chain
163
+ * resolve to `signer: null` (matching the read-only-wallet contract).
164
+ *
165
+ * One client = one chain. To switch networks, create a separate client
166
+ * with a different chain and RPC endpoint.
167
+ */
168
+ chain: SolanaChain | (IdentifierString & {});
169
+
170
+ /**
171
+ * Optional filter function for wallet discovery. Called for each wallet
172
+ * that supports the configured chain and `standard:connect`. Return `true`
173
+ * to include the wallet, `false` to exclude it.
174
+ *
175
+ * @example
176
+ * ```ts
177
+ * // Require signAndSendTransaction
178
+ * filter: (w) => w.features.includes('solana:signAndSendTransaction')
179
+ *
180
+ * // Whitelist specific wallets
181
+ * filter: (w) => ['SomeWallet', 'SomeOtherWallet'].includes(w.name)
182
+ * ```
183
+ */
184
+ filter?: (wallet: UiWallet) => boolean;
185
+
186
+ /**
187
+ * Storage adapter for persisting the selected wallet account across page
188
+ * loads. Pass `null` to disable persistence entirely.
189
+ *
190
+ * When omitted in a browser environment, `localStorage` is used by default.
191
+ * On the server, storage is always skipped regardless of this option.
192
+ *
193
+ * @default localStorage (in browser)
194
+ * @see {@link WalletStorage}
195
+ */
196
+ storage?: WalletStorage | null;
197
+
198
+ /**
199
+ * Storage key used for persistence.
200
+ *
201
+ * @default 'kit-wallet'
202
+ */
203
+ storageKey?: string;
204
+ };
205
+
206
+ /**
207
+ * The `wallet` namespace exposed on the client as `client.wallet`.
208
+ *
209
+ * All wallet state is accessed via {@link getState}. Use {@link subscribe}
210
+ * to be notified of changes and integrate with framework primitives such as
211
+ * React's `useSyncExternalStore`.
212
+ *
213
+ * @see {@link ClientWithWallet}
214
+ */
215
+ export type WalletNamespace = {
216
+ // -- Actions --
217
+
218
+ /**
219
+ * Connect to a wallet. Calls `standard:connect`, then selects the first
220
+ * newly authorized account (or the first account if reconnecting). Creates
221
+ * and caches a signer for the active account.
222
+ *
223
+ * Resolving means the wallet is connected; any failure rejects. If a wallet
224
+ * is already connected, it stays connected until the new one is established:
225
+ * a failed attempt — a rejected prompt, no authorized accounts, or the
226
+ * wallet becoming unavailable — leaves the previous connection in place
227
+ * rather than disconnecting it.
228
+ *
229
+ * @returns All accounts from the wallet after connection.
230
+ * @throws The wallet's rejection error if the user declines the prompt.
231
+ * @throws `SolanaError(SOLANA_ERROR__WALLET__NOT_CONNECTED)` if the wallet
232
+ * authorizes no accounts, or unregisters (or drops a required
233
+ * feature/chain) while its connect prompt is open. Any previously
234
+ * connected wallet is left in place.
235
+ * @throws `DOMException` with `name: 'AbortError'` if a newer `connect` or
236
+ * `signIn` is started before this call resolves. The newer request wins
237
+ * and owns the resulting connection; this superseded call rejects so it
238
+ * can be ignored like any other aborted operation (e.g. an accidental
239
+ * double-click still connects — only the orphaned first promise rejects).
240
+ * @throws `options.abortSignal.reason` if the signal is already aborted
241
+ * when the action is called. Aborts after the wallet call has been
242
+ * dispatched do not take effect.
243
+ */
244
+ connect: (wallet: UiWallet, options?: WalletActionOptions) => Promise<readonly UiWalletAccount[]>;
245
+
246
+ /**
247
+ * Disconnect the active wallet. Calls `standard:disconnect` if supported.
248
+ *
249
+ * @throws `options.abortSignal.reason` if the signal is already aborted
250
+ * when the action is called. Aborts after the wallet call has been
251
+ * dispatched do not take effect.
252
+ */
253
+ disconnect: (options?: WalletActionOptions) => Promise<void>;
254
+
255
+ // -- State --
256
+ /**
257
+ * Get the current wallet state. Referentially stable — a new object is
258
+ * only created when a field actually changes, so React's
259
+ * `useSyncExternalStore` skips re-renders when nothing meaningful changed.
260
+ *
261
+ * @see {@link WalletState}
262
+ */
263
+ getState: () => WalletState;
264
+
265
+ /**
266
+ * Switch to a different account within the connected wallet. Creates and
267
+ * caches a new signer for the selected account.
268
+ *
269
+ * @throws `SolanaError(SOLANA_ERROR__WALLET__NOT_CONNECTED)` if no wallet is
270
+ * connected, or the connected wallet is currently disconnecting.
271
+ * @throws `SolanaError(SOLANA_ERROR__WALLET__ACCOUNT_NOT_AVAILABLE)` if the
272
+ * specified account is not among the connected wallet's accounts.
273
+ */
274
+ selectAccount: (account: UiWalletAccount) => void;
275
+
276
+ /**
277
+ * Sign In With Solana (SIWS-as-connect).
278
+ *
279
+ * Connects the wallet, calls `solana:signIn`, sets the returned account as
280
+ * active, and creates a signer. Resolving means the client is in the same
281
+ * state as if {@link connect} had been called; any failure to connect
282
+ * rejects rather than resolving while disconnected.
283
+ *
284
+ * All fields on `SolanaSignInInput` are optional — pass `{}` if no sign-in
285
+ * customization is needed.
286
+ *
287
+ * To sign in with the already-connected wallet, pass
288
+ * `getState().connected.wallet`.
289
+ *
290
+ * Like {@link connect}, a failed sign-in to a different wallet rejects and
291
+ * leaves any existing connection in place rather than disconnecting it.
292
+ *
293
+ * @returns The wallet's sign-in output, once the connection is established.
294
+ * @throws `WalletStandardError(WALLET_STANDARD_ERROR__FEATURES__WALLET_ACCOUNT_FEATURE_UNIMPLEMENTED)`
295
+ * if the wallet does not support `solana:signIn`.
296
+ * @throws `SolanaError(SOLANA_ERROR__WALLET__NOT_CONNECTED)` if the wallet
297
+ * unregisters (or drops a required feature/chain) while its sign-in prompt
298
+ * is open, or signs in with an account it does not expose. Any previously
299
+ * connected wallet is left in place.
300
+ * @throws `DOMException` with `name: 'AbortError'` if a newer `connect` or
301
+ * `signIn` is started before this call resolves. The newer request wins
302
+ * and owns the resulting connection; this superseded call rejects so it
303
+ * can be ignored like any other aborted operation.
304
+ * @throws `options.abortSignal.reason` if the signal is already aborted
305
+ * when the action is called. Aborts after the wallet call has been
306
+ * dispatched do not take effect.
307
+ */
308
+ signIn: (wallet: UiWallet, input: SolanaSignInInput, options?: WalletActionOptions) => Promise<SolanaSignInOutput>;
309
+
310
+ /**
311
+ * Sign an arbitrary message with the connected account.
312
+ *
313
+ * Calls the wallet's `solana:signMessage` feature directly (does not go
314
+ * through the cached signer), so message signing works even for wallets
315
+ * that don't support transaction signing.
316
+ *
317
+ * @throws `SolanaError(SOLANA_ERROR__WALLET__NOT_CONNECTED)` if no wallet is connected.
318
+ * @throws `WalletStandardError(WALLET_STANDARD_ERROR__FEATURES__WALLET_ACCOUNT_FEATURE_UNIMPLEMENTED)`
319
+ * if the wallet does not support `solana:signMessage`.
320
+ * @throws `options.abortSignal.reason` if the signal is already aborted
321
+ * when the action is called. Aborts after the wallet call has been
322
+ * dispatched do not take effect.
323
+ */
324
+ signMessage: (message: Uint8Array, options?: WalletActionOptions) => Promise<SignatureBytes>;
325
+
326
+ /**
327
+ * Subscribe to any wallet state change. Compatible with React's
328
+ * `useSyncExternalStore` and similar framework primitives.
329
+ *
330
+ * @returns An unsubscribe function.
331
+ *
332
+ * @example
333
+ * ```ts
334
+ * const state = useSyncExternalStore(client.wallet.subscribe, client.wallet.getState);
335
+ * ```
336
+ */
337
+ subscribe: (listener: () => void) => () => void;
338
+ };
339
+
340
+ /**
341
+ * Properties added to the client by the wallet plugins.
342
+ *
343
+ * All wallet state and actions are namespaced under `client.wallet`.
344
+ * Depending on which plugin variant is used, `client.payer` and/or
345
+ * `client.identity` may also be set to the connected wallet's signer.
346
+ * This is not part of Kit plugin-interfaces, as it depends on wallet-standard types
347
+ *
348
+ * @see {@link walletSigner}
349
+ * @see {@link walletPayer}
350
+ * @see {@link walletIdentity}
351
+ * @see {@link walletWithoutSigner}
352
+ * @see {@link WalletNamespace}
353
+ *
354
+ */
355
+ export type ClientWithWallet = {
356
+ /** The wallet namespace — state, actions, and framework integration. */
357
+ readonly wallet: WalletNamespace;
358
+ };
package/src/wallet.ts ADDED
@@ -0,0 +1,216 @@
1
+ import {
2
+ type ClientWithIdentity,
3
+ type ClientWithPayer,
4
+ type ClientWithSubscribeToIdentity,
5
+ type ClientWithSubscribeToPayer,
6
+ extendClient,
7
+ SOLANA_ERROR__WALLET__NO_SIGNER_CONNECTED,
8
+ SOLANA_ERROR__WALLET__SIGNER_NOT_AVAILABLE,
9
+ SolanaError,
10
+ withCleanup,
11
+ } from '@solana/kit';
12
+
13
+ import { createWalletStore } from './store';
14
+ import type { ClientWithWallet, WalletPluginConfig } from './types';
15
+
16
+ // -- Internal helpers ---------------------------------------------------------
17
+
18
+ function defineSignerGetter(
19
+ additions: Record<string, unknown>,
20
+ property: string,
21
+ store: ReturnType<typeof createWalletStore>,
22
+ ): void {
23
+ Object.defineProperty(additions, property, {
24
+ configurable: true,
25
+ enumerable: true,
26
+ get() {
27
+ const state = store.getState();
28
+ if (!state.connected) {
29
+ throw new SolanaError(SOLANA_ERROR__WALLET__NO_SIGNER_CONNECTED, { status: state.status });
30
+ }
31
+ if (!state.connected.signer) {
32
+ throw new SolanaError(SOLANA_ERROR__WALLET__SIGNER_NOT_AVAILABLE);
33
+ }
34
+ return state.connected.signer;
35
+ },
36
+ });
37
+ }
38
+
39
+ type SignerProperties = 'payer' | 'identity';
40
+
41
+ // Maps each signer capability to the Kit-convention reactive hook installed
42
+ // alongside it (`@solana/plugin-interfaces`). A plugin whose `client.payer` /
43
+ // `client.identity` is dynamic installs the matching `subscribeTo*` function
44
+ // so reactive consumers can observe changes without naming this plugin.
45
+ const SUBSCRIBE_PROPERTY: Record<SignerProperties, 'subscribeToIdentity' | 'subscribeToPayer'> = {
46
+ identity: 'subscribeToIdentity',
47
+ payer: 'subscribeToPayer',
48
+ };
49
+
50
+ function createPlugin<TAdditions extends ClientWithWallet>(
51
+ config: WalletPluginConfig,
52
+ signerProperties: SignerProperties[],
53
+ ) {
54
+ return <T extends object & { wallet?: never }>(client: T): Disposable & Omit<T, keyof TAdditions> & TAdditions => {
55
+ if ('wallet' in client) {
56
+ throw new Error(
57
+ 'Only one wallet plugin can be used per client. ' +
58
+ 'Use walletSigner, walletPayer, walletIdentity, or walletWithoutSigner — not multiple.',
59
+ );
60
+ }
61
+
62
+ const store = createWalletStore(config);
63
+
64
+ const additions: Record<string, unknown> = { wallet: store };
65
+ for (const prop of signerProperties) {
66
+ defineSignerGetter(additions, prop, store);
67
+ additions[SUBSCRIBE_PROPERTY[prop]] = store.subscribeSigner;
68
+ }
69
+
70
+ return withCleanup(extendClient(client, additions), () => store[Symbol.dispose]()) as unknown as Disposable &
71
+ Omit<T, keyof TAdditions> &
72
+ TAdditions;
73
+ };
74
+ }
75
+
76
+ // -- Public API ---------------------------------------------------------------
77
+
78
+ /**
79
+ * A framework-agnostic Kit plugin that manages wallet discovery, connection
80
+ * lifecycle, and signer creation using wallet-standard — and syncs the
81
+ * connected wallet's signer to both `client.payer` and `client.identity`.
82
+ *
83
+ * This is the most common entrypoint for dApps. When a signing-capable
84
+ * wallet is connected, `client.payer` and `client.identity` both return the
85
+ * wallet signer. When disconnected or read-only, accessing either throws.
86
+ *
87
+ * Because the signer is dynamic, both `client.subscribeToPayer` and
88
+ * `client.subscribeToIdentity` are installed so reactive consumers can observe
89
+ * changes (the Kit reactive-capability convention).
90
+ *
91
+ * **SSR-safe.** Can be included in a shared client chain that runs on both
92
+ * server and browser.
93
+ *
94
+ * ```ts
95
+ * const client = createClient()
96
+ * .use(walletSigner({ chain: 'solana:mainnet' }))
97
+ * .use(solanaRpc({ rpcUrl: 'https://api.mainnet-beta.solana.com' }))
98
+ * .use(planAndSendTransactions());
99
+ * ```
100
+ *
101
+ * @param config - Plugin configuration.
102
+ *
103
+ * @see {@link walletPayer}
104
+ * @see {@link walletIdentity}
105
+ * @see {@link walletWithoutSigner}
106
+ * @see {@link WalletPluginConfig}
107
+ */
108
+ export function walletSigner(config: WalletPluginConfig) {
109
+ return createPlugin<
110
+ ClientWithIdentity &
111
+ ClientWithPayer &
112
+ ClientWithSubscribeToIdentity &
113
+ ClientWithSubscribeToPayer &
114
+ ClientWithWallet
115
+ >(config, ['payer', 'identity']);
116
+ }
117
+
118
+ /**
119
+ * A framework-agnostic Kit plugin that manages wallet discovery, connection
120
+ * lifecycle, and signer creation using wallet-standard — and syncs the
121
+ * connected wallet's signer to `client.identity`.
122
+ *
123
+ * Use this when `client.payer` is controlled by a separate `payer()` plugin
124
+ * (e.g. a backend relayer pays fees, but the user's wallet is the identity).
125
+ *
126
+ * Because the signer is dynamic, `client.subscribeToIdentity` is installed so
127
+ * reactive consumers can observe changes (the Kit reactive-capability convention).
128
+ *
129
+ * **SSR-safe.** Can be included in a shared client chain that runs on both
130
+ * server and browser.
131
+ *
132
+ * ```ts
133
+ * const client = createClient()
134
+ * .use(payer(relayerKeypair))
135
+ * .use(walletIdentity({ chain: 'solana:mainnet' }))
136
+ * .use(solanaRpc({ rpcUrl: 'https://api.mainnet-beta.solana.com' }))
137
+ * .use(planAndSendTransactions());
138
+ * ```
139
+ *
140
+ * @param config - Plugin configuration.
141
+ *
142
+ * @see {@link walletSigner}
143
+ * @see {@link walletPayer}
144
+ * @see {@link walletWithoutSigner}
145
+ * @see {@link WalletPluginConfig}
146
+ */
147
+ export function walletIdentity(config: WalletPluginConfig) {
148
+ return createPlugin<ClientWithIdentity & ClientWithSubscribeToIdentity & ClientWithWallet>(config, ['identity']);
149
+ }
150
+
151
+ /**
152
+ * A framework-agnostic Kit plugin that manages wallet discovery, connection
153
+ * lifecycle, and signer creation using wallet-standard — and syncs the
154
+ * connected wallet's signer to `client.payer`.
155
+ *
156
+ * Use this when you need the wallet as the fee payer but don't need
157
+ * `client.identity`. For most dApps, prefer {@link walletSigner} which
158
+ * sets both.
159
+ *
160
+ * Because the signer is dynamic, `client.subscribeToPayer` is installed so
161
+ * reactive consumers can observe changes (the Kit reactive-capability convention).
162
+ *
163
+ * **SSR-safe.** Can be included in a shared client chain that runs on both
164
+ * server and browser.
165
+ *
166
+ * ```ts
167
+ * const client = createClient()
168
+ * .use(walletPayer({ chain: 'solana:mainnet' }))
169
+ * .use(solanaRpc({ rpcUrl: 'https://api.mainnet-beta.solana.com' }))
170
+ * .use(planAndSendTransactions());
171
+ * ```
172
+ *
173
+ * @param config - Plugin configuration.
174
+ *
175
+ * @see {@link walletSigner}
176
+ * @see {@link walletIdentity}
177
+ * @see {@link walletWithoutSigner}
178
+ * @see {@link WalletPluginConfig}
179
+ */
180
+ export function walletPayer(config: WalletPluginConfig) {
181
+ return createPlugin<ClientWithPayer & ClientWithSubscribeToPayer & ClientWithWallet>(config, ['payer']);
182
+ }
183
+
184
+ /**
185
+ * A framework-agnostic Kit plugin that manages wallet discovery, connection
186
+ * lifecycle, and signer creation using wallet-standard.
187
+ *
188
+ * Adds the `wallet` namespace to the client without setting `client.payer`
189
+ * or `client.identity`. Use this alongside separate `payer()` and/or
190
+ * `identity()` plugins, or when the wallet's signer is used explicitly in
191
+ * instructions. For most dApps, prefer {@link walletSigner} instead.
192
+ *
193
+ * **SSR-safe.** Can be included in a shared client chain that runs on both
194
+ * server and browser.
195
+ *
196
+ * ```ts
197
+ * const client = createClient()
198
+ * .use(payer(backendKeypair))
199
+ * .use(walletWithoutSigner({ chain: 'solana:mainnet' }))
200
+ * .use(solanaRpc({ rpcUrl: 'https://api.mainnet-beta.solana.com' }))
201
+ * .use(planAndSendTransactions());
202
+ *
203
+ * // client.payer is always backendKeypair
204
+ * // client.wallet.getState().connected?.signer for manual use
205
+ * ```
206
+ *
207
+ * @param config - Plugin configuration.
208
+ *
209
+ * @see {@link walletSigner}
210
+ * @see {@link walletPayer}
211
+ * @see {@link walletIdentity}
212
+ * @see {@link WalletPluginConfig}
213
+ */
214
+ export function walletWithoutSigner(config: WalletPluginConfig) {
215
+ return createPlugin<ClientWithWallet>(config, []);
216
+ }