Sui (Mysten Labs) logo

Skill

frontend-apps

build Sui dApps with dapp-kit

Covers React Sui Vue Svelte Web3 Frontend

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:

  1. Wrong package. @mysten/dapp-kit (no suffix) is the legacy JSON-RPC-only package — deprecated. New code uses @mysten/dapp-kit-react or @mysten/dapp-kit-core.
  2. Wrong client. dApp Kit takes a SuiGrpcClient (recommended) in createDAppKit's createClient. Do not pass SuiJsonRpcClient — JSON-RPC is deprecated.
  3. Old provider stack. Code often tries the v1 pattern: QueryClientProviderSuiClientProviderWalletProvider. That's gone. New pattern: createDAppKit factory + DAppKitProvider (or a non-React equivalent).
  4. Dead hooks. useSuiClientQuery, useSuiClientInfiniteQuery, useSignAndExecuteTransaction (mutation hook), useConnectWallet, useDisconnectWallet, useSuiClient, useSuiClientContextremoved. Replaced by useCurrentClient / useCurrentNetwork / useDAppKit() (imperative methods) + your own TanStack Query wrappers.
  5. Skipping waitForTransaction between execute and refetch. Fullnodes index transactions asynchronously — invalidating TanStack caches immediately after signAndExecuteTransaction refetches stale data.
  6. Building PTBs in app code with tx.build() before handing to the wallet. Defeats the wallet's gas selection. Always pass the Transaction instance (or tx.serialize()) to the wallet.

All patterns in this skill are derived from:

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

TaskLoad
New React dApp from scratchsetup + react + queries + transactions
New Vue / vanilla / Svelte dAppsetup + 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 flowtransactions (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.jssetup + limitations
Full code review of a dAppall 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-react wraps @mysten/dapp-kit-core. createDAppKit exists 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. The createClient factory is called once per network, lazily.
  • gRPC by default. The new dApp Kit is built for SuiGrpcClient. JSON-RPC is deprecated; use SuiGrpcClient in createClient.
  • Wallets are browser-only. Wallet detection uses window.navigator.wallets and 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 Transaction and pass it to the wallet. The wallet picks gas coins, sets budget (via dry-run), and signs. Never call tx.build() + pass bytes unless it's a sponsored flow — see transactions.md.
  • Fullnodes are eventually consistent. signAndExecuteTransaction returns a digest before the data is queryable. Always waitForTransaction before refetching.

Rules

  1. Use @mysten/dapp-kit-react or @mysten/dapp-kit-core — never the bare @mysten/dapp-kit in new code. That package is JSON-RPC-only and deprecated.
  2. Use SuiGrpcClient in createClient. Not SuiClientSuiClient is removed in v2; use SuiGrpcClient from @mysten/sui/grpc.
  3. Use createDAppKit + DAppKitProvider. Not the three-provider stack (QueryClientProvider + SuiClientProvider + WalletProvider). You still wrap with QueryClientProvider if you use TanStack Query for data fetching, but dApp Kit itself doesn't need it.
  4. Include the declare module TypeScript augmentation so hooks get proper types without passing the instance manually.
  5. Do not use the removed hooks. useSuiClientQuery / useSuiClientInfiniteQuery / useSuiClientContext / useSuiClient / useSignAndExecuteTransaction (mutation hook) / useConnectWallet / useDisconnectWallet — gone. Use useCurrentClient + useQuery/useInfiniteQuery + useDAppKit() imperative methods.
  6. Null-check the current account. useCurrentAccount() returns null before connection. Always if (!account) return / gate with enabled: !!account in queries.
  7. waitForTransaction before cache invalidation. await client.waitForTransaction({ digest: result.Transaction.digest }) then queryClient.invalidateQueries(...). Reversing this fetches stale data.
  8. Pass the Transaction instance (or tx.serialize()) to the wallet, not await tx.build(...) bytes. The wallet needs to own gas selection. Exception: sponsored flows that use tx.build({ client, onlyTransactionKind: true }) — see ptbs skill.
  9. Check result.$kind === 'FailedTransaction' (or result.FailedTransaction). Don't assume success. Don't use v1's result.effects?.status?.status.
  10. 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.
  11. Vue: useStore returns a Vue ref — use .value in script code. const connection = useStore(dAppKit.stores.$connection) returns a ref. Access state as connection.value.account in <script setup>. Vue auto-unwraps refs in templates, but always show the .value pattern 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; ConnectButton and ConnectModal are exported from @mysten/dapp-kit-react/ui. Using the wrong path causes a silent white screen.
  • import { SuiClient }SuiClient is removed in v2 — use SuiGrpcClient from @mysten/sui/grpc (or useCurrentClient() inside components).
  • import { TransactionBlock } — renamed to Transaction in v2.

Removed hooks (any of these = bug)

  • useSuiClientQuery, useSuiClientInfiniteQuery, useSuiClientContext, useSuiClient — replace with useCurrentClient() + TanStack useQuery / useInfiniteQuery.
  • useSignAndExecuteTransaction (mutation hook) — replace with useDAppKit().signAndExecuteTransaction(...) called imperatively in event handlers.
  • useConnectWallet, useDisconnectWallet — replace with useDAppKit().connectWallet() / disconnectWallet().

Provider stack

  • SuiClientProvider + WalletProvider (the v1 three-provider stack) — replace with createDAppKit(...) + <DAppKitProvider>. QueryClientProvider from 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. Use useCurrentClient().
  • Missing enabled: !!account on 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 the Transaction instance (or tx.serialize()).

Execute / wait / status

  • signAndExecuteTransactionBlock(...) (v1 method) — replace with signAndExecuteTransaction(...).
  • { transactionBlock: tx } parameter shape — replace with { transaction: tx }.
  • result.effects?.status?.status === 'success' — v1 shape; replace with result.$kind !== 'FailedTransaction' (or check result.FailedTransaction).
  • result.digest direct access — on success the digest is at result.Transaction.digest; on failure there's no digest.
  • Cache invalidation immediately after execute — must await client.waitForTransaction({ digest }) first, then queryClient.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's useQuery without enabled: !!account for queries that require a connected wallet. The query fires with undefined owner and errors.
  • Returning a Transaction from a React-Query queryFn. Transactions aren't queries — use them in mutations or event handlers via useDAppKit().
  • dAppKit.signAndExecuteTransaction({ transactionBlock: tx }). It's { transaction: tx } in the new API.
  • Reading result.digest directly. On success the digest is at result.Transaction.digest. On failure there's no digest — check result.FailedTransaction first.
  • Invalidating queries before waitForTransaction. Classic stale-UI bug. Always wait first.
  • Calling new SuiGrpcClient(...) inside components. Breaks network switching. Use useCurrentClient().
  • Instantiating SuinsClient / DeepBookClient directly. Use client.$extend(suins(), deepbook({ address })) on the client returned from createClient.
  • Leaving autoConnect: true with 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_URLS map keyed by network name and reference from createClient.
  • Using wallet-standard APIs directly. dApp Kit wraps the wallet standard — don't call window.navigator.wallets yourself; use useWallets() / dAppKit.stores.$wallets.
  • Storing private keys or secrets in the browser. Never. dApps sign via wallets; backends hold keys.

© 2026 YourAI.tools. Every skill from an identity-verified publisher.

Independent catalog. Not affiliated with, endorsed by, or sponsored by Anthropic or any listed publisher. All trademarks belong to their respective owners.