Web3 开发实战:Sui 区块链从零到上手

· 5min · Paxon Qiao

Web3 开发实战:Sui 区块链从零到上手

Web3 浪潮席卷而来,Sui 作为新一代高性能区块链,以其独特的 Move 语言和创新设计脱颖而出。你是否想快速入门 Web3 开发,却不知从何下手?本文将带你从零开始,通过实战操作掌握 Sui 区块链的核心技能。从安装 Sui CLI 到编写智能合约,再到打造一个前端 DApp,全程手把手教学。不管你是区块链小白还是想探索新平台的开发者,这篇指南都将是你开启 Sui Web3 之旅的完美起点!

本文通过详细的实战步骤,展示了如何在 Sui 区块链上从零开始完成开发全流程。内容涵盖 Sui CLI 的安装与配置、钱包地址和代币余额管理、Move 语言编写的计数器智能合约开发与部署,以及基于 React 和 @mysten /dapp-kit 的前端 DApp 搭建。每个环节配有代码示例、运行结果和浏览器交互演示,让你轻松理解 Sui 的核心功能和操作逻辑。无论你是想快速上手 Web3 开发,还是探索 Sui 生态的潜力,这篇教程都能帮你打下坚实基础。

实操

安装

brew install sui

更新

brew upgrade sui
==> Auto-updating Homebrew...
Adjust how often this is run with HOMEBREW_AUTO_UPDATE_SECS or disable with
HOMEBREW_NO_AUTO_UPDATE. Hide these hints with HOMEBREW_NO_ENV_HINTS (see `man brew`).
==> Auto-updated Homebrew!
Updated 3 taps (pulumi/tap, homebrew/core and homebrew/cask).
==> New Formulae
geesefs                            harsh                              lld@19                             llvm@19
==> New Casks
bambu-connect               earnapp                     font-playpen-sans-deva      inmusic-software-center     restapia
captainplugins              font-lxgw-wenkai-gb-lite    hamrs-pro                   moment                      slidepad

You have 60 outdated formulae installed.

Warning: sui 1.45.2 already installed

查看 Sui 版本号

sui --version
sui 1.45.2-homebrew

查看当前活动的默认钱包地址

sui client active-address
[warning] Client/Server api version mismatch, client api version : 1.45.2, server api version : 1.45.1
0x35370841d2e69b495b1e2f944a3087e4242f314e503691a00b054e0ee2a45a73

列出当前 Sui 客户端配置中所有可用的钱包地址

sui client addresses
[warning] Client/Server api version mismatch, client api version : 1.45.2, server api version : 1.45.1
╭──────────────────────┬────────────────────────────────────────────────────────────────────┬────────────────╮
│ alias                │ address                                                            │ active address │
├──────────────────────┼────────────────────────────────────────────────────────────────────┼────────────────┤
│ dazzling-chrysoprase │ 0x35370841d2e69b495b1e2f944a3087e4242f314e503691a00b054e0ee2a45a73 │ *╰──────────────────────┴────────────────────────────────────────────────────────────────────┴────────────────╯

查询指定钱包地址的 SUI 代币余额

sui client balance
[warning] Client/Server api version mismatch, client api version : 1.45.2, server api version : 1.45.1
╭────────────────────────────────────────╮
│ Balance of coins owned by this address │
├────────────────────────────────────────┤
│ ╭──────────────────────────────────╮   │
│ │ coin  balance (raw)  balance     │   │
│ ├──────────────────────────────────┤   │
│ │ Sui   14829596064    14.82 SUI   │   │
│ │ WAL   0              0.00 WAL    │   │
│ ╰──────────────────────────────────╯   │
╰────────────────────────────────────────╯


sui client balance 0x35370841d2e69b495b1e2f944a3087e4242f314e503691a00b054e0ee2a45a73
[warning] Client/Server api version mismatch, client api version : 1.45.2, server api version : 1.45.1
╭────────────────────────────────────────╮
│ Balance of coins owned by this address │
├────────────────────────────────────────┤
│ ╭──────────────────────────────────╮   │
│ │ coin  balance (raw)  balance     │   │
│ ├──────────────────────────────────┤   │
│ │ Sui   14829596064    14.82 SUI   │   │
│ │ WAL   0              0.00 WAL    │   │
│ ╰──────────────────────────────────╯   │
╰────────────────────────────────────────╯


