如何开发一个简单的 dApp(一)

  • Ch1hiro
  • 更新于 2025-01-12 13:42
  • 阅读 713

本篇文章将学习如何在Sui链上开发并部署一个资源管理器

后端合约

合约升级

​ 在开始合约编写之前,先一起来了解一下合约升级,顾名思义可以将已经发布的合约进行升级,在dApp遇到漏洞或需要添加新功能时可以及时更正,并且能防止原有数据的丢失

​ 需要注意的是进行合约升级需要在共享obejct中添加版本属性,这样能防止在给共享object添加动态属性后,旧合约仍对共享object进行操作从而引发的兼容问题,关于具体的操作可以参考这里

​ 执行 sui move new resource_manage 创建一个包,接着就可以开始编写合约了

首先设置一个常量VERSION代表当前版本号

const VERSION: u64 = 1;

创建一个AdminCap用来将数据转移操作成为专有,只允许使用AdminCap调用

public struct AdminCap has key {
    id: UID,
}

定义两个 Struct 用来创建 Profile 并记录在 State 中,在 object StateProfile中添加 version 属性来记录当前object的版本信息

public struct State has key {
    id: UID,
    users: Table<address, address>,
    admin: ID,
    version: u64,
}

public struct Profile has key {
    id: UID,
    name: String,
    description: String,
    version: u64,
}

接着创建一个事件来记录Profile的创建过程

public struct ProfileCreated has copy, drop {
    profile: address,
    owner: address,
}

下一步初始化合约将 State 共享,并把AdminCap的所有权转移给合约的发布者

fun init(ctx: &mut TxContext) {
    let admin = AdminCap {
        id: object::new(ctx),
    };
    transfer::share_object(State{
        id: object::new(ctx), 
        users: table::new(ctx),
        admin: object::id(&admin),
        version: VERSION,
    });
    transfer::transfer(admin, tx_context::sender(ctx));
}

下面开始定义一个创建 Profile 的函数,注意要判断version的值

const EWrongVersion: u64 = 0;
const EProfileExit: u64 = 1;

public entry fun create_profile(name: String, description: String, state: &mut State, ctx: &mut TxContext) {
    // 判断 State 的版本
    assert!(state.version == VERSION, EWrongVersion);
    let owner = tx_context::sender(ctx);
    assert!(!table::contains(&state.users, owner), EProfileExit);
    let uid = object::new(ctx);
    let id = object::uid_to_inner(&uid);

    let new_profile = Profile {
        id: uid,
        name: name,
        description: description,
        version: VERSION,
    };

    transfer::transfer(new_profile, owner);
    table::add(&mut state.users, owner, object::id_to_address(&id));

    event::emit(ProfileCreated { 
        profile: object::id_to_address(&id), 
        owner,
    });
}

​ 函数的大致逻辑就是先从 State.users 中判断交易者是否已经记录过,如果没有则创建一个 Profile 并将所有权转移给交易者,并将交易者的 addressProfileid 转换成 address 类型后一并记录到 State 中去,最后触发事件记录资源的创建

接着定义一个检查资源是否存在的函数

public fun check_has_profile(state: &State, user: address): Option<address> {
    // 判断State的版本
    assert!(state.version == VERSION, EWrongVersion);
    if(table::contains(&state.users, user)) {
        option::some(*table::borrow(&state.users, user))
    }else {
        option::none()
    }
}

这里 * 号的作用是“解引用”,table::borrow() 返回的是一个引用,但是 option::some() 函数需要接收一个值

到此我们的合约就编写完成了,最后修改 Move.toml 文件的格式

[package]
name = "resource_manage"
edition = "2024.beta" # edition = "legacy" to use legacy (pre-2024) Move
version = "0.0.1"

[addresses]
resource_manage = "0x0"
执行 sui move build 编译

执行 sui client publish 部署

image-20250111190251615

PackageID: 0xcb764601ba2573e1e31fc11f5770897753ee2934ed11e8c86612b8ca5c73e9af 
State: 0x185d432938a81bec3945417806c11ef739c962a7d99b5aa55292d894d31651dd
AdminCap: 0x88e4a163b42ead5b524a66b47ae06f5da39063d7b557b66617b73f8bdbeb34a6
UpgradeCap: 0xd44c6de28d105a9a4fe581f701f1f3aa43585797fbdfffca1cc018103026167b

通过Sui Cli调用create_profile函数

sui client call --package <PACKAGE-ID> --module resource_manage --function create_profile --args "file_1" "version = 1" <STATE-ID>

可以获取到ProfileID,使用sui client object <Profile-ID>查看详细信息

image-20250111190533618

前端调用

执行命令 npm create @mysten/dapp 开始初始化项目

image-20241112202825435

这里选择第一个生成一个简单的 dapp 模板,第二个是携带 Move 代码的计算器示例

接着进入项目内执行 npm install 下载依赖包,这时可能会提示 eslint 版本不兼容

image-20250104130647602

我们需要在 package.json 中将 eslint 设置为 ^8.56.0

image-20250104132129994

重新执行 npm install 即可成功

注意:如果安装不成功通过执行 npm install -g npm 来升级版本,以获得更好的兼容

可以在项目根目录执行 npm run dev 来检验项目是否部署成功

image-20250104132515706

如果浏览器出现此界面就代表成功了

往下我们先学习 Sui SDK 一些基本用法

  1. ConnectButton:就是用于连接钱包的按钮

    import { ConnectButton } from '@mysten/dapp-kit';
    
    export function App() {
    return <ConnectButton />;
    }
  2. useCurrentAccount:检索当前选择的钱包账户

    import { ConnectButton, useCurrentAccount } from '@mysten/dapp-kit';
    
    function MyComponent() {
    const account = useCurrentAccount();
    
    return (
        <div>
            <ConnectButton />
            {!account && <div>No account connected</div>}
            {account && (
                <div>
                    <h2>Current account:</h2>
                    <div>Address: {account.address}</div>
                </div>
            )}
        </div>
    );
    }
  3. SuiClient:用来连接到网络并进行交互

    import { getFullnodeUrl, SuiClient } from '@mysten/sui/client';
    
    const suiClient = new SuiClient({
    url: getFullnodeUrl('testnet'),
    });
  4. Transaction:用来打包交易块,可以使用 PTB

    import { Transaction } from "@mysten/sui/transactions";
    
    export const Test = async(name: string, description: string) => {
       const tx = new Transaction();
       tx.moveCall({
           package: packageID,
           module: "module_name",
           function: "fun_name",
           arguments: [
               tx.pure.string(name),
               tx.pure.string(description),
               tx.object(state),
           ],
       });
    
       return tx;
    }
  5. useSignAndExecuteTransaction:进行签名并交易

    import { useSignAndExecuteTransaction } from "@mysten/dapp-kit";
    
    const {mutate: signAndExecute} = useSignAndExecuteTransaction();
    
    const tx = await Test(name, description);
    signAndExecute({
     transaction: tx
    }, {
     onSuccess: () => {
       console.log("success!");
     },
     onError: (error) => {
       console.log(error);
     }
    });

完整代码:https://github.com/Ch1hiro4002/Sui_Frontend_Study/tree/main/week_1

展示效果:

image-20250107194519474

点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
Ch1hiro
Ch1hiro
一名Web3初学者