
Description
Sui frontend / dApp development with @mysten/dapp-kit-react (React) and @mysten/dapp-kit-core (Vue, vanilla JS, Svelte, Web Components, other frameworks). Use when building browser apps that connect Sui wallets, query on-chain state, or submit transactions. Covers wallet connection, network switching, transaction execution, query patterns with TanStack React Query, and the specific pitfalls of browser + wallet + async-indexer environments. Pair with the `sui-sdks` skill for @mysten/sui Transaction construction patterns and the `ptbs` skill for PTB semantics.
SKILL.md
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 (ortx.serialize()) 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.mdLoad 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.mdLoad 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.mdLoad 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.mdLoad 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.mdLoad 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.mdLoad 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 (ortx.serialize()) 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 (ortx.serialize()).
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.
More skills from the skills repository
View all 20 skillsaccessing-data
read data from the Sui network
Aug 1Data AnalysisSuiWeb3composable-move-functions
design composable Sui Move functions
Jul 16API DevelopmentSmart ContractsSuiWeb3generate-sui-agent-config
generate configuration files for Sui projects
Jul 16ConfigurationDocumentationSuimodern-move-syntax
write Move 2024 edition code for Sui
Jul 16EngineeringSmart ContractsSuiWeb3move-unit-testing
write unit tests for Sui Move contracts
Aug 1QASmart ContractsSuiTesting +1naming-conventions
apply Sui Move naming conventions
Jul 16Best PracticesSmart ContractsSuiWeb3
More from Sui (Mysten Labs)
View publishermove-bytecode-comprehension
analyze and disassemble Move bytecode
sui
Jul 16Code AnalysisEngineeringSuiofficial-sui-skills
access official Sui development resources
sui
Jul 16DocumentationSuisui-and-move-tools
disassemble Sui Move bytecode
sui
Jul 16Code AnalysisEngineeringSuisui-move-security-review
audit Sui Move smart contracts
sui
Jul 16Code ReviewSecuritySmart ContractsSuimemwal
integrate Walrus Memory SDK
MemWal
Jul 16AgentsMemorySDK