sui client balance 0x6518cfc4854eb8b175c406e25e16e5042cf84a6c91c6eea9485eebeb18df4df0
[warning] Client/Server api version mismatch, client api version : 1.45.2, server api version : 1.45.1
╭────────────────────────────────────────╮
│ Balance of coins owned by this address │
├────────────────────────────────────────┤
│ ╭─────────────────────────────────╮    │
│ │ coin  balance (raw)  balance    │    │
│ ├─────────────────────────────────┤    │
│ │ Sui   6987944472     6.98 SUI   │    │
│ │ WAL   0              0.00 WAL   │    │
│ ╰─────────────────────────────────╯    │
╰────────────────────────────────────────╯

列出当前活跃地址拥有的所有 Gas 对象(Gas Objects)

sui client gas
[warning] Client/Server api version mismatch, client api version : 1.45.2, server api version : 1.45.1
╭────────────────────────────────────────────────────────────────────┬────────────────────┬──────────────────╮
│ gasCoinId                                                          │ mistBalance (MIST) │ suiBalance (SUI) │
├────────────────────────────────────────────────────────────────────┼────────────────────┼──────────────────┤
│ 0x09ae107f8b03e0297bd8419d9aba9cc3358dd638ca3a8d7cf7c60de3c0eb51ed │ 14829596064        │ 14.82            │
╰────────────────────────────────────────────────────────────────────┴────────────────────┴──────────────────╯

列出所有已配置的 Sui 网络环境(Environments)

sui client envs
[warning] Client/Server api version mismatch, client api version : 1.45.2, server api version : 1.45.1
╭─────────┬─────────────────────────────────────┬────────╮
│ alias   │ url                                 │ active │
├─────────┼─────────────────────────────────────┼────────┤
│ devnet  │ https://fullnode.devnet.sui.io:443  │        │
│ mainnet │ https://fullnode.mainnet.sui.io:443 │        │
│ testnet │ https://fullnode.testnet.sui.io:443 │ *╰─────────┴─────────────────────────────────────┴────────╯

显示当前 Sui CLI 正在使用的网络环境(environment)

sui client active-env
[warning] Client/Server api version mismatch, client api version : 1.45.2, server api version : 1.45.1
testnet

将当前 CLI 会话切换到 Sui 主网环境

sui client switch --env mainnet
[warning] Client/Server api version mismatch, client api version : 1.45.2, server api version : 1.45.1
Active environment switched to [mainnet]


sui client active-env
[warning] Client/Server api version mismatch, client api version : 1.45.2, server api version : 1.45.3
mainnet

sui client envs
[warning] Client/Server api version mismatch, client api version : 1.45.2, server api version : 1.45.3
╭─────────┬─────────────────────────────────────┬────────╮
│ alias   │ url                                 │ active │
├─────────┼─────────────────────────────────────┼────────┤
│ devnet  │ https://fullnode.devnet.sui.io:443  │        │
│ mainnet │ https://fullnode.mainnet.sui.io:443 │ *│ testnet │ https://fullnode.testnet.sui.io:443 │        │
╰─────────┴─────────────────────────────────────┴────────╯

sui client switch --env testnet
[warning] Client/Server api version mismatch, client api version : 1.45.2, server api version : 1.45.3
Active environment switched to [testnet]

sui client active-env
[warning] Client/Server api version mismatch, client api version : 1.45.2, server api version : 1.45.1
testnet

创建项目

创建一个新的 Sui Move Dapp项目

https://sdk.mystenlabs.com/dapp-kit/create-dapp

pnpm create @mysten/dapp
✔ Which starter template would you like to use? · react-e2e-counter
✔ What is the name of your dApp? (this will be used as the directory name) · sui-dapp      

切换到项目目录并用RustRover打开项目

cd sui-dapp/
open -a RustRover .

查看项目目录结构

hello_sui_move/sui-dapp on  main [?] via ⬢ v22.1.0 via 🅒 base 
➜ tree . -L 6 -I 'target|cache|lib|build|node_modules'


