SUI 主网上线,前端开发者的 DApp 开发指南

1,446 阅读2分钟

我正在参加「掘金·启航计划」!

前言

作为和 Aptos 同期出名的 Move 语言生态双子星的 Sui 网络,在北京时间5月3日晚上8点,也已上线了主网。那么要进入 Sui 生态进行 dapp 的开发,要先记住一句话:万物皆是 Object

  • 为什么这么说呢?

Sui 借鉴了 BTC 的 UTXO 的模式,它将每个交易输出视为一个未使用的交易,使得交易的输入和输出可以更精细地控制和跟踪。

用更为直观的方式来解读,在链上,我的资产,也是一个个 Object 的模式。看下图:

image.png

一个账号下可以有多种 Coin,每一种 Coin 都是由一个个的 Object 组成,所有这种 Coin 的 Object 的 balance 相加,就是账户下这种 Coin 的余额。

而 nft 作为非同质化代币,在 Sui 上的表现,也是一个 Object,只不过它是一种更为特殊的 Object。

进入开发

连接钱包

1. 安装依赖

作为 dapp 开发,第一步势必是从连接插件钱包开始,这里我们使用 suiet 团队开发的库,相较于官方的,它对于其他钱包连接的支持更好

yarn add @suiet/wallet-kit

2. 引入 WalletProvider

在项目的根文件中,引入 WalletProvider(也就是在你 React 项目的根文件中,如果是 nextjs,则在 _app.tsx 中)

<WalletProvider>
  <Component {...pageProps} />
</WalletProvider>

3. 在页面中连接钱包

  • 可以使用 Suiet 中自带的按钮 ConnectButton,点击后展开钱包列表
import { ConnectButton } from '@suiet/wallet-kit'

//...
<ConnectButton />

image.png

  • 也可以根据需求,支持哪些钱包的连接

这里 select 的参数,现在可以使用有 SuietSui WalletEthos WalletMartian Sui Wallet 这4种,分别对应4种钱包。其中 Sui Wallet 是官方钱包

const { select } = useWallet()

<button onClick={() => select('Suiet')}>Connect wallet</button>

4. 获取地址和连接状态

const { address, connected } = useWallet()

5. 签名(signMessage)

import { ethers } from 'ethers';
// 这里借用 ethers 库来帮我们处理 message,将其转为 Uint8Array 类型

const message = ethers.utils.toUtf8Bytes('test 123')
const { signature, messageBytes } = await signMessage({ message })

6. 发送交易

这里要注意一下,sui 的交易和之前的 aptos、evm 都有所区别

  • 普通交易,不涉及到自己的资产的
import { TransactionBlock } from '@mysten/sui.js';
const { signAndExecuteTransactionBlock } = useWallet()

const handleTransaction = async () => {
    const tx = new TransactionBlock()
    tx.moveCall({
      target: `${packageId}::${moduleName}::${functionName}`,
      arguments: [
        tx.pure(参数1),
        tx.pure(参数2),
      ],
      typeArguments: [],
    })
    const result = await signAndExecuteTransactionBlock({
      transactionBlock: tx,
      options: { showEffects: true },
    })
    console.log('result', result)
    // 通过result?.effects?.status?.status获取交易状态,成功为 'success',失败为'failure'
}
<button onClick={handleTransaction}>Send Transaction</button>
  • 涉及到资产交易的。

由于上文提到过 Sui 的核心:万物皆 object,在对资产进行交易时,需要传入作为费用的 objectId。

但这又会出现一个问题:如果我只有一个 object,我既要用这个 object 来作为交易费用,同时,我有需要一个 object 来支付这笔交易的 gasFee,那这交易应该怎么发呢?

这里就会引入一个新的概念:拆币(splitCoin)。也就是说,我需要发一笔交易,把一个 object 拆成了两个,一个足以支付交易费用,一个集合了剩余的 balance 的 object 来作为 gasFee。

例如:我的账户下当前只有一个 balance 为 0.5 的 object。我要发一笔交易,这个合约需要收 0.3 的费用。那么此时,我需要发送一笔拆币交易,将这个 0.5 的 object 拆出一个 0.3 的object,剩余部分作为一个 object 来作为 gasFee。

但如果每次交易都要分开发送两笔:拆币、调用合约,那太复杂了,这里我们将它们合并成一笔交易:

import { TransactionBlock } from '@mysten/sui.js';
const { signAndExecuteTransactionBlock } = useWallet()

const handleTransaction = async () => {
    const tx = new TransactionBlock()

    const value = '300000000'  // 这里是想要拆出的目标值
    const [coins] = tx.splitCoins(tx.gas, [
      tx.pure(BigInt(value)),
    ])
    tx.moveCall({
      target: `${packageId}::${moduleName}::${functionName}`,
      arguments: [
        tx.pure(参数1),
        tx.pure(参数2),
        tx.makeMoveVec({ objects: [coins] }),
      ],
      typeArguments: [],
    })
    const result = await signAndExecuteTransactionBlock({
      transactionBlock: tx,
      options: { showEffects: true },
    })
    console.log('result', result)
    // 通过result?.effects?.status?.status获取交易状态,成功为 'success',失败为'failure'
}
<button onClick={handleTransaction}>Send Transaction</button>

可以看到,这里的交易合并了 splitCoinmoveCall 两笔交易。为了确保所有人都能够正常交易,建议涉及到资产的交易,都加入 splitCoin

常用 rpc 方法

1. 创建 rpcProvider

import { Connection, JsonRpcProvider } from '@mysten/sui.js';
const suiProvider = new JsonRpcProvider(new Connection({ fullnode: `${节点rpc地址}` }))

2. 获取余额

const { totalBalance } = await suiProvider.getBalance({
  owner: `${你的地址}`,
  coinType: `${币的类型地址}` //如果不传,那么返回的就是 SUI 的 balance
})

3. 获取账户下的所有 object,注意:这里的 getOwnedObjects 是分页返回的

const allObjects = await suiProvider.getOwnedObjects({
  owner: address,
  options: {
    showType: true,
    showDisplay: true,
    showContent: true,
  }
});

4. 筛选账户下的 nft

import { Coin } from '@mysten/sui.js';

const objectIDs = (allObjectRefs?.data || [])
  .filter((item) => !Coin.isCoin(item))
  .map((anObj) => anObj.data.objectId);

5. 模拟交易(调用合约的 public 的方法,也用这个)

import { TransactionBlock, JsonRpcProvider, Connection } from '@mysten/sui.js';

const suiProvider = new JsonRpcProvider(new Connection({ fullnode: `${节点rpc地址}` }))
const tx = new TransactionBlock()

tx.moveCall({
  target: `${packageId}::${moduleName}::${functionName}`,
  arguments: [
    tx.pure(参数1),
    tx.pure(参数2),
  ],
  typeArguments: [],
})
const result = await suiProvider.devInspectTransactionBlock({
  transactionBlock: tx,
  sender: `${地址}`
})

总结

Sui 相对于 evm、aptos 生态,它是一种完全不同的链。不管是内部实现,还是对于 dapp 开发者来说,都是一种非常新奇的体验。