Skip to content

Update the react-app example to use the wallet plugin - #1825

Merged
mcintyre94 merged 4 commits into
mainfrom
react-app-wallet-plugin
Aug 4, 2026
Merged

Update the react-app example to use the wallet plugin#1825
mcintyre94 merged 4 commits into
mainfrom
react-app-wallet-plugin

Conversation

@mcintyre94

@mcintyre94 mcintyre94 commented Jul 10, 2026

Copy link
Copy Markdown
Member

This PR updates the react-app example to use the walletSigner plugin, and the react hooks from the wallet plugin.

It creates a Client with only the walletSigner plugin, future iterations will add more plugins (namely solanaRpc and the system program plugin).

It maintains as much functionality from the previous react app as possible, I've flagged inline where the functionality differs.

This PR now uses 0.14.0 of the wallet plugin, which requires client as the first param of each hook.

The PR also bumps wallet-standard dependencies in @solana/react to match those in the wallet plugin (the latest versions).

@changeset-bot

changeset-bot Bot commented Jul 10, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 64ea156

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 48 packages
Name Type
@solana/react Patch
@solana/accounts Patch
@solana/addresses Patch
@solana/assertions Patch
@solana/codecs-core Patch
@solana/codecs-data-structures Patch
@solana/codecs-numbers Patch
@solana/codecs-strings Patch
@solana/codecs Patch
@solana/compat Patch
@solana/errors Patch
@solana/fast-stable-stringify Patch
@solana/fixed-points Patch
@solana/functional Patch
@solana/instruction-plans Patch
@solana/instructions Patch
@solana/keys Patch
@solana/kit Patch
@solana/nominal-types Patch
@solana/offchain-messages Patch
@solana/options Patch
@solana/plugin-core Patch
@solana/plugin-interfaces Patch
@solana/program-client-core Patch
@solana/programs Patch
@solana/promises Patch
@solana/rpc-api Patch
@solana/rpc-graphql Patch
@solana/rpc-parsed-types Patch
@solana/rpc-spec-types Patch
@solana/rpc-spec Patch
@solana/rpc-subscriptions-api Patch
@solana/rpc-subscriptions-channel-websocket Patch
@solana/rpc-subscriptions-spec Patch
@solana/rpc-subscriptions Patch
@solana/rpc-transformers Patch
@solana/rpc-transport-http Patch
@solana/rpc-types Patch
@solana/rpc Patch
@solana/signers Patch
@solana/subscribable Patch
@solana/sysvars Patch
@solana/transaction-confirmation Patch
@solana/transaction-introspection Patch
@solana/transaction-messages Patch
@solana/transactions Patch
@solana/wallet-account-signer Patch
@solana/webcrypto-ed25519-polyfill Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@mcintyre94 mcintyre94 changed the title Update the react-app example to use the wallet plugin Draft: Update the react-app example to use the wallet plugin Jul 10, 2026
@socket-security

socket-security Bot commented Jul 10, 2026

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Added@​wallet-standard/​ui@​1.0.3851006090100
Added@​wallet-standard/​ui-registry@​1.1.1901007290100
Added@​solana/​kit-plugin-wallet@​0.14.07710010091100
Added@​solana-program/​system@​0.13.01001008493100

View full report

Comment on lines +23 to +24
// Every wallet from `useWallets()` is pre-filtered by the plugin to those that support
// `standard:connect` on the active chain, so every item rendered here is connectable.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Flagging this: the previous implementation displayed wallets that cannot be connected to, eg because they don't support the selected chain or don't have the StandardConnect feature. The plugin filters wallets to only those that support the selected chain and have the StandardConnect feature.

It is not possible, using only the plugin, to display wallets that have been filtered out. An app could do this by using the wallet-standard libraries directly, as the previous implementation did, but I haven't included that here.

In general I think this is the right default - the app only gets wallets that it can connect to.

Opened an issue in kit-plugins to add the filtered wallets if we want to bring this ability to display them back: anza-xyz/kit-plugins#313