.
├── README.md
├── index.html
├── move
│   └── counter
│       ├── Move.lock
│       ├── Move.toml
│       └── sources
│           └── counter.move
├── package.json
├── pnpm-lock.yaml
├── prettier.config.cjs
├── src
│   ├── App.tsx
│   ├── Counter.tsx
│   ├── CreateCounter.tsx
│   ├── constants.ts
│   ├── main.tsx
│   ├── networkConfig.ts
│   └── vite-env.d.ts
├── tsconfig.json
├── tsconfig.node.json
└── vite.config.mts

5 directories, 18 files


合约代码counter.move

module counter::counter {

  public struct Counter has key {
    id: UID,
    owner: address,
    value: u64
  }
 
  public fun create(ctx: &mut TxContext) {
    transfer::share_object(Counter {
      id: object::new(ctx),
      owner: ctx.sender(),
      value: 0
    })
  }

  public fun increment(counter: &mut Counter) {
    counter.value = counter.value + 1;
  }
  
  public fun set_value(counter: &mut Counter, value: u64, ctx: &TxContext) {
    assert!(counter.owner == ctx.sender(), 0);
    counter.value = value;
  }
}

部署合约

hello_sui_move/sui-dapp on  main [?] via ⬢ v22.1.0 via 🅒 base 
➜ cd move/counter 

hello_sui_move/sui-dapp/move/counter on  main [?] via ⬢ v22.1.0 via 🅒 base 
➜ ls
Move.toml sources

