sui-frontend
Sui frontend dApp development with @mysten/dapp-kit-react (React) and @mysten/dapp-kit-core (Vue, vanilla JS, other frameworks). Use when building browser apps that connect to Sui wallets, query on-chain data, or execute transactions. Use alongside the sui-ts-sdk skill for PTB construction patterns.
Best use case
sui-frontend is best used when you need a repeatable AI agent workflow instead of a one-off prompt.
Sui frontend dApp development with @mysten/dapp-kit-react (React) and @mysten/dapp-kit-core (Vue, vanilla JS, other frameworks). Use when building browser apps that connect to Sui wallets, query on-chain data, or execute transactions. Use alongside the sui-ts-sdk skill for PTB construction patterns.
Teams using sui-frontend should expect a more consistent output, faster repeated execution, less prompt rewriting.
When to use this skill
- You want a reusable workflow that can be run more than once with consistent structure.
When not to use this skill
- You only need a quick one-off answer and do not need a reusable workflow.
- You cannot install or maintain the underlying files, dependencies, or repository context.
Installation
Claude Code / Cursor / Codex
Manual Installation
- Download SKILL.md from GitHub
- Place it in
.claude/skills/sui-frontend/SKILL.mdinside your project - Restart your AI agent — it will auto-discover the skill
How sui-frontend Compares
| Feature / Agent | sui-frontend | Standard Approach |
|---|---|---|
| Platform Support | Not specified | Limited / Varies |
| Context Awareness | High | Baseline |
| Installation Complexity | Unknown | N/A |
Frequently Asked Questions
What does this skill do?
Sui frontend dApp development with @mysten/dapp-kit-react (React) and @mysten/dapp-kit-core (Vue, vanilla JS, other frameworks). Use when building browser apps that connect to Sui wallets, query on-chain data, or execute transactions. Use alongside the sui-ts-sdk skill for PTB construction patterns.
Where can I find the source code?
You can find the source code on GitHub using the link provided at the top of the page.
SKILL.md Source
# Sui Frontend Skill
## SDK Versions
Targets: `@mysten/sui` 2.17.0 (^2.0), `@mysten/dapp-kit-react` 2.0.3 (^2.0), `@mysten/dapp-kit-core` 1.3.2 (^1.3). Tested: 2026-05-21.
**Compatibility notes:** Before installing, run `npm ls @mysten/sui` — if you already have `@mysten/sui@1.x` from `seal`, `walrus`, or legacy `@mysten/dapp-kit`, do not add a 2.x package on top. Remediation: either upgrade those peers to versions compatible with sui 2.x, or stay fully on the 1.x line — don't mix. UI components moved to `@mysten/dapp-kit-react/ui` in 2.x — importing `ConnectButton` from the package root will fail with "is not exported".
---
This skill covers building browser-based Sui dApps using the dApp Kit SDK. The SDK has two packages:
- **`@mysten/dapp-kit-react`** — React hooks, `DAppKitProvider`, and React component wrappers
- **`@mysten/dapp-kit-core`** — Framework-agnostic core: actions, nanostores state, and Web Components for Vue, vanilla JS, Svelte, or any other framework
Both packages expose the same `createDAppKit` factory and identical action APIs (`signAndExecuteTransaction`, `signTransaction`, `signPersonalMessage`, etc.). What differs is how you access reactive state and render UI: React uses hooks and provider components; other frameworks use nanostores stores and Web Components.
For PTB construction details (splitCoins, moveCall, coinWithBalance, etc.), apply the **sui-ts-sdk** skill alongside this one — the `Transaction` API is identical in browser and Node contexts.
> **Note**: The older `@mysten/dapp-kit` package is deprecated (JSON-RPC only, no gRPC/GraphQL support). New projects must use `@mysten/dapp-kit-react` or `@mysten/dapp-kit-core`.
---
## 1. Package Installation
**React:**
```bash
npm install @mysten/dapp-kit-react @mysten/sui
# Add React Query for declarative on-chain data fetching (recommended)
npm install @tanstack/react-query
```
**Vue / vanilla JS / other frameworks:**
```bash
npm install @mysten/dapp-kit-core @mysten/sui
# For Vue reactive bindings:
npm install @nanostores/vue
```
| Package | Purpose |
|---------|---------|
| `@mysten/dapp-kit-react` | React hooks, `DAppKitProvider`, React component wrappers |
| `@mysten/dapp-kit-core` | Framework-agnostic actions, stores, Web Components |
| `@mysten/sui` | Sui TypeScript SDK (Transaction class, gRPC client) |
| `@tanstack/react-query` | Declarative on-chain data fetching (React only) |
| `@nanostores/vue` | Reactive store bindings for Vue |
---
## 2. Instance & Provider Setup (React)
> **Not using React?** Skip to the [Non-React Integration](#non-react-integration-vue--vanilla-js--svelte) section below.
The new dApp Kit uses a single `createDAppKit` factory instead of three nested providers. Create the instance once in a dedicated file, then wrap your app with `DAppKitProvider`:
```tsx
// dapp-kit.ts
import { createDAppKit } from '@mysten/dapp-kit-react';
import { SuiGrpcClient } from '@mysten/sui/grpc';
const GRPC_URLS: Record<string, string> = {
testnet: 'https://fullnode.testnet.sui.io:443',
mainnet: 'https://fullnode.mainnet.sui.io:443',
};
export const dAppKit = createDAppKit({
networks: ['testnet', 'mainnet'],
defaultNetwork: 'testnet',
createClient: (network) =>
new SuiGrpcClient({ network, baseUrl: GRPC_URLS[network] }),
});
// Register the instance type for TypeScript inference in hooks
declare module '@mysten/dapp-kit-react' {
interface Register {
dAppKit: typeof dAppKit;
}
}
```
```tsx
// @check:skip
// App.tsx
import { DAppKitProvider } from '@mysten/dapp-kit-react';
import { ConnectButton } from '@mysten/dapp-kit-react/ui';
import { dAppKit } from './dapp-kit';
export default function App() {
return (
<DAppKitProvider dAppKit={dAppKit}>
<ConnectButton />
<YourApp />
</DAppKitProvider>
);
}
```
The `declare module` augmentation is what makes `useDAppKit()` and other hooks return properly typed values without passing the instance explicitly.
---
## 3. Network & Client Configuration
`createDAppKit` accepts these key options:
```tsx
// @check:skip
createDAppKit({
networks: ['testnet', 'mainnet'], // which networks your app supports
defaultNetwork: 'testnet', // starting network
createClient: (network) => // called once per network
new SuiGrpcClient({ network, baseUrl: GRPC_URLS[network] }),
autoConnect: true, // default: true — restores last wallet on reload
});
```
**Use `SuiGrpcClient` here** — unlike the deprecated `@mysten/dapp-kit`, the new package is built for gRPC. Do not pass `SuiJsonRpcClient` to `createClient`.
```tsx
// @check:skip — illustrative wrong/correct contrast, not a runnable snippet
// wrong client type — belongs to the deprecated @mysten/dapp-kit era
import { SuiJsonRpcClient } from '@mysten/sui/jsonRpc';
createClient: (network) => new SuiJsonRpcClient({ ... })
// correct
import { SuiGrpcClient } from '@mysten/sui/grpc';
createClient: (network) => new SuiGrpcClient({ network, baseUrl: GRPC_URLS[network] })
```
---
## Non-React Integration (Vue / Vanilla JS / Svelte)
Use `@mysten/dapp-kit-core` when not building with React. The `createDAppKit` call is identical — only the import path differs:
```ts
// @check:skip
// dapp-kit.ts
import { createDAppKit } from '@mysten/dapp-kit-core'; // core, not -react
import { SuiGrpcClient } from '@mysten/sui/grpc';
const GRPC_URLS: Record<string, string> = {
testnet: 'https://fullnode.testnet.sui.io:443',
mainnet: 'https://fullnode.mainnet.sui.io:443',
};
export const dAppKit = createDAppKit({
networks: ['testnet', 'mainnet'],
defaultNetwork: 'testnet',
createClient: (network) => new SuiGrpcClient({ network, baseUrl: GRPC_URLS[network] }),
});
```
No `declare module` augmentation needed — that's React-only.
All actions on the instance work identically to the React sections below: `signAndExecuteTransaction`, `signTransaction`, `signPersonalMessage`, `connectWallet`, `disconnectWallet`, `switchNetwork`, `switchAccount`.
### Web Components
Register the web components once at your app entry point, then use them in any HTML or template:
```ts
// main.ts (app entry point)
import '@mysten/dapp-kit-core/web';
```
**Connect Button** — set `instance` as a DOM property (not an HTML attribute):
```html
<mysten-dapp-kit-connect-button></mysten-dapp-kit-connect-button>
<script type="module">
import { dAppKit } from './dapp-kit.js';
document.querySelector('mysten-dapp-kit-connect-button').instance = dAppKit;
</script>
```
In Vue templates use property binding:
```vue
<mysten-dapp-kit-connect-button :instance="dAppKit" />
```
**Connect Modal** — for custom triggers (menu items, keyboard shortcuts, programmatic open):
```html
<mysten-dapp-kit-connect-modal></mysten-dapp-kit-connect-modal>
<script type="module">
const modal = document.querySelector('mysten-dapp-kit-connect-modal');
modal.instance = dAppKit;
document.getElementById('open-btn').addEventListener('click', () => modal.show());
</script>
```
Modal events: `open`, `opened`, `close`, `closed`, `cancel`.
### Reactive State (nanostores)
State is exposed as [nanostores](https://github.com/nanostores/nanostores) stores on `dAppKit.stores`:
| Store | Type | Description |
|-------|------|-------------|
| `$connection` | `{ wallet, account, status, isConnected, isConnecting, isReconnecting, isDisconnected }` | Full connection state |
| `$currentNetwork` | `string` | Active network name |
| `$currentClient` | `SuiClient` | Client for the active network |
| `$wallets` | `UiWallet[]` | Detected wallets |
**Vanilla JS** — subscribe for reactive updates:
```ts
// @check:skip
// Read current value synchronously
const connection = dAppKit.stores.$connection.get();
// Subscribe (returns an unsubscribe function — always clean up)
const unsubscribe = dAppKit.stores.$connection.subscribe((conn) => {
const el = document.getElementById('status');
if (!el) return;
if (conn.isConnected && conn.account) {
el.textContent = `${conn.wallet?.name}: ${conn.account.address}`;
} else {
el.textContent = 'Not connected';
}
});
// Unsubscribe when the view is destroyed
unsubscribe();
```
**Vue** — use `@nanostores/vue` for reactive template bindings:
```vue
<script setup lang="ts">
import { useStore } from '@nanostores/vue';
import { Transaction } from '@mysten/sui/transactions';
import { dAppKit } from './dapp-kit';
const connection = useStore(dAppKit.stores.$connection);
const network = useStore(dAppKit.stores.$currentNetwork);
async function handleTransfer() {
if (!connection.value.account) return;
const tx = new Transaction();
// ... build PTB ...
const result = await dAppKit.signAndExecuteTransaction({ transaction: tx });
if (result.FailedTransaction) {
throw new Error(result.FailedTransaction.status.error?.message ?? 'Transaction failed');
}
console.log('Digest:', result.Transaction.digest);
}
</script>
<template>
<mysten-dapp-kit-connect-button :instance="dAppKit" />
<div v-if="connection.account">
<p>Wallet: {{ connection.wallet?.name }}</p>
<p>Address: {{ connection.account.address }}</p>
<p>Network: {{ network }}</p>
<button @click="handleTransfer">Send Transaction</button>
</div>
<p v-else>Connect your wallet to get started</p>
</template>
```
### On-chain queries (non-React)
Outside React there's no `useCurrentClient` hook. Use the store or `getClient()` directly:
```ts
// @check:skip
const client = dAppKit.stores.$currentClient.get();
// or equivalently:
const client = dAppKit.getClient(); // current network's client
const mainnetClient = dAppKit.getClient('mainnet'); // specific network
const connection = dAppKit.stores.$connection.get();
if (!connection.account) throw new Error('Wallet not connected');
const balance = await client.getBalance({
owner: connection.account.address,
coinType: '0x2::sui::SUI',
});
```
---
## 4. Wallet Connection
### ConnectButton
The simplest approach — renders a "Connect Wallet" button that opens a wallet selection modal. **In dapp-kit-react 2.x, UI components live in the `/ui` subpath** — importing `ConnectButton` from the package root will fail:
```tsx
import { ConnectButton } from '@mysten/dapp-kit-react/ui';
function Header() {
return (
<header>
<ConnectButton />
</header>
);
}
```
`ConnectButton` auto-connects on page load by default (controlled by `autoConnect` in `createDAppKit`). Wallet detection happens in the browser — the component must be client-side rendered.
You can filter or sort the wallet list:
```tsx
// @check:skip
<ConnectButton
modalOptions={{
filterFn: (wallet) => wallet.name !== 'ExcludedWallet',
sortFn: (a, b) => a.name.localeCompare(b.name),
}}
/>
```
### Custom connection UI
Use `useWallets` to list wallets and `useDAppKit` for the connect/disconnect actions:
```tsx
import { useWallets, useDAppKit } from '@mysten/dapp-kit-react';
function WalletMenu() {
const wallets = useWallets();
const dAppKit = useDAppKit();
return (
<div>
{wallets.map((wallet) => (
<button
key={wallet.name}
onClick={() => dAppKit.connectWallet({ wallet })}
>
{wallet.name}
</button>
))}
<button onClick={() => dAppKit.disconnectWallet()}>Disconnect</button>
</div>
);
}
```
### Connection status
`useWalletConnection` provides the full connection state:
```tsx
import { useWalletConnection } from '@mysten/dapp-kit-react';
function ConnectionStatus() {
const { status, wallet, account } = useWalletConnection();
// status: 'disconnected' | 'connecting' | 'reconnecting' | 'connected'
if (status === 'connecting' || status === 'reconnecting') return <p>Connecting...</p>;
if (status === 'connected') return <p>Connected: {wallet?.name}</p>;
return <p>Disconnected</p>;
}
```
---
## 5. Current Account & Wallet
`useCurrentAccount` gives you the connected address; `useCurrentWallet` gives you the wallet object (name, icon, accounts list):
```tsx
import { useCurrentAccount, useCurrentWallet } from '@mysten/dapp-kit-react';
function Profile() {
const account = useCurrentAccount();
const wallet = useCurrentWallet();
if (!account) {
return <p>No wallet connected</p>;
}
return (
<div>
<p>Wallet: {wallet?.name}</p>
<p>Address: {account.address}</p>
<p>Label: {account.label}</p>
</div>
);
}
```
Both return `null` when no wallet is connected. Always null-check before accessing their properties.
- `useCurrentAccount()` -> `UiWalletAccount | null` — provides `address`, `label`
- `useCurrentWallet()` -> `UiWallet | null` — provides `name`, `icon`, `accounts`
---
## 6. Accessing the Raw Client
`useCurrentClient` returns the `SuiClient` for the active network:
```tsx
import { useCurrentClient } from '@mysten/dapp-kit-react';
function SomeComponent() {
const client = useCurrentClient();
const handleSuccess = async (digest: string) => {
// Wait for indexing before follow-up reads (see sui-ts-sdk section 11)
await client.waitForTransaction({ digest });
// All SuiGrpcClient methods are available
};
}
```
Do not instantiate `new SuiGrpcClient(...)` inside components — use `useCurrentClient` so it stays in sync with the active network.
> **Parser-breaking (v1.70+):** `0x1::type_name::TypeName` values in structured outputs (JSON-RPC, gRPC, GraphQL) are now serialized as a plain string (e.g. `"0x2::sui::SUI"`) instead of `{ "name": "0x2::sui::SUI" }`. If your frontend parses these fields, expect a string at that position.
---
## 7. Signing and Executing Transactions
Use `useDAppKit` and call `signAndExecuteTransaction`. Build the `Transaction` using **sui-ts-sdk** PTB patterns:
```tsx
import { useDAppKit, useCurrentClient, useCurrentAccount } from '@mysten/dapp-kit-react';
import { Transaction } from '@mysten/sui/transactions';
import { useState } from 'react';
function ActionButton() {
const dAppKit = useDAppKit();
const client = useCurrentClient();
const account = useCurrentAccount();
const [isPending, setIsPending] = useState(false);
const handleAction = async () => {
if (!account) return;
setIsPending(true);
try {
const tx = new Transaction();
// ... build PTB ...
const result = await dAppKit.signAndExecuteTransaction({ transaction: tx });
if (result.FailedTransaction) {
throw new Error(result.FailedTransaction.status.error?.message ?? 'Transaction failed');
}
await client.waitForTransaction({ digest: result.Transaction.digest });
} finally {
setIsPending(false);
}
};
return (
<button onClick={handleAction} disabled={!account || isPending}>
{isPending ? 'Waiting for wallet...' : 'Submit'}
</button>
);
}
```
**Key points:**
- No mutation hook — `signAndExecuteTransaction` is a plain async function on `useDAppKit()`
- Result is a discriminated union: `result.FailedTransaction` (failure) or `result.Transaction.digest` (success)
- Always `waitForTransaction` before re-querying state
For querying on-chain data (React Query patterns), paginated queries, sign-without-execute, personal message signing, network switching, cache invalidation, wallet-gated UI, and the full "What dApp Kit is NOT" migration table, see:
- [references/react-patterns.md](references/react-patterns.md)
---
## 8. What dApp Kit is NOT (key items)
| Mistake | Correct approach |
|---------|-----------------|
| Using `@mysten/dapp-kit` | Deprecated — use `@mysten/dapp-kit-react` or `@mysten/dapp-kit-core` |
| Three-provider setup | Use `createDAppKit` + `DAppKitProvider` |
| `useSignAndExecuteTransaction` hook | Use `useDAppKit().signAndExecuteTransaction()` |
| `useSuiClient` | Renamed to `useCurrentClient` |
| `useSuiClientQuery` | Removed — use `useCurrentClient` + `@tanstack/react-query` |
Full migration table in [references/react-patterns.md](references/react-patterns.md).
---
## Integration
### Called By
- `sui-full-stack` (Phase 3: Frontend development)
- `sui-fullstack-integration` (contract-frontend integration)
### Calls
- `sui-ts-sdk` - PTB construction patterns
- `sui-docs-query` - Query latest SDK documentation
## References
- [references/react-patterns.md](references/react-patterns.md) - Queries, pagination, signing, cache invalidation, wallet-gated UI, full migration table
- [references/reference.md](references/reference.md) - Complete SDK API reference, hooks documentation
- [references/grpc-reference.md](references/grpc-reference.md) - gRPC API reference, migration guide
- [references/examples.md](references/examples.md) - Complete component examples, integration patternsRelated Skills
sui-zklogin
Use when implementing zkLogin on SUI — OAuth login (Google, Facebook, Apple, Twitch) with zero-knowledge proofs for privacy-preserving authentication. Triggers on "zkLogin", "social login on SUI", "Google login", "OAuth", "ephemeral keypair", "JWT proof", or any authentication flow that derives a SUI address from an OAuth provider. Also use when the user mentions "login without wallet extension".
sui-walrus
Use when storing or retrieving files using Walrus — SUI's decentralized blob storage. Triggers on "Walrus", "blob storage", "upload file to chain", "decentralized storage", "store NFT image", "IPFS alternative on SUI", "where to store NFT metadata", "host a site on-chain", or any off-chain data storage needs on SUI. Also use for Walrus Sites (decentralized web hosting), storing game assets, media files, or when the user asks "where do I put large files on SUI".
sui-wallet
Use when performing on-chain transactions (transfer, Move call, publish) through the agent's CLI wallet via MCP tools. Triggers on "transfer SUI", "call Move function", "publish package", "wallet status", "sign transaction", or any agent-driven on-chain operation. This is for headless/backend wallet operations — for browser wallet UI (React/Vue), use sui-frontend instead.
sui-tester
Use when writing Move tests, setting up test suites, running gas benchmarks, or planning test strategy for SUI contracts. Triggers on "write tests", "test this module", "#[test]", "test coverage", "gas benchmark", "property-based test", or any Move testing task. Use even for simple "how do I test this function" questions.
sui-suins
Use when integrating SuiNS (SUI Name Service) — resolving .sui names to addresses, reverse lookups, or registering names. Triggers on "SuiNS", ".sui name", "name resolution", "reverse lookup", "human-readable address", or any name service integration. Also use when the user wants to display user-friendly names instead of hex addresses.
sui-security-guard
Use when setting up security scanning, detecting leaked secrets/API keys, implementing pre-commit hooks, or auditing a Sui Move contract for security/architecture/quality issues. Triggers on "security scan", "detect secrets", "pre-commit hook", "security audit setup", "API key leaked", and on contract-level review requests like "audit this contract", "review access control", "is this Move safe", "check for vulnerabilities", "Move security review" — these load the SEC/DES/PAT/TST/QA/CFG finding registry in references/move-security-findings.md. For offensive/adversarial testing (attack vector discovery, writing exploits/PoCs), use sui-red-team instead. For Move style/idiom quality (non-security), use move-code-quality.
sui-seal
Use when implementing data encryption, access control, or secrets management on SUI using the Seal protocol. Triggers on threshold encryption, data privacy, token-gated content, encrypted storage, decryption policies, paywall, gated access, encrypted NFT metadata, private data sharing, or any scenario requiring on-chain access control for off-chain data. Also use when the user mentions Seal, pay-to-decrypt, "only NFT holders can see", or subscriber-only content on SUI.
sui-red-team
Use when performing adversarial security testing on SUI Move contracts — generating attack tests for access control bypass, integer overflow, object manipulation, economic exploits, reentrancy, and DoS vectors. Triggers on "red team", "attack test", "find vulnerabilities", "exploit", "pentest", "security test", or when the user wants to stress-test their contract's security. For defensive security setup (scanning, hooks, checklists), use sui-security-guard instead.
sui-passkey
Use when implementing WebAuthn passkeys or biometric authentication (Face ID, fingerprint, hardware keys) on SUI. Triggers on "passkey", "WebAuthn", "biometric login", "Face ID", "fingerprint auth", "FIDO2", or passwordless auth that uses device authenticators instead of seed phrases. Different from zkLogin (which uses OAuth providers).
sui-nautilus
Use when building verifiable off-chain computation, integrating external APIs with on-chain proof, or running trusted execution environments on SUI. Triggers on Nautilus, off-chain oracle, "verify API data on-chain", "connect external API to Move", "prove off-chain result", trusted compute, AWS Nitro Enclave, attestation, price feed, weather data on-chain, or any scenario requiring cryptographically verified external data. Also use when the user asks "how do I get real-world data into my SUI contract" or needs an oracle-like pattern.
sui-kiosk
Use when building NFT marketplaces, enforcing royalties, or managing transfer policies using SUI's Kiosk standard. Triggers on "Kiosk", "NFT marketplace", "transfer policy", "royalty enforcement", "list NFT for sale", "purchase rules", or any NFT commerce on SUI. Also use when the user asks about listing, delisting, or trading NFTs with enforced rules.
sui-install
Use when installing or updating the Sui CLI, managing CLI versions with suiup, or resolving environment/setup problems — "install sui", "update sui", "command not found", "sui not found", "client/server api version mismatch", build errors about "old dependencies", switching CLI versions per network, or installing toolchain components (Walrus, MVR, Move Analyzer, site-builder). Also use for first-time client setup, getting faucet tokens, recovering keys from a phrase, or "Cannot find gas coin for signer address". For deploying/upgrading packages use sui-deployer; for on-chain data queries use sui-ts-sdk.