Module 5: Building Your Mini Demo Product with Smart Contracts
Learning Objective
- Giới thiệu về dapp-kit
- Create a project/ explain the Project Structure
Giới thiệu về dApp kit
dApp Kit là một bộ React components, hooks, và utilities giúp bạn xây dựng decentralized applications (dApps) trên Sui blockchain nhanh chóng và dễ dàng hơn. Nó tối ưu các tác vụ thường gặp như kết nối ví (wallets), ký giao dịch (sign transactions), và lấy dữ liệu từ RPC nodes — giúp bạn tập trung phát triển logic ứng dụng thay vì xử lý boilerplate code.
Key Features
- Query Hooks – Lấy dữ liệu on-chain mà dApp của bạn cần một cách tiện lợi.
- Automatic Wallet State Management – Tự động quản lý trạng thái kết nối ví, không cần code thủ công.
- Multi-Wallet Support – Hỗ trợ hầu hết các Sui wallets phổ biến.
- Pre-built React Components – Có sẵn các UI components để bạn dev nhanh hơn.
- Customizable Hooks – Linh hoạt mở rộng, tạo ra components riêng từ low-level hooks.
How to install dApp kit
Để bắt đầu sử dụng dApp Kit, bạn cần cài đặt các packages cần thiết.
Có 2 cách phổ biến để cài đặt:
- cài bằng npm
npm i --save @mysten/dapp-kit @mysten/sui @tanstack/react-query
Cài bằng yarn
yarn add @mysten/dapp-kit @mysten/sui @tanstack/react-query
Ngoài ra, dApp Kit cũng hỗ trợ cài đặt bằng pnpm và bun. Tuỳ vào workflow/project của bạn, hãy chọn công cụ package manager phù hợp.
How to initialize a front-end template
Khi khởi tạo project, bạn có thể chọn giữa 2 template sau:
- react-client-dapp: Một template React đơn giản, có sẵn ví dụ hiển thị danh sách assets trong wallet.
- react-e2e-counter: Một template phức tạp hơn (end-to-end) kết hợp Move code với một UI đếm đơn giản (counter UI).
Network Configuration với @mysten/dapp-kit và @mysten/sui/client
Đầu tiên, cần config network connection và Provider components trong file entry của ứng dụng (ví dụ: App.js hoặc App.tsx).
import { createNetworkConfig, SuiClientProvider, WalletProvider } from '@mysten/dapp-kit';
import { getFullnodeUrl } from '@mysten/sui/client';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
const { networkConfig } = createNetworkConfig({
localnet: { url: getFullnodeUrl('localnet') },
mainnet: { url: getFullnodeUrl('mainnet') },
});
const queryClient = new QueryClient();
function App() {
return (
<QueryClientProvider client={queryClient}>
<SuiClientProvider networks={networkConfig} defaultNetwork="localnet">
<WalletProvider>
<YourApp />
</WalletProvider>
</SuiClientProvider>
</QueryClientProvider>
);
}
export default App;
Để config network connections trong app, bạn sẽ dùng hai package chính:
@mysten/sui/client
→ cung cấp functiongetFullnodeUrl
.@mysten/dapp-kit
→ cung cấp functioncreateNetworkConfig
và các hooks hỗ trợ.
Với cấu hình này, app của bạn có thể Dễ dàng chuyển đổi giữa nhiều Sui networks (localnet, devnet, testnet, mainnet). Kết hợp cùng Provider (SuiClientProvider) để tự động kết nối đúng network khi dApp khởi chạy.
import { getFullnodeUrl } from "@mysten/sui/client";
import { createNetworkConfig } from "@mysten/dapp-kit";
const { networkConfig, useNetworkVariable, useNetworkVariables } =
createNetworkConfig({
devnet: {
url: getFullnodeUrl("devnet"),
},
testnet: {
url: getFullnodeUrl("testnet"),
},
mainnet: {
url: getFullnodeUrl("mainnet"),
},
});
export { useNetworkVariable, useNetworkVariables, networkConfig };
Wallet Status Component
import { useCurrentAccount } from "@mysten/dapp-kit";
import { Container, Flex, Heading, Text } from "@radix-ui/themes";
import { OwnedObjects } from "./OwnedObjects";
export function WalletStatus() {
const account = useCurrentAccount();
return (
<Container my="2">
<Heading mb="2">Wallet Status</Heading>
{account ? (
<Flex direction="column">
<Text>Wallet connected</Text>
<Text>Address: {account.address}</Text>
</Flex>
) : (
<Text>Wallet not connected</Text>
)}
<OwnedObjects />
</Container>
);
}
WalletStatus
là một React component hiển thị trạng thái kết nối ví và danh sách objects thuộc ví đang kết nối.
-
useCurrentAccount
: Import từ@mysten/dapp-kit
. Là hook dùng để lấy thông tin wallet account hiện tại đang kết nối. -
Conditional rendering:
- Nếu account tồn tại → hiển thị "Wallet connected" và địa chỉ ví.
- Nếu account không tồn tại → hiển thị "Wallet not connected".
Address query component
import { useCurrentAccount, useSuiClientQuery } from "@mysten/dapp-kit";
import { Flex, Heading, Text } from "@radix-ui/themes";
export function OwnedObjects() {
const account = useCurrentAccount();
const { data, isPending, error } = useSuiClientQuery(
"getOwnedObjects",
{
owner: account?.address as string,
},
{
enabled: !!account,
},
);
if (!account) {
return;
}
if (error) {
return <Flex>Error: {error.message}</Flex>;
}
if (isPending || !data) {
return <Flex>Loading...</Flex>;
}
return (
<Flex direction="column" my="2">
{data.data.length === 0 ? (
<Text>No objects owned by the connected wallet</Text>
) : (
<Heading size="4">Objects owned by the connected wallet</Heading>
)}
{data.data.map((object) => (
<Flex key={object.data?.objectId}>
<Text>Object ID: {object.data?.objectId}</Text>
</Flex>
))}
</Flex>
);
}
Component này phù hợp để render danh sách tài sản hoặc objects on-chain mà ví user sở hữu ngay sau khi kết nối.
OwnedObjects
là một React component dùng để hiển thị các objects thuộc ví hiện tại đang kết nối.
- Conditional Rendering
- Nếu chưa có account kết nối → component return null.
- Nếu query bị lỗi → hiển thị error message.
- Nếu query đang loading hoặc chưa có dữ liệu → hiển thị "Loading...".
- Nếu query thành công → hiển thị danh sách objects.
- Nếu không có objects → hiển thị thông báo "No objects found".
Split Coin tách SUI từ gas/coin lớn
Khi bạn cần tách một lượng SUI nhỏ (ví dụ để thanh toán phí hay gửi tip), có thể dùng tx.splitCoins
để chia từ coin gas/coin lớn hơn.
// Số lượng cần tách theo đơn vị "một SUI" * precision
// Lưu ý: (ví dụ) nếu Sui.decimal = 9 thì 1 SUI = 10 ** 9
const suiAmount = 1 * 10 ** Sui.decimal;
// Tách từ gas hoặc từ coin nguồn
const [suiCoin] = tx.splitCoins(tx.gas, [suiAmount]);
// suiCoin lúc này là một coin object mới mang giá trị suiAmount
Bạn có thể tách nhiều “mệnh giá” cùng lúc bằng cách truyền nhiều phần tử trong mảng.
- Nếu không muốn dùng tx.gas, có thể truyền coin object nguồn khác:
tx.splitCoins(tx.object(sourceCoinId), [amount])
. - Bạn có thể tách nhiều “mệnh giá” cùng lúc bằng cách truyền nhiều phần tử trong mảng.
Merge Coin – gộp nhiều coin lẻ
Trong Sui, người dùng thường có nhiều coin lẻ; mergeCoins giúp gộp lại thành 1 coin để dễ quản lý.
// Gộp coinObjectId_2, coinObjectId_3 vào coinObjectId (đích)
tx.mergeCoins(
tx.object(coinObjectId),
[tx.object(coinObjectId_2), tx.object(coinObjectId_3)]
);
Hãy chọn một “coin đích” làm nơi merge; các coin còn lại sẽ bị “đổ” vào coin này. Merge trước khi thực hiện giao dịch khác có thể giúp giảm số object và đơn giản hóa logic.
Ký & Gửi Transaction qua wallet (plugin signature)
@mysten/dapp-kit
cung cấp hook useSignAndExecuteTransaction
giúp bạn ký và gửi transaction với ví người dùng.
// Khai báo hook
const { mutate: signAndExecuteTransaction } = useSignAndExecuteTransaction();
// Thực thi transaction (đã build sẵn "tx")
signAndExecuteTransaction(
{ transaction: tx },
{
onSuccess: (result) => {
console.log(result.digest); // digest của tx trên chain
},
onError: (error) => {
console.log(error);
},
}
);
Trước khi mutate, hãy build tx đầy đủ (split/merge/moveCall…).
Advanced
How to build a SUI Move dApp based on zkLogin? 👀