hello_sui_move/sui-dapp/move/counter on  main [?] via ⬢ v22.1.0 via 🅒 base 
➜ sui client publish --skip-dependency-verification                                                          
[warn] Client/Server api version mismatch, client api version : 1.32.0, server api version : 1.46.1
UPDATING GIT DEPENDENCY https://github.com/MystenLabs/sui.git
INCLUDING DEPENDENCY Sui
INCLUDING DEPENDENCY MoveStdlib
BUILDING counter
Skipping dependency verification
Transaction Digest: GjioTVaWBDXem8fXVL2PmYs3wjVNiyXBKBFUV4MRs4sc
╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ Transaction Data                                                                                             │
├──────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ Sender: 0x35370841d2e69b495b1e2f944a3087e4242f314e503691a00b054e0ee2a45a73                                   │
│ Gas Owner: 0x35370841d2e69b495b1e2f944a3087e4242f314e503691a00b054e0ee2a45a73                                │
│ Gas Budget: 10033200 MIST                                                                                    │
│ Gas Price: 1000 MIST                                                                                         │
│ Gas Payment:                                                                                                 │
│  ┌──                                                                                                         │
│  │ ID: 0x09ae107f8b03e0297bd8419d9aba9cc3358dd638ca3a8d7cf7c60de3c0eb51ed                                    │
│  │ Version: 370791513                                                                                        │
│  │ Digest: 7aSdnD3XRN49WWApxK3LPhiFLzQrQ9gPdmhf2TfB7eYo                                                      │
│  └──                                                                                                         │
│                                                                                                              │
│ Transaction Kind: Programmable                                                                               │
│ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │
│ │ Input Objects                                                                                            │ │
│ ├──────────────────────────────────────────────────────────────────────────────────────────────────────────┤ │
│ │ 0   Pure Arg: Type: address, Value: "0x35370841d2e69b495b1e2f944a3087e4242f314e503691a00b054e0ee2a45a73" │ │
│ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────╯ │
│ ╭─────────────────────────────────────────────────────────────────────────╮                                  │
│ │ Commands                                                                │                                  │
│ ├─────────────────────────────────────────────────────────────────────────┤                                  │
│ │ 0  Publish:                                                             │                                  │
│ │  ┌                                                                      │                                  │
│ │  │ Dependencies:                                                        │                                  │
│ │  │   0x0000000000000000000000000000000000000000000000000000000000000001 │                                  │
│ │  │   0x0000000000000000000000000000000000000000000000000000000000000002 │                                  │
│ │  └                                                                      │                                  │
│ │                                                                         │                                  │
│ │ 1  TransferObjects:                                                     │                                  │
│ │  ┌                                                                      │                                  │
│ │  │ Arguments:                                                           │                                  │
│ │  │   Result 0                                                           │                                  │
│ │  │ Address: Input  0                                                    │                                  │
│ │  └                                                                      │                                  │
│ ╰─────────────────────────────────────────────────────────────────────────╯                                  │
│                                                                                                              │
│ Signatures:                                                                                                  │
│    bM5PpjP6DBvq3YtuRb+W9vI5QnjWHPCWMU7N5lKQtdYkKPd7KRopfy+FnLg8fBT62OGDqRTVZTuz048l70qbDA==                  │
│                                                                                                              │
╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
╭───────────────────────────────────────────────────────────────────────────────────────────────────╮
│ Transaction Effects                                                                               │
├───────────────────────────────────────────────────────────────────────────────────────────────────┤
│ Digest: GjioTVaWBDXem8fXVL2PmYs3wjVNiyXBKBFUV4MRs4sc                                              │
│ Status: Success                                                                                   │
│ Executed Epoch: 695                                                                               │
│                                                                                                   │
│ Created Objects:                                                                                  │
│  ┌──                                                                                              │
│  │ ID: 0x00012e566ad8b36293836f4901f72d32f181782299bf54724f7dbb6b718c9683                         │
│  │ Owner: Immutable                                                                               │
│  │ Version: 1                                                                                     │
│  │ Digest: BwqnKimSHf4iD4Erf3Jwj4JntQRxMxYN5YZ4dGEB5Dx4                                           │
│  └──                                                                                              │
│  ┌──                                                                                              │
│  │ ID: 0x9d049515152a144171b4037eb8b5b24c2f811474321c121872e56c6273c99a20                         │
│  │ Owner: Account Address ( 0x35370841d2e69b495b1e2f944a3087e4242f314e503691a00b054e0ee2a45a73 )  │
│  │ Version: 370791514                                                                             │
│  │ Digest: HZL8SN7iwmzy4Q2KjTLUxeWEv6Gb4oFXU2WwuiGvRpuQ                                           │
│  └──                                                                                              │
│ Mutated Objects:                                                                                  │
│  ┌──                                                                                              │
│  │ ID: 0x09ae107f8b03e0297bd8419d9aba9cc3358dd638ca3a8d7cf7c60de3c0eb51ed                         │
│  │ Owner: Account Address ( 0x35370841d2e69b495b1e2f944a3087e4242f314e503691a00b054e0ee2a45a73 )  │
│  │ Version: 370791514                                                                             │
│  │ Digest: 4FrFJ4bYDx8yBAvnQJeBjJ768TcQ5EHLje33kJmReQRb                                           │
│  └──                                                                                              │
│ Gas Object:                                                                                       │
│  ┌──                                                                                              │
│  │ ID: 0x09ae107f8b03e0297bd8419d9aba9cc3358dd638ca3a8d7cf7c60de3c0eb51ed                         │
│  │ Owner: Account Address ( 0x35370841d2e69b495b1e2f944a3087e4242f314e503691a00b054e0ee2a45a73 )  │
│  │ Version: 370791514                                                                             │
│  │ Digest: 4FrFJ4bYDx8yBAvnQJeBjJ768TcQ5EHLje33kJmReQRb                                           │
│  └──                                                                                              │
│ Gas Cost Summary:                                                                                 │
│    Storage Cost: 8033200 MIST                                                                     │
│    Computation Cost: 1000000 MIST                                                                 │
│    Storage Rebate: 978120 MIST                                                                    │
│    Non-refundable Storage Fee: 9880 MIST                                                          │
│                                                                                                   │
│ Transaction Dependencies:                                                                         │
│    VRqNMSsv1sWT8v2yzYkUXozp3aupQVKM4HBM1T547Lf                                                    │
│    2KKFDYfXCwBWaS1e3i4gLnjW1DsQoWqYQMb4SVBZFQR2                                                   │
╰───────────────────────────────────────────────────────────────────────────────────────────────────╯
╭─────────────────────────────╮
│ No transaction block events │
╰─────────────────────────────╯