Comment thread examples/react-app/src/components/Nav.tsx Outdated
onSignIn();
} catch (e) {
// Filter out abort error, which just means a later action superseded
if (!isAbortError(e)) {

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Example of the app using isAbortError to avoid throwing when an action is superseded, which previously required an install of @solana/promises.

Comment on lines +90 to +93
// Render-time capability guard: throws so the surrounding `ErrorBoundary` renders
// `FeatureNotSupportedCallout` when the connected account can't sign transactions
// Also narrows the signer for the `useAction` below
assertCanSignTransactions(signer);

@mcintyre94 mcintyre94 Jul 10, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that this is a bit different from the previous version, which used hooks like useWalletAccountTransactionSigner.

The app now receives a signer: TransactionSigner, and needs to check for (in this case) it having the partial sign feature. It does this by using an assertion which narrows the type. This is just a wrapper around functions we have in the signers package, but the app handles a null signer and has its own errors.

This assert throws if false, like the previous useWalletAccountTransactionSigner hook did, rendering the error boundary. But apps can also just branch on the signer type, etc, depending what makes sense for them.

I'd also note that this example is a bit arbitrary - the instruction-plan and signer APIs offer higher-level helpers and mean apps generally don't need to know exactly which features are available.

Comment on lines 113 to 119
getTransferSolInstruction({
amount,
destination: address(recipientAccount.address),
source: transactionSigner,
source: signer,
}),
m,
),

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to highlight that this transaction construction and program instruction code will disappear in a future iteration that uses the instruction plans plugin and program plugins. This PR intentionally only adds wallet, and leaves everything RPC/transaction building/etc alone.

chain: SolanaChain;
displayName: string;
setChain?(chain: `solana:${string}`): void;
setChain?(chain: SolanaChain): void;

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not new in the plugin, but wallet-standard exports a SolanaChain type that we use throughout

* `whenReady()` resolves and the continuation runs — the guard is what keeps the disposed client
* from being published.
*/
export function WalletClientProvider({ children }: Props) {

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the biggest red flag from this exercise

This is intentionally poking at an advanced use case, but it is realistic. When we change the chain, we build a new client (wallet plugin uses chain), but we avoid re-rendering using it until it is ready, using the next.wallet.whenReady construct. This means that the UI doesn't flash an unconnected state, and if the connected wallet remains valid on the new chain then there's no UI churn.

The rest of this is all juggling react lifecycle and the dispose mechanism.

My plan to fix this:

  • Add withWhenReady as a function that plugins can use to register a whenReady promise. Currently only the wallet needs this. In a similar way to dispose, we will internally expose a whenReady on the client, that combines (using Promise.allSettled in this case) all registered promises from all plugins. The result is that an app will only need client.whenReady regardless which plugins are used
  • Given that, this WalletClientProvider can be generalised to a ManagedClientProvider that takes a function to construct the client, and then handles the client.whenReady for you. Nothing wallet specific there. I will add that to the @solana/react package as a new provider.
  • After that, apps that don't need to change their client will just use ClientProvider as they currently do. Apps that need to handle the client changing, and want to be able to wait until the new client has settled to re-render with it, will use ManagedClientProvider. All this complexity will disappear from the app while achieving the same UX.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Issues opened for this: #1842, #1843

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've reconsidered this design in ee8d403

New design, app changes:

  • The client provider now immediately creates and updates the client, does not wait for whenReady. All the complexity is gone, except dispose
  • The UI now uses the hooks from the wallet plugin to granularly disable/dim UI that depends on the wallet state when it's in a not ready state

I am no longer suggesting standardising whenReady in Kit, and ManagedClientProvider may not be worth building - maybe for dispose. Everything we have in the wallet plugin remains, as it enables the UI to react to the readiness.

The issue with this design shows as we add more to the client - we want to immediately have access to eg the new RPC when the chain changes. UI that only depends on the RPC should not wait for the wallet to reconnect before updating. I don't have any examples of other plugins that would need this yet, but if there was one then UI that depends only on the wallet shouldn't wait for that plugin either. A client-level whenReady doesn't make sense IMO.

The app-level complexity remaining is that I have a hook useDisplayedWallet which captures the last seen wallet.connected state, and returns that alongside an isStale based on the wallet ready hook. This enables the app to display the previous connected wallet in a disabled/dimmed state during the reconnect. We could improve this by exposing the UiWalletAccount that is being reconnected to from the plugin when it is available, which would provide a better first load UX (the app can't currently know it until it's connected), and would avoid the app needing to store it in this hook. Captured as a plugin issue: anza-xyz/kit-plugins#350

Example of the current UX using a deliberately slowed down wallet reconnect path:

react-app-chain.mp4

Comment on lines -15 to -20
const STORAGE_KEY = 'solana-wallet-standard-example-react:selected-wallet-and-address';
const stateSync = {
deleteSelectedWallet: () => localStorage.removeItem(STORAGE_KEY),
getSelectedWallet: () => localStorage.getItem(STORAGE_KEY),
storeSelectedWallet: (accountKey: string) => localStorage.setItem(STORAGE_KEY, accountKey),
};

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All this goes away, the wallet plugin handles persisting the connected wallet (using localStorage by default)

Comment thread examples/react-app/src/main.tsx
Comment thread examples/react-app/package.json Outdated
Base automatically changed from reexport-promises to main July 13, 2026 12:25
@mcintyre94
mcintyre94 changed the base branch from main to graphite-base/1825 July 14, 2026 15:33
@mcintyre94
mcintyre94 force-pushed the react-app-wallet-plugin branch from 8931118 to 3fb47c3 Compare July 14, 2026 15:33
@mcintyre94
mcintyre94 changed the base branch from graphite-base/1825 to use-client-nonoptional July 14, 2026 15:33
@bundlemon

bundlemon Bot commented Jul 14, 2026

Copy link
Copy Markdown

BundleMon

Unchanged files (150)
Status Path Size Limits
@solana/kit production bundle
kit/dist/index.production.min.js
55.54KB -
errors/dist/index.node.mjs
21.6KB -
errors/dist/index.browser.mjs
21.58KB -
errors/dist/index.native.mjs
21.58KB -
rpc-graphql/dist/index.browser.mjs
18.82KB -
rpc-graphql/dist/index.native.mjs
18.82KB -
rpc-graphql/dist/index.node.mjs
18.82KB -
wallet-account-signer/dist/index.node.mjs
18.31KB -
wallet-account-signer/dist/index.browser.mjs
18.29KB -
wallet-account-signer/dist/index.native.mjs
18.29KB -
transaction-messages/dist/index.browser.mjs
11.34KB -
transaction-messages/dist/index.native.mjs
11.34KB -
transaction-messages/dist/index.node.mjs
11.34KB -
instruction-plans/dist/index.browser.mjs
7.02KB -
instruction-plans/dist/index.native.mjs
7.02KB -
instruction-plans/dist/index.node.mjs
7.02KB -
codecs-data-structures/dist/index.browser.mjs
5.3KB -
codecs-data-structures/dist/index.native.mjs
5.3KB -
codecs-data-structures/dist/index.node.mjs
5.29KB -
fixed-points/dist/index.browser.mjs
5.08KB -
fixed-points/dist/index.native.mjs
5.07KB -
fixed-points/dist/index.node.mjs
5.07KB -
offchain-messages/dist/index.browser.mjs
5.06KB -
offchain-messages/dist/index.native.mjs
5.06KB -
offchain-messages/dist/index.node.mjs
5.06KB -
react/dist/index.browser.mjs
5.02KB -
react/dist/index.node.mjs
5.02KB -
react/dist/index.native.mjs
5.02KB -
kit/dist/index.browser.mjs
4.61KB -
kit/dist/index.native.mjs
4.6KB -
kit/dist/index.node.mjs
4.6KB -
transactions/dist/index.browser.mjs
4.07KB -
transactions/dist/index.native.mjs
4.07KB -
transactions/dist/index.node.mjs
4.07KB -
codecs-core/dist/index.browser.mjs
3.62KB -
codecs-core/dist/index.native.mjs
3.62KB -
codecs-core/dist/index.node.mjs
3.62KB -
webcrypto-ed25519-polyfill/dist/index.node.mj
s
3.61KB -
webcrypto-ed25519-polyfill/dist/index.browser
.mjs
3.59KB -
webcrypto-ed25519-polyfill/dist/index.native.
mjs
3.57KB -
rpc-subscriptions/dist/index.browser.mjs
3.37KB -
rpc-subscriptions/dist/index.node.mjs
3.34KB -
rpc-subscriptions/dist/index.native.mjs
3.31KB -
signers/dist/index.browser.mjs
3.26KB -
signers/dist/index.native.mjs
3.26KB -
signers/dist/index.node.mjs
3.26KB -
rpc-transformers/dist/index.browser.mjs
3.16KB -
rpc-transformers/dist/index.native.mjs
3.16KB -
rpc-transformers/dist/index.node.mjs
3.15KB -
subscribable/dist/index.node.mjs
3.13KB -
keys/dist/index.node.mjs
3.06KB -
subscribable/dist/index.native.mjs
3.06KB -
subscribable/dist/index.browser.mjs
3.05KB -
addresses/dist/index.browser.mjs
2.93KB -
addresses/dist/index.native.mjs
2.92KB -
addresses/dist/index.node.mjs
2.92KB -
keys/dist/index.browser.mjs
2.85KB -
keys/dist/index.native.mjs
2.85KB -
transaction-introspection/dist/index.browser.
mjs
2.73KB -
transaction-introspection/dist/index.native.m
js
2.73KB -
transaction-introspection/dist/index.node.mjs
2.73KB -
codecs-strings/dist/index.browser.mjs
2.55KB -
codecs-strings/dist/index.node.mjs
2.51KB -
codecs-strings/dist/index.native.mjs
2.47KB -
transaction-confirmation/dist/index.node.mjs
2.42KB -
transaction-confirmation/dist/index.native.mj
s
2.37KB -
sysvars/dist/index.browser.mjs
2.37KB -
sysvars/dist/index.native.mjs
2.37KB -
transaction-confirmation/dist/index.browser.m
js
2.37KB -
sysvars/dist/index.node.mjs
2.37KB -
rpc-subscriptions-spec/dist/index.node.mjs
2.23KB -
rpc-subscriptions-spec/dist/index.native.mjs
2.19KB -
rpc-subscriptions-spec/dist/index.browser.mjs
2.19KB -
rpc/dist/index.node.mjs
1.95KB -
codecs-numbers/dist/index.browser.mjs
1.95KB -
codecs-numbers/dist/index.native.mjs
1.95KB -
codecs-numbers/dist/index.node.mjs
1.94KB -
rpc-types/dist/index.browser.mjs
1.9KB -
rpc-types/dist/index.native.mjs
1.9KB -
rpc-types/dist/index.node.mjs
1.9KB -
rpc-transport-http/dist/index.browser.mjs
1.89KB -
rpc-transport-http/dist/index.native.mjs
1.89KB -
rpc/dist/index.native.mjs
1.81KB -
rpc/dist/index.browser.mjs
1.8KB -
rpc-transport-http/dist/index.node.mjs
1.71KB -
rpc-subscriptions-channel-websocket/dist/inde
x.node.mjs
1.33KB -
rpc-subscriptions-channel-websocket/dist/inde
x.native.mjs
1.27KB -
rpc-subscriptions-channel-websocket/dist/inde
x.browser.mjs
1.26KB -
program-client-core/dist/index.browser.mjs
1.21KB -
program-client-core/dist/index.native.mjs
1.21KB -
program-client-core/dist/index.node.mjs
1.21KB -
plugin-core/dist/index.browser.mjs
1.18KB -
plugin-core/dist/index.native.mjs
1.18KB -
plugin-core/dist/index.node.mjs
1.18KB -
options/dist/index.browser.mjs
1.18KB -
options/dist/index.native.mjs
1.18KB -
options/dist/index.node.mjs
1.17KB -
accounts/dist/index.browser.mjs
1.17KB -
accounts/dist/index.native.mjs
1.17KB -
accounts/dist/index.node.mjs
1.16KB -
rpc-spec-types/dist/index.browser.mjs
1.15KB -
rpc-spec-types/dist/index.native.mjs
1.15KB -
rpc-spec-types/dist/index.node.mjs
1.15KB -
rpc-api/dist/index.browser.mjs
1.04KB -
rpc-api/dist/index.native.mjs
1.04KB -
rpc-api/dist/index.node.mjs
1.04KB -
compat/dist/index.browser.mjs
969B -
compat/dist/index.native.mjs
968B -
compat/dist/index.node.mjs
966B -
rpc-spec/dist/index.browser.mjs
898B -
rpc-spec/dist/index.native.mjs
897B -
rpc-spec/dist/index.node.mjs
896B -
rpc-subscriptions-api/dist/index.native.mjs
871B -
rpc-subscriptions-api/dist/index.browser.mjs
870B -
rpc-subscriptions-api/dist/index.node.mjs
870B -
promises/dist/index.native.mjs
841B -
promises/dist/index.node.mjs
840B -
promises/dist/index.browser.mjs
839B -
assertions/dist/index.browser.mjs
783B -
instructions/dist/index.browser.mjs
771B -
instructions/dist/index.native.mjs
770B -
instructions/dist/index.node.mjs
768B -
fast-stable-stringify/dist/index.browser.mjs
726B -
fast-stable-stringify/dist/index.native.mjs
725B -
assertions/dist/index.native.mjs
724B -
fast-stable-stringify/dist/index.node.mjs
724B -
assertions/dist/index.node.mjs
723B -
programs/dist/index.browser.mjs
329B -
programs/dist/index.native.mjs
327B -
programs/dist/index.node.mjs
325B -
fs-impl/dist/index.browser.mjs
245B -
event-target-impl/dist/index.node.mjs
230B -
functional/dist/index.browser.mjs
154B -
functional/dist/index.native.mjs
152B -
text-encoding-impl/dist/index.native.mjs
152B -
functional/dist/index.node.mjs
151B -
codecs/dist/index.browser.mjs
145B -
codecs/dist/index.native.mjs
144B -
codecs/dist/index.node.mjs
142B -
event-target-impl/dist/index.browser.mjs
133B -
ws-impl/dist/index.node.mjs
131B -
text-encoding-impl/dist/index.browser.mjs
122B -
fs-impl/dist/index.node.mjs
120B -
text-encoding-impl/dist/index.node.mjs
119B -
ws-impl/dist/index.browser.mjs
113B -
crypto-impl/dist/index.node.mjs
111B -
crypto-impl/dist/index.browser.mjs
109B -
rpc-parsed-types/dist/index.browser.mjs
66B -
rpc-parsed-types/dist/index.native.mjs
65B -
rpc-parsed-types/dist/index.node.mjs
63B -

No change in files bundle size

Final result: ✅

View report in BundleMon website ➡️


Current branch size history | Target branch size history

@bundlemon

bundlemon Bot commented Jul 14, 2026

Copy link
Copy Markdown

BundleMon

Unchanged files (150)
Status Path Size Limits
@solana/kit production bundle
kit/dist/index.production.min.js
55.26KB -
errors/dist/index.node.mjs
21.6KB -
errors/dist/index.browser.mjs
21.58KB -
errors/dist/index.native.mjs
21.58KB -
rpc-graphql/dist/index.browser.mjs
18.82KB -
rpc-graphql/dist/index.native.mjs
18.82KB -
rpc-graphql/dist/index.node.mjs
18.82KB -
wallet-account-signer/dist/index.node.mjs
18.31KB -
wallet-account-signer/dist/index.browser.mjs
18.29KB -
wallet-account-signer/dist/index.native.mjs
18.29KB -
transaction-messages/dist/index.browser.mjs
11.34KB -
transaction-messages/dist/index.native.mjs
11.34KB -
transaction-messages/dist/index.node.mjs
11.34KB -
instruction-plans/dist/index.browser.mjs
7.02KB -
instruction-plans/dist/index.native.mjs
7.02KB -
instruction-plans/dist/index.node.mjs
7.02KB -
codecs-data-structures/dist/index.browser.mjs
5.3KB -
codecs-data-structures/dist/index.native.mjs
5.3KB -
codecs-data-structures/dist/index.node.mjs
5.29KB -
fixed-points/dist/index.browser.mjs
5.08KB -
fixed-points/dist/index.native.mjs
5.07KB -
fixed-points/dist/index.node.mjs
5.07KB -
offchain-messages/dist/index.browser.mjs
5.06KB -
offchain-messages/dist/index.native.mjs
5.06KB -
offchain-messages/dist/index.node.mjs
5.06KB -
react/dist/index.browser.mjs
5.02KB -
react/dist/index.node.mjs
5.02KB -
react/dist/index.native.mjs
5.02KB -
kit/dist/index.browser.mjs
4.61KB -
kit/dist/index.native.mjs
4.6KB -
kit/dist/index.node.mjs
4.6KB -
transactions/dist/index.browser.mjs
4.07KB -
transactions/dist/index.native.mjs
4.07KB -
transactions/dist/index.node.mjs
4.07KB -
codecs-core/dist/index.browser.mjs
3.62KB -
codecs-core/dist/index.native.mjs
3.62KB -
codecs-core/dist/index.node.mjs
3.62KB -
webcrypto-ed25519-polyfill/dist/index.node.mj
s
3.61KB -
webcrypto-ed25519-polyfill/dist/index.browser
.mjs
3.59KB -
webcrypto-ed25519-polyfill/dist/index.native.
mjs
3.57KB -
rpc-subscriptions/dist/index.browser.mjs
3.37KB -
rpc-subscriptions/dist/index.node.mjs
3.34KB -
rpc-subscriptions/dist/index.native.mjs
3.31KB -
signers/dist/index.browser.mjs
3.26KB -
signers/dist/index.native.mjs
3.26KB -
signers/dist/index.node.mjs
3.26KB -
rpc-transformers/dist/index.browser.mjs
3.16KB -
rpc-transformers/dist/index.native.mjs
3.16KB -
rpc-transformers/dist/index.node.mjs
3.15KB -
subscribable/dist/index.node.mjs
3.13KB -
keys/dist/index.node.mjs
3.06KB -
subscribable/dist/index.native.mjs
3.06KB -
subscribable/dist/index.browser.mjs
3.05KB -
addresses/dist/index.browser.mjs
2.93KB -
addresses/dist/index.native.mjs
2.92KB -
addresses/dist/index.node.mjs
2.92KB -
keys/dist/index.browser.mjs
2.85KB -
keys/dist/index.native.mjs
2.85KB -
transaction-introspection/dist/index.browser.
mjs
2.73KB -
transaction-introspection/dist/index.native.m
js
2.73KB -
transaction-introspection/dist/index.node.mjs
2.73KB -
codecs-strings/dist/index.browser.mjs
2.55KB -
codecs-strings/dist/index.node.mjs
2.51KB -
codecs-strings/dist/index.native.mjs
2.47KB -
transaction-confirmation/dist/index.node.mjs
2.42KB -
transaction-confirmation/dist/index.native.mj
s
2.37KB -
sysvars/dist/index.browser.mjs
2.37KB -
sysvars/dist/index.native.mjs
2.37KB -
transaction-confirmation/dist/index.browser.m
js
2.37KB -
sysvars/dist/index.node.mjs
2.37KB -
rpc-subscriptions-spec/dist/index.node.mjs
2.23KB -
rpc-subscriptions-spec/dist/index.native.mjs
2.19KB -
rpc-subscriptions-spec/dist/index.browser.mjs
2.19KB -
rpc/dist/index.node.mjs
1.95KB -
codecs-numbers/dist/index.browser.mjs
1.95KB -
codecs-numbers/dist/index.native.mjs
1.95KB -
codecs-numbers/dist/index.node.mjs
1.94KB -
rpc-types/dist/index.browser.mjs
1.9KB -
rpc-types/dist/index.native.mjs
1.9KB -
rpc-types/dist/index.node.mjs
1.9KB -
rpc-transport-http/dist/index.browser.mjs
1.89KB -
rpc-transport-http/dist/index.native.mjs
1.89KB -
rpc/dist/index.native.mjs
1.81KB -
rpc/dist/index.browser.mjs
1.8KB -
rpc-transport-http/dist/index.node.mjs
1.71KB -
rpc-subscriptions-channel-websocket/dist/inde
x.node.mjs
1.33KB -
rpc-subscriptions-channel-websocket/dist/inde
x.native.mjs
1.27KB -
rpc-subscriptions-channel-websocket/dist/inde
x.browser.mjs
1.26KB -
program-client-core/dist/index.browser.mjs
1.21KB -
program-client-core/dist/index.native.mjs
1.21KB -
program-client-core/dist/index.node.mjs
1.21KB -
options/dist/index.browser.mjs
1.18KB -
options/dist/index.native.mjs
1.18KB -
options/dist/index.node.mjs
1.17KB -
accounts/dist/index.browser.mjs
1.17KB -
accounts/dist/index.native.mjs
1.17KB -
accounts/dist/index.node.mjs
1.16KB -
rpc-spec-types/dist/index.browser.mjs
1.15KB -
rpc-spec-types/dist/index.native.mjs
1.15KB -
rpc-spec-types/dist/index.node.mjs
1.15KB -
rpc-api/dist/index.browser.mjs
1.04KB -
rpc-api/dist/index.native.mjs
1.04KB -
rpc-api/dist/index.node.mjs
1.04KB -
compat/dist/index.browser.mjs
969B -
compat/dist/index.native.mjs
968B -
compat/dist/index.node.mjs
966B -
rpc-spec/dist/index.browser.mjs
898B -
rpc-spec/dist/index.native.mjs
897B -
rpc-spec/dist/index.node.mjs
896B -
rpc-subscriptions-api/dist/index.native.mjs
871B -
rpc-subscriptions-api/dist/index.browser.mjs
870B -
rpc-subscriptions-api/dist/index.node.mjs
870B -
promises/dist/index.native.mjs
841B -
promises/dist/index.node.mjs
840B -
promises/dist/index.browser.mjs
839B -
plugin-core/dist/index.browser.mjs
799B -
plugin-core/dist/index.native.mjs
798B -
plugin-core/dist/index.node.mjs
796B -
assertions/dist/index.browser.mjs
783B -
instructions/dist/index.browser.mjs
771B -
instructions/dist/index.native.mjs
770B -
instructions/dist/index.node.mjs
768B -
fast-stable-stringify/dist/index.browser.mjs
726B -
fast-stable-stringify/dist/index.native.mjs
725B -
assertions/dist/index.native.mjs
724B -
fast-stable-stringify/dist/index.node.mjs
724B -
assertions/dist/index.node.mjs
723B -
programs/dist/index.browser.mjs
329B -
programs/dist/index.native.mjs
327B -
programs/dist/index.node.mjs
325B -
fs-impl/dist/index.browser.mjs
245B -
event-target-impl/dist/index.node.mjs
230B -
functional/dist/index.browser.mjs
154B -
functional/dist/index.native.mjs
152B -
text-encoding-impl/dist/index.native.mjs
152B -
functional/dist/index.node.mjs
151B -
codecs/dist/index.browser.mjs
145B -
codecs/dist/index.native.mjs
144B -
codecs/dist/index.node.mjs
142B -
event-target-impl/dist/index.browser.mjs
133B -
ws-impl/dist/index.node.mjs
131B -
text-encoding-impl/dist/index.browser.mjs
122B -
fs-impl/dist/index.node.mjs
120B -
text-encoding-impl/dist/index.node.mjs
119B -
ws-impl/dist/index.browser.mjs
113B -
crypto-impl/dist/index.node.mjs
111B -
crypto-impl/dist/index.browser.mjs
109B -
rpc-parsed-types/dist/index.browser.mjs
66B -
rpc-parsed-types/dist/index.native.mjs
65B -
rpc-parsed-types/dist/index.node.mjs
63B -

No change in files bundle size

Final result: ✅

View report in BundleMon website ➡️


Current branch size history | Target branch size history

@github-actions

github-actions Bot commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

Documentation Preview: https://kit-docs-ex7erm46g-anza-tech.vercel.app

@mcintyre94

Copy link
Copy Markdown
Member Author

@trevor-cortex

@mcintyre94 mcintyre94 changed the title Draft: Update the react-app example to use the wallet plugin Update the react-app example to use the wallet plugin Jul 14, 2026

@trevor-cortex trevor-cortex left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summary

Migrates the examples/react-app demo from @wallet-standard/react (useSelectedWalletAccount, useConnect, useDisconnect, useWalletAccount*Signer) to the @solana/kit-plugin-wallet model — a Kit Client built with the walletSigner plugin, published via ClientProvider, and consumed through the plugin's React hooks (useConnectedWallet, useConnect, useDisconnect, useSelectAccount, useSignIn, useSignMessage, useWallets, WalletReadyGate). Also bumps @wallet-standard/ui^1.0.3 and @wallet-standard/ui-registry^1.1.1 in packages/react so a consumer that mixes @solana/react with @solana/kit-plugin-wallet resolves a single, shared registry singleton (well-explained in the changeset).

The example ports feature-for-feature: connect/disconnect/sign-in dropdowns, sign message / sign transaction / sign-and-send / partial sign panels, balance, chain switching, and the recipient picker. The former SelectedWalletAccountContextProvider + localStorage sync is replaced by the plugin's own connected-account state, and UnconnectableWalletMenuItem/ErrorBoundary wrapping is dropped because useWallets() already filters to wallets that support standard:connect on the active chain.

A new WalletClientProvider handles the chain-switch client rebuild (each wallet plugin is chain-bound) with hand-over-hand disposal and a whenReady() handoff so mid-switch the old client stays on screen. WalletReadyGate gates only the wallet-dependent UI so the chrome stays visible during the initial warm-up. Feature panels now call render-time assertCan… guards (walletCapability.ts) that throw into the existing FeatureNotSupportedCallout ErrorBoundary, replacing the previous static feature-list checks in the wallet-menu items.

Overall the migration is clean and the tricky bits (client lifecycle, StrictMode double-invoke, whenReady() deadlock avoidance, optimizeDeps for the linked workspace) are all called out in comments. This is a good reference for how the plugin is meant to be consumed from a React app.

Key things to watch out for

  • WalletClientProvider lifecycle. Worth re-reading the docblock and effect carefully — the invariants (who owns disposal at each point, why cancelled is load-bearing, why the whenReady continuation can't own disposal) are all correct as far as I can tell, but this is the kind of code that's easy to break in a follow-up. Suggest keeping the current comments verbatim as a regression guard, and considering a small test in a later PR that exercises the chain-switch and StrictMode double-invoke paths — an example app doesn't have to have one, but this provider is subtle enough that it might be worth extracting for reuse eventually.
  • Draft / follow-ups. PR description flags (a) the pending bump to @solana/kit-plugin-wallet@0.14.0 (currently open on kit-plugins) will require a small refactor, and (b) future iterations will add the solanaRpc and system-program plugins to the client. That's fine for a draft, just wanted to surface it here so it isn't lost.
  • @wallet-standard/ui-registry bump in @solana/react. ^1.0.1^1.1.1 is a minor bump, shipped as a patch changeset. The changeset justifies it as a backward-compatible superset (registry is a runtime singleton and this de-duplicates the shared instance), which reads reasonably to me — but flagging for a second pair of eyes since this is the only user-facing change in a published package.
  • packages/react still depends on @wallet-standard/react. The example app dropped it, but SelectedWalletAccountContextProvider / selectedWalletAccountContext are still exported from @solana/react and still use it, so this is intentional. Just noting in case a follow-up wants to prune those exports now that the recommended flow no longer needs them.

Notes for subsequent reviewers

  • The ErrorBoundary around each feature panel is now doing double duty: it still catches thrown-from-render errors from the Kit signer hooks, and it now also catches the new assertCan… throws from walletCapability.ts. The message the user sees in FeatureNotSupportedCallout should be checked once against a wallet that doesn't support each feature (e.g. a wallet without solana:signMessage, or a sending-only wallet against the "Sign Transaction" panel) to make sure the copy matches expectations.
  • The SignInMenu used to wrap each menu item in an ErrorBoundaryUnconnectableWalletMenuItem. That safety net is gone; sign-in failures now surface through onErrorErrorDialog. That's a behaviour change worth verifying — since SolanaSignIn is a per-feature filter (not a per-chain connect filter), any wallet that advertises the feature but errors on invocation should reach onError correctly, but a render-time throw from useSignIn() would now propagate. In practice it shouldn't happen, but worth a manual smoke test.
  • Manually verifying under StrictMode is worth doing: the WalletClientProvider comment describes the StrictMode double-invoke path, and it's the most likely place for a subtle regression to hide.

Comment on lines 21 to 28
return (
<ErrorBoundary
fallbackRender={({ error }) => <UnconnectableWalletMenuItem error={error} wallet={wallet} />}
<SignInMenuItem
key={`wallet:${wallet.name}`}
>
<SignInMenuItem
onSignIn={account => {
setSelectedWalletAccount(account);
setForceClose(true);
}}
onError={setError}
wallet={wallet}
/>
</ErrorBoundary>
onSignIn={() => setForceClose(true)}
onError={setError}
wallet={wallet}
/>
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Behaviour change worth double-checking: previously each SignInMenuItem was wrapped in an ErrorBoundary that fell back to UnconnectableWalletMenuItem, so a wallet that advertised solana:signIn but crashed at hook-init render time still rendered as a disabled item rather than tearing down the menu. That safety net is gone here. Sign-in invocation errors still surface through onErrorErrorDialog (good), but a render-time throw from useSignIn() would now propagate up. In practice useSignIn() from the plugin shouldn't throw at render, so this is probably fine — flagging so it's an explicit decision rather than an accidental regression.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is intentional - the plugin version doesn't call the sign-in function at render, only when the action is dispatched. So there's no render-time failure path here

Comment thread examples/react-app/src/context/WalletClientProvider.tsx Outdated
Comment on lines +30 to +34
if (!signer || !(isTransactionModifyingSigner(signer) || isTransactionPartialSigner(signer))) {
throw new Error('This account does not support signing transactions');
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Worth a passing note in the docblock (or here) that a TransactionSendingSigner also satisfies TransactionSigner structurally (a modifying signer that can also send), but this assert deliberately narrows to partial-or-modifying-only because the "Sign Transaction" / "Partial Sign Transaction" panels need to obtain the raw signed transaction bytes rather than have the wallet send it. The current docblock says "deliberately fails this assert" which is right — just calling out that the reason is UX-level (panel semantics), not that a sending signer isn't a valid TransactionSigner. Not blocking, but the comment might read more clearly if it said e.g. "…because those panels need the signed transaction bytes, not a submitted signature".

Comment on lines +20 to +27
optimizeDeps: {
// `@solana/react` is a linked workspace package, so Vite excludes it from dependency
// pre-bundling by default. When Vite then pre-bundles the npm dependency
// `@solana/kit-plugin-wallet` (which imports `@solana/react`), esbuild inlines a *second*
// copy of `@solana/react`, which causes mis-matched context.
// Forcing `@solana/react` to be pre-bundled makes it a single shared copy
// that both the app and the plugin reference.
// This is only an issue in our example because of the linked workspace.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great comment — this is exactly the kind of thing that would burn hours to rediscover. One small suggestion: consider mentioning that the two-copies-of-@solana/react symptom typically surfaces as a context mismatch at runtime (e.g. useClient() from one copy reading null even though ClientProvider from the other copy is mounted above it), so a future reader hitting that symptom can search for it and land here. Optional.

@mcintyre94
mcintyre94 force-pushed the react-app-wallet-plugin branch from 3fb47c3 to 5abe375 Compare July 14, 2026 16:07
@mcintyre94
mcintyre94 marked this pull request as ready for review July 14, 2026 16:08
@mcintyre94
mcintyre94 requested a review from lorisleiva July 14, 2026 16:08
Copilot AI review requested due to automatic review settings July 27, 2026 14:00
@mcintyre94
mcintyre94 force-pushed the react-app-wallet-plugin branch from 5abe375 to 78c7d28 Compare July 27, 2026 14:00

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates the examples/react-app sample to use the @solana/kit-plugin-wallet client/plugin model (including its React hooks), and aligns @solana/react’s wallet-standard UI dependencies to avoid multiple incompatible wallet-standard UI registry singletons when consumers also depend on the wallet plugin.

Changes:

  • Switch the react-app example from @wallet-standard/react/@solana/react wallet selection to @solana/kit-plugin-wallet (client + hooks + WalletReadyGate).
  • Add a wallet-capability assertion layer (walletCapability.ts) to gate feature panels via render-time asserts and ErrorBoundary fallbacks.
  • Bump @solana/react’s @wallet-standard/ui / @wallet-standard/ui-registry versions and update lockfile accordingly (with a changeset).

Reviewed changes

Copilot reviewed 25 out of 26 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
pnpm-lock.yaml Locks new wallet plugin dependency for the example and updates wallet-standard/ui versions across the workspace.
packages/react/package.json Bumps @wallet-standard/ui and @wallet-standard/ui-registry to match wallet plugin expectations.
examples/react-app/package.json Adds @solana/kit-plugin-wallet and migrates example dependencies from @wallet-standard/react to @wallet-standard/ui.
examples/react-app/vite.config.ts Forces pre-bundling @solana/react to avoid duplicated linked-workspace copies when prebundling the wallet plugin.
examples/react-app/src/context/WalletClientProvider.tsx Introduces a client provider that rebuilds/disposes the Kit client on chain changes with wallet plugin installed.
examples/react-app/src/context/ChainContext.tsx Refines chain typing to SolanaChain for wallet-standard compatibility.
examples/react-app/src/walletCapability.ts Adds render-time capability asserts for transaction signing/sending and message signing.
examples/react-app/src/main.tsx Replaces prior wallet context wiring with WalletClientProvider and gates wallet-dependent UI via WalletReadyGate.
examples/react-app/src/routes/root.tsx Switches root route to useConnectedWallet() and passes signer/account to feature panels.
examples/react-app/src/components/Nav.tsx Disables wallet menus until wallet client is ready; updates chain casting to SolanaChain.
examples/react-app/src/components/ConnectWalletMenu.tsx Migrates wallet listing/connection UI to wallet plugin hooks; updates empty-wallet messaging.
examples/react-app/src/components/ConnectWalletMenuItem.tsx Reworks connect/select-account/disconnect behavior using wallet plugin actions and abort handling.
examples/react-app/src/components/SignInMenu.tsx Migrates sign-in menu to plugin-provided wallets list and simplified menu items.
examples/react-app/src/components/SignInMenuItem.tsx Switches to plugin useSignIn() action store; filters abort errors and uses action running state.
examples/react-app/src/components/SolanaSignMessageFeaturePanel.tsx Uses plugin useSignMessage() and capability assert instead of legacy message-signer hook.
examples/react-app/src/components/SolanaSignTransactionFeaturePanel.tsx Switches from account-based signer hook to plugin-provided `WalletSigner
examples/react-app/src/components/SolanaSignAndSendTransactionFeaturePanel.tsx Switches to plugin signer + capability assert and updates recipient/wallet plumbing.
examples/react-app/src/components/SolanaPartialSignTransactionFeaturePanel.tsx Switches to plugin signer + capability assert and updates imports/types accordingly.
examples/react-app/src/components/WalletAccountIcon.tsx Uses plugin useWallets() and @wallet-standard/ui types/helpers for icon lookup.
examples/react-app/src/components/WalletMenuItemContent.tsx Updates UiWallet import source to @wallet-standard/ui.
examples/react-app/src/components/BaseSignMessageFeaturePanel.tsx Uses Kit’s ReadonlyUint8Array type instead of wallet-standard core’s type.
examples/react-app/src/components/Balance.tsx Updates UiWalletAccount import source to @wallet-standard/ui.
examples/react-app/src/components/tests/Balance-test.browser.tsx Updates UiWalletAccount import source to @wallet-standard/ui to match runtime changes.
examples/react-app/src/components/UnconnectableWalletMenuItem.tsx Removes legacy “unconnectable wallet” menu item component now that plugin pre-filters wallets.
examples/react-app/src/components/DisconnectButton.tsx Removes legacy disconnect button that relied on wallet-standard/react hooks directly.
.changeset/slick-rabbits-admire.md Adds changeset for @solana/react wallet-standard UI dependency bumps.
Files not reviewed (1)
  • pnpm-lock.yaml: Generated file

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

* `solana:signTransaction`) deliberately fails this assert even though it satisfies
* `TransactionSigner`, because it doesn't work with those panels.
*/
export function assertCanSignTransactions(signer: WalletSigner | null): asserts signer is TransactionSigner {
@mcintyre94

Copy link
Copy Markdown
Member Author

@trevor-cortex

@trevor-cortex trevor-cortex left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice iteration. The 0.14 hook API (client as first param) is threaded through cleanly, and the new decomposition — Dimmable, GatedRoot, SlotIndicatorPanel, plus useDisplayedWallet / useHasWalletSettled — is a real readability win over the previous inline logic. The stale-vs-settled distinction is subtle, and pulling it into two small named hooks with docblocks that spell out why they're separate is the right call. New tests cover the tricky bits (StrictMode double-mount disposal, retained-vs-live connection, latching behavior) — that's exactly where I would have wanted them.

A few small notes below; none block. Given Loris already approved, treat these as follow-up thoughts rather than change requests.

Things worth a second look:

  1. Duplicated "busy button" styling between ConnectWalletMenu and SignInMenu. Both apply the same aria-busy/opacity: 0.5/pointerEvents: 'none'/transition: 150ms block to their <Button> triggers. Dimmable can't wrap a Trigger child (Radix needs the button directly), but a tiny <BusyButton> component (or a useBusyButtonProps(busy) hook that returns the props bag) would keep the two triggers in lockstep and match the abstraction level of Dimmable.

  2. ConnectWalletMenu and SignInMenu use different signals for the same UX. ConnectWalletMenu disables via isStale (from useDisplayedWallet), SignInMenu disables via !isReady (from useIsWalletReady). They're the same value today (isStale === !isReady), but a reader has to work that out. Deriving both from the same primitive — or using useDisplayedWallet's isStale in both — would make the intent unambiguous.

  3. SolanaSignMessageFeaturePanel deliberately leaks a WALLET_STANDARD_ERROR__* up to the boundary so getErrorMessage can format it consistently with the other panels. walletCapability.ts explains this, which is great — but a reader landing in SolanaSignMessageFeaturePanel.tsx first won't have that context. Consider a one-line comment on the assertCanSignMessages(account) call pointing at the docblock (or restating the "we intentionally let the wallet-standard error through here" bit).

Notes for later reviewers:

  • The two custom hooks disable react-hooks/refs and mutate a ref during render. That's safe here because the mutation is a pure function of the props (isReady, connected), so StrictMode's double-render is idempotent — and there are tests exercising exactly that path via the StrictMode-wrapping render helper. Worth being aware of when reading them.
  • WalletClientProvider publishes the client synchronously from a useLayoutEffect and disposes it in cleanup. Under StrictMode the effect runs twice, so the mount test's mockPublishedClients array will actually contain the disposed dev-double and the live client — [length - 1] correctly picks the live one, but if that test ever gets tightened it's worth asserting the disposed one was in fact disposed.
  • Follow-ups the PR description already flags: solanaRpc + system-program plugins, and any hooks that currently accept client explicitly could later move to a useClient()-implicit style if the plugin gains one.

mcintyre94 commented Aug 4, 2026

Copy link
Copy Markdown
Member Author

Merge activity

Base automatically changed from use-client-nonoptional to main August 4, 2026 16:15
@mcintyre94
mcintyre94 force-pushed the react-app-wallet-plugin branch from e352dce to 581e54a Compare August 4, 2026 16:15
@mcintyre94
mcintyre94 force-pushed the react-app-wallet-plugin branch from 581e54a to 64ea156 Compare August 4, 2026 16:21
@mcintyre94
mcintyre94 merged commit d54b899 into main Aug 4, 2026
14 checks passed
@mcintyre94
mcintyre94 deleted the react-app-wallet-plugin branch August 4, 2026 17:12
@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

🔎💬 Inkeep AI search and chat service is syncing content for source 'Solana Kit Docs'

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants