Sui Frontend / dApp Kit
MCP tool: When available in your environment, also query the Sui documentation MCP server (
https://sui.mcp.kapa.ai) for up-to-date answers. Use it for verification and for details not covered by these reference files.
Browser Sui apps fail for a consistent set of reasons:
- Wrong package.
@mysten/dapp-kit(no suffix) is the legacy JSON-RPC-only package — deprecated. New code uses@mysten/dapp-kit-reactor@mysten/dapp-kit-core. - Wrong client. dApp Kit takes a
SuiGrpcClient(recommended) increateDAppKit'screateClient. Do not passSuiJsonRpcClient— JSON-RPC is deprecated. - Old provider stack. Code often tries the v1 pattern:
QueryClientProvider→SuiClientProvider→WalletProvider. That's gone. New pattern:createDAppKitfactory +DAppKitProvider(or a non-React equivalent). - Dead hooks.
useSuiClientQuery,useSuiClientInfiniteQuery,useSignAndExecuteTransaction(mutation hook),useConnectWallet,useDisconnectWallet,useSuiClient,useSuiClientContext— removed. Replaced byuseCurrentClient/useCurrentNetwork/useDAppKit()(imperative methods) + your own TanStack Query wrappers. - Skipping
waitForTransactionbetween execute and refetch. Fullnodes index transactions asynchronously — invalidating TanStack caches immediately aftersignAndExecuteTransactionrefetches stale data. - Building PTBs in app code with
tx.build()before handing to the wallet. Defeats the wallet's gas selection. Always pass theTransactioninstance to the wallet.
All patterns in this skill are derived from:
- https://sdk.mystenlabs.com/dapp-kit (landing)
- https://sdk.mystenlabs.com/dapp-kit/getting-started/react
- https://sdk.mystenlabs.com/dapp-kit/getting-started/next-js
- https://sdk.mystenlabs.com/dapp-kit/getting-started/vue
- https://docs.sui.io/standards/wallet-standard
If unsure about any API, fetch from the relevant page — do not extrapolate from the legacy @mysten/dapp-kit or the pre-v2 hook surface. Many outdated tutorials exist.
Reference files
setup — Install, factory, provider
Path: setup.md
Load when: starting a new dApp project, scaffolding React/Next.js/Vue setup, or migrating from the old three-provider pattern. Covers package choice, createDAppKit({ networks, createClient }), DAppKitProvider (React), and the declare module TypeScript augmentation.
react — React hooks & patterns
Path: react.md
Load when: writing React components. Covers every current hook (useCurrentAccount, useCurrentWallet, useCurrentNetwork, useCurrentClient, useDAppKit, useWallets, useWalletConnection), standard components (ConnectButton), and wallet-gated UI idioms.
non-react — Vue, vanilla JS, Web Components
Path: non-react.md
Load when: building Vue / Svelte / vanilla JS dApps. Covers @mysten/dapp-kit-core, nanostores reactive state (dAppKit.stores.$connection etc.), Web Components registration, Vue bindings via @nanostores/vue.
queries — TanStack Query patterns
Path: queries.md
Load when: fetching on-chain data (balances, owned objects, coins, dynamic fields, transactions) in a dApp. Covers useQuery + useCurrentClient, useInfiniteQuery for paginated results, enabled guards, and cache invalidation after writes.
transactions — Signing and executing
Path: transactions.md
Load when: submitting any transaction from a dApp. Covers dAppKit.signAndExecuteTransaction, the $kind-based result discriminant, signTransaction (wallet signs but doesn't execute — for sponsored flows), signPersonalMessage, the waitForTransaction + invalidateQueries sequence, and common wallet UX failures.
limitations — What frontends can't or shouldn't do
Path: limitations.md
Load when: a user is designing something that feels like it crosses a browser-environment boundary. Covers: no backend-only features (gas station internals, validator keys), browser/SSR caveats, auto-connect reliability, and the "don't put secrets in the browser" rules.
Routing guide
| Task | Load |
|---|---|
| New React dApp from scratch | setup + react + queries + transactions |
| New Vue / vanilla / Svelte dApp | setup + non-react |
| "How do I connect a wallet?" | react (or non-react) |
| "How do I query a balance / owned objects?" | queries |
| "How do I send a transaction?" | transactions + (sui-sdks for PTB construction) |
| Sponsored tx flow | transactions (front-end side) + ptbs (PTB side) |
Migrating from @mysten/dapp-kit (no suffix) |
setup + limitations + all as needed |
| Why is my UI stale after a tx? | transactions + queries |
| Dealing with SSR / Next.js | setup + limitations |
| Full code review of a dApp | all reference files + the code-review checklist below |
| "Review this code" / "what's wrong with this snippet" | the code-review checklist below + relevant reference files |
Skill Content
Key concepts
- Two packages, one API.
@mysten/dapp-kit-reactwraps@mysten/dapp-kit-core.createDAppKitexists in both; actions (signAndExecuteTransaction,signTransaction,switchNetwork,connectWallet,disconnectWallet) are identical. What differs is how you read reactive state: React uses hooks; non-React reads nanostores stores. - One instance, many networks.
createDAppKit({ networks: [...], createClient })creates one dApp Kit instance that knows about multiple networks.dAppKit.switchNetwork(name)changes the active one. ThecreateClientfactory is called once per network, lazily. - gRPC by default. The new dApp Kit is built for
SuiGrpcClient. JSON-RPC is deprecated; useSuiGrpcClientincreateClient. - Wallets are browser-only. Wallet detection uses
window.navigator.walletsand CustomEvents. Any component that touches wallet state must be client-side rendered. In Next.js / SSR frameworks this means'use client'on wallet-aware components. - The wallet owns gas. Apps build the
Transactionand pass it to the wallet. The wallet picks gas coins, sets budget (via dry-run), and signs. Never calltx.build()+ pass bytes unless it's a sponsored flow — seetransactions.md. - Fullnodes are eventually consistent.
signAndExecuteTransactionreturns a digest before the data is queryable. AlwayswaitForTransactionbefore refetching.
Rules
- Use
@mysten/dapp-kit-reactor@mysten/dapp-kit-core— never the bare@mysten/dapp-kitin new code. That package is JSON-RPC-only and deprecated. - Use
SuiGrpcClientincreateClient. NotSuiClient—SuiClientis removed in v2; useSuiGrpcClientfrom@mysten/sui/grpc. - Use
createDAppKit+DAppKitProvider. Not the three-provider stack (QueryClientProvider+SuiClientProvider+WalletProvider). You still wrap withQueryClientProviderif you use TanStack Query for data fetching, but dApp Kit itself doesn't need it. - Include the
declare moduleTypeScript augmentation so hooks get proper types without passing the instance manually. - Do not use the removed hooks.
useSuiClientQuery/useSuiClientInfiniteQuery/useSuiClientContext/useSuiClient/useSignAndExecuteTransaction(mutation hook) /useConnectWallet/useDisconnectWallet— gone. UseuseCurrentClient+useQuery/useInfiniteQuery+useDAppKit()imperative methods. - Null-check the current account.
useCurrentAccount()returnsnullbefore connection. Alwaysif (!account) return/ gate withenabled: !!accountin queries. waitForTransactionbefore cache invalidation.await client.waitForTransaction({ digest: result.Transaction.digest })thenqueryClient.invalidateQueries(...). Reversing this fetches stale data.- Pass the
Transactioninstance to the wallet, notawait tx.build(...)bytes. The wallet needs to own gas selection. Exception: sponsored flows that usetx.build({ client, onlyTransactionKind: true })— seeptbsskill. - Check
result.$kind === 'FailedTransaction'(orresult.FailedTransaction). Don't assume success. Don't use v1'sresult.effects?.status?.status. - Wallet-gated UI must client-render. SSR without a client-side guard renders wallet buttons before wallets are detectable. Use
'use client'/ dynamic imports / effect-based hydration. - Vue:
useStorereturns a Vue ref — use.valuein script code.const connection = useStore(dAppKit.stores.$connection)returns a ref. Access state asconnection.value.accountin<script setup>. Vue auto-unwraps refs in templates, but always show the.valuepattern in script examples.
Code-review checklist
When the user asks you to review a code snippet, do not stop at the first 2–3 issues you spot. Walk this checklist explicitly and call out every match. Browser Sui code typically has 5–10 issues stacked in a single component because it was copied from a v1 tutorial.
Imports / packages
@mysten/sui.js(anywhere) — frozen v1 package; replace with@mysten/sui.@mysten/dapp-kit(no suffix) — deprecated JSON-RPC-only package; replace with@mysten/dapp-kit-react(React) or@mysten/dapp-kit-core(other frameworks).import { ConnectButton } from '@mysten/dapp-kit-react'— wrong path;ConnectButtonandConnectModalare exported from@mysten/dapp-kit-react/ui. Using the wrong path causes a silent white screen.import { SuiClient }—SuiClientis removed in v2 — useSuiGrpcClientfrom@mysten/sui/grpc(oruseCurrentClient()inside components).import { TransactionBlock }— renamed toTransactionin v2.
Removed hooks (any of these = bug)
useSuiClientQuery,useSuiClientInfiniteQuery,useSuiClientContext,useSuiClient— replace withuseCurrentClient()+ TanStackuseQuery/useInfiniteQuery.useSignAndExecuteTransaction(mutation hook) — replace withuseDAppKit().signAndExecuteTransaction(...)called imperatively in event handlers.useConnectWallet,useDisconnectWallet— replace withuseDAppKit().connectWallet()/disconnectWallet().
Provider stack
SuiClientProvider+WalletProvider(the v1 three-provider stack) — replace withcreateDAppKit(...)+<DAppKitProvider>.QueryClientProviderfrom TanStack is still allowed.
Client construction inside components
new SuiClient(...)/new SuiGrpcClient(...)inside a component body — breaks network switching and re-creates a client per render. UseuseCurrentClient().- Missing
enabled: !!accounton queries that need a connected wallet — fires on undefined owner and errors.
Transaction construction
tx.pure(value)(untyped) — replace with the typed helper matching the Move type:tx.pure.u64(n),tx.pure.address(addr),tx.pure.string(s), etc.tx.build()before handing to the wallet — defeats wallet gas selection. Pass theTransactioninstance directly.
Execute / wait / status
signAndExecuteTransactionBlock(...)(v1 method) — replace withsignAndExecuteTransaction(...).{ transactionBlock: tx }parameter shape — replace with{ transaction: tx }.result.effects?.status?.status === 'success'— v1 shape; replace withresult.$kind !== 'FailedTransaction'(or checkresult.FailedTransaction).result.digestdirect access — on success the digest is atresult.Transaction.digest; on failure there's no digest.- Cache invalidation immediately after execute — must
await client.waitForTransaction({ digest })first, thenqueryClient.invalidateQueries(...).
SSR / Next.js
- Wallet-aware component without
'use client'— wallet detection uses browser-only APIs.
After walking the list, count the distinct issues you found. If it's fewer than 5 on a typical multi-line snippet pulled from an outdated tutorial, re-read the snippet — you almost certainly missed something.
Common mistakes
- Using
@tanstack/react-query'suseQuerywithoutenabled: !!accountfor queries that require a connected wallet. The query fires with undefined owner and errors. - Returning a
Transactionfrom a React-QueryqueryFn. Transactions aren't queries — use them in mutations or event handlers viauseDAppKit(). dAppKit.signAndExecuteTransaction({ transactionBlock: tx }). It's{ transaction: tx }in the new API.- Reading
result.digestdirectly. On success the digest is atresult.Transaction.digest. On failure there's no digest — checkresult.FailedTransactionfirst. - Invalidating queries before
waitForTransaction. Classic stale-UI bug. Always wait first. - Calling
new SuiGrpcClient(...)inside components. Breaks network switching. UseuseCurrentClient(). - Instantiating
SuinsClient/DeepBookClientdirectly. Useclient.$extend(suins(), deepbook({ address }))on the client returned fromcreateClient. - Leaving
autoConnect: truewith a confusing UX. Auto-reconnect restores the last wallet on load. If the app handles permissions/nonces, account for the async nature of that restore (pending / reconnecting states). - Hardcoding testnet / mainnet URLs in multiple places. Keep them in a single
GRPC_URLSmap keyed by network name and reference fromcreateClient. - Using wallet-standard APIs directly. dApp Kit wraps the wallet standard — don't call
window.navigator.walletsyourself; useuseWallets()/dAppKit.stores.$wallets. - Storing private keys or secrets in the browser. Never. dApps sign via wallets; backends hold keys.