╭──────────────────────────────────────────────────────────────────────────────────────────────────╮
│ Object Changes                                                                                   │
├──────────────────────────────────────────────────────────────────────────────────────────────────┤
│ Created Objects:                                                                                 │
│  ┌──                                                                                             │
│  │ ObjectID: 0x9d049515152a144171b4037eb8b5b24c2f811474321c121872e56c6273c99a20                  │
│  │ Sender: 0x35370841d2e69b495b1e2f944a3087e4242f314e503691a00b054e0ee2a45a73                    │
│  │ Owner: Account Address ( 0x35370841d2e69b495b1e2f944a3087e4242f314e503691a00b054e0ee2a45a73 ) │
│  │ ObjectType: 0x2::package::UpgradeCap                                                          │
│  │ Version: 370791514                                                                            │
│  │ Digest: HZL8SN7iwmzy4Q2KjTLUxeWEv6Gb4oFXU2WwuiGvRpuQ                                          │
│  └──                                                                                             │
│ Mutated Objects:                                                                                 │
│  ┌──                                                                                             │
│  │ ObjectID: 0x09ae107f8b03e0297bd8419d9aba9cc3358dd638ca3a8d7cf7c60de3c0eb51ed                  │
│  │ Sender: 0x35370841d2e69b495b1e2f944a3087e4242f314e503691a00b054e0ee2a45a73                    │
│  │ Owner: Account Address ( 0x35370841d2e69b495b1e2f944a3087e4242f314e503691a00b054e0ee2a45a73 ) │
│  │ ObjectType: 0x2::coin::Coin<0x2::sui::SUI>│  │ Version: 370791514                                                                            │
│  │ Digest: 4FrFJ4bYDx8yBAvnQJeBjJ768TcQ5EHLje33kJmReQRb                                          │
│  └──                                                                                             │
│ Published Objects:                                                                               │
│  ┌──                                                                                             │
│  │ PackageID: 0x00012e566ad8b36293836f4901f72d32f181782299bf54724f7dbb6b718c9683                 │
│  │ Version: 1                                                                                    │
│  │ Digest: BwqnKimSHf4iD4Erf3Jwj4JntQRxMxYN5YZ4dGEB5Dx4                                          │
│  │ Modules: counter                                                                              │
│  └──                                                                                             │
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
╭───────────────────────────────────────────────────────────────────────────────────────────────────╮
│ Balance Changes                                                                                   │
├───────────────────────────────────────────────────────────────────────────────────────────────────┤
│  ┌──                                                                                              │
│  │ Owner: Account Address ( 0x35370841d2e69b495b1e2f944a3087e4242f314e503691a00b054e0ee2a45a73 )  │
│  │ CoinType: 0x2::sui::SUI                                                                        │
│  │ Amount: -8055080│  └──                                                                                              │
╰───────────────────────────────────────────────────────────────────────────────────────────────────╯

查看交易详情

https://testnet.suivision.xyz/txblock/GjioTVaWBDXem8fXVL2PmYs3wjVNiyXBKBFUV4MRs4sc

image-20250405165710255

https://testnet.suivision.xyz/txblock/GjioTVaWBDXem8fXVL2PmYs3wjVNiyXBKBFUV4MRs4sc?tab=Changes

image-20250405165845751

在浏览器查看合约

https://testnet.suivision.xyz/package/0x00012e566ad8b36293836f4901f72d32f181782299bf54724f7dbb6b718c9683?tab=Code

image-20250405170359559

main.tsx 文件

import React from "react";
import ReactDOM from "react-dom/client";
import "@mysten/dapp-kit/dist/index.css";
import "@radix-ui/themes/styles.css";

import { SuiClientProvider, WalletProvider } from "@mysten/dapp-kit";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { Theme } from "@radix-ui/themes";
import App from "./App.tsx";
import { networkConfig } from "./networkConfig.ts";

const queryClient = new QueryClient();

ReactDOM.createRoot(document.getElementById("root")!).render(
  <React.StrictMode>
    <Theme appearance="dark">
      <QueryClientProvider client={queryClient}>
        <SuiClientProvider networks={networkConfig} defaultNetwork="testnet">
          <WalletProvider autoConnect>
            <App />
          </WalletProvider>
        </SuiClientProvider>
      </QueryClientProvider>
    </Theme>
  </React.StrictMode>,
);

App.tsx 文件

import { ConnectButton, useCurrentAccount } from "@mysten/dapp-kit";
import { isValidSuiObjectId } from "@mysten/sui/utils";
import { Box, Container, Flex, Heading } from "@radix-ui/themes";
import { useState } from "react";
import { Counter } from "./Counter";
import { CreateCounter } from "./CreateCounter";

function App() {
  const currentAccount = useCurrentAccount();
  const [counterId, setCounter] = useState(() => {
    const hash = window.location.hash.slice(1);
    return isValidSuiObjectId(hash) ? hash : null;
  });

  return (
    <>
      <Flex
        position="sticky"
        px="4"
        py="2"
        justify="between"
        style={{
          borderBottom: "1px solid var(--gray-a2)",
        }}
      >
        <Box>
          <Heading>dApp Starter Template</Heading>
        </Box>

        <Box>
          <ConnectButton />
        </Box>
      </Flex>
      <Container>
        <Container
          mt="5"
          pt="2"
          px="4"
          style={{ background: "var(--gray-a2)", minHeight: 500 }}
        >
          {currentAccount ? (
            counterId ? (
              <Counter id={counterId} />
            ) : (
              <CreateCounter
                onCreated={(id) => {
                  window.location.hash = id;
                  setCounter(id);
                }}
              />
            )
          ) : (
            <Heading>Please connect your wallet</Heading>
          )}
        </Container>
      </Container>
    </>
  );
}

export default App;

Counter.tsx 文件

import {
  useCurrentAccount,
  useSignAndExecuteTransaction,
  useSuiClient,
  useSuiClientQuery,
} from "@mysten/dapp-kit";
import type { SuiObjectData } from "@mysten/sui/client";
import { Transaction } from "@mysten/sui/transactions";
import { Button, Flex, Heading, Text } from "@radix-ui/themes";
import { useNetworkVariable } from "./networkConfig";
import { useState } from "react";
import ClipLoader from "react-spinners/ClipLoader";

export function Counter({ id }: { id: string }) {
  const counterPackageId = useNetworkVariable("counterPackageId");
  const suiClient = useSuiClient();
  const currentAccount = useCurrentAccount();
  const { mutate: signAndExecute } = useSignAndExecuteTransaction();
  const { data, isPending, error, refetch } = useSuiClientQuery("getObject", {
    id,
    options: {
      showContent: true,
      showOwner: true,
    },
  });

  const [waitingForTxn, setWaitingForTxn] = useState("");

  const executeMoveCall = (method: "increment" | "reset") => {
    setWaitingForTxn(method);

    const tx = new Transaction();

    if (method === "reset") {
      tx.moveCall({
        arguments: [tx.object(id), tx.pure.u64(0)],
        target: `${counterPackageId}::counter::set_value`,
      });
    } else {
      tx.moveCall({
        arguments: [tx.object(id)],
        target: `${counterPackageId}::counter::increment`,
      });
    }

    signAndExecute(
      {
        transaction: tx,
      },
      {
        onSuccess: (tx) => {
          suiClient.waitForTransaction({ digest: tx.digest }).then(async () => {
            await refetch();
            setWaitingForTxn("");
          });
        },
      },
    );
  };

  if (isPending) return <Text>Loading...</Text>;

  if (error) return <Text>Error: {error.message}</Text>;

  if (!data.data) return <Text>Not found</Text>;

  const ownedByCurrentAccount =
    getCounterFields(data.data)?.owner === currentAccount?.address;

  return (
    <>
      <Heading size="3">Counter {id}</Heading>

      <Flex direction="column" gap="2">
        <Text>Count: {getCounterFields(data.data)?.value}</Text>
        <Flex direction="row" gap="2">
          <Button
            onClick={() => executeMoveCall("increment")}
            disabled={waitingForTxn !== ""}
          >
            {waitingForTxn === "increment" ? (
              <ClipLoader size={20} />
            ) : (
              "Increment"
            )}
          </Button>
          {ownedByCurrentAccount ? (
            <Button
              onClick={() => executeMoveCall("reset")}
              disabled={waitingForTxn !== ""}
            >
              {waitingForTxn === "reset" ? <ClipLoader size={20} /> : "Reset"}
            </Button>
          ) : null}
        </Flex>
      </Flex>
    </>
  );
}
function getCounterFields(data: SuiObjectData) {
  if (data.content?.dataType !== "moveObject") {
    return null;
  }

  return data.content.fields as { value: number; owner: string };
}

CreateCounter.tsx 文件

import { Transaction } from "@mysten/sui/transactions";
import { Button, Container } from "@radix-ui/themes";
import { useSignAndExecuteTransaction, useSuiClient } from "@mysten/dapp-kit";
import { useNetworkVariable } from "./networkConfig";
import ClipLoader from "react-spinners/ClipLoader";

export function CreateCounter({
  onCreated,
}: {
  onCreated: (id: string) => void;
}) {
  const counterPackageId = useNetworkVariable("counterPackageId");
  const suiClient = useSuiClient();
  const {
    mutate: signAndExecute,
    isSuccess,
    isPending,
  } = useSignAndExecuteTransaction();

  function create() {
    const tx = new Transaction();

    tx.moveCall({
      arguments: [],
      target: `${counterPackageId}::counter::create`,
    });

    signAndExecute(
      {
        transaction: tx,
      },
      {
        onSuccess: async ({ digest }) => {
          const { effects } = await suiClient.waitForTransaction({
            digest: digest,
            options: {
              showEffects: true,
            },
          });

          onCreated(effects?.created?.[0]?.reference?.objectId!);
        },
      },
    );
  }

  return (
    <Container>
      <Button
        size="3"
        onClick={() => {
          create();
        }}
        disabled={isSuccess || isPending}
      >
        {isSuccess || isPending ? <ClipLoader size={20} /> : "Create Counter"}
      </Button>
    </Container>
  );
}

networkConfig.ts 文件

import { getFullnodeUrl } from "@mysten/sui/client";
import {
  DEVNET_COUNTER_PACKAGE_ID,
  TESTNET_COUNTER_PACKAGE_ID,
  MAINNET_COUNTER_PACKAGE_ID,
} from "./constants.ts";
import { createNetworkConfig } from "@mysten/dapp-kit";

const { networkConfig, useNetworkVariable, useNetworkVariables } =
  createNetworkConfig({
    devnet: {
      url: getFullnodeUrl("devnet"),
      variables: {
        counterPackageId: DEVNET_COUNTER_PACKAGE_ID,
      },
    },
    testnet: {
      url: getFullnodeUrl("testnet"),
      variables: {
        counterPackageId: TESTNET_COUNTER_PACKAGE_ID,
      },
    },
    mainnet: {
      url: getFullnodeUrl("mainnet"),
      variables: {
        counterPackageId: MAINNET_COUNTER_PACKAGE_ID,
      },
    },
  });

export { useNetworkVariable, useNetworkVariables, networkConfig };

PACKAGE_ID 配置

constants.ts文件中配置PACKAGE_ID

export const DEVNET_COUNTER_PACKAGE_ID = "0xTODO";
export const TESTNET_COUNTER_PACKAGE_ID = "0x00012e566ad8b36293836f4901f72d32f181782299bf54724f7dbb6b718c9683";
export const MAINNET_COUNTER_PACKAGE_ID = "0xTODO";

安装依赖

hello_sui_move/sui-dapp on  main [?] via ⬢ v22.1.0 via 🅒 base 
➜ pnpm install    

运行前端项目

hello_sui_move/sui-dapp on  main [?] via ⬢ v22.1.0 via 🅒 base took 18.3s 
➜ pnpm dev                         

> sui-dapp@0.0.0 dev /Users/qiaopengjun/Code/sui/hello_sui_move/sui-dapp
> vite


  VITE v6.2.5  ready in 168 ms

  ➜  Local:   http://localhost:5173/
  ➜  Network: use --host to expose
  ➜  press h + enter to show help

在浏览器打开并连接钱包

image-20250405170937474

调用 Create Counter

image-20250405172625166

成功调用

image-20250405172727930

调用 increment

image-20250405172846655

成功调用

image-20250405172914244

总结

通过这篇实战指南,我们从安装 Sui CLI 开始,一步步完成了钱包管理、智能合约开发和前端交互的完整流程,最终在 Sui 区块链上成功运行了一个计数器 DApp。这个过程不仅展示了 Sui 在 Web3 开发中的高效与简洁,也为新手提供了一个清晰的学习路径。无论你是想迈出 Web3 开发的第一步,还是希望深入了解 Sui 的技术优势,现在都可以信心满满地动手实践。下一站,不妨挑战更复杂的合约或应用,解锁 Sui 的更多可能!

参考