Aptos Wiki
MoveMove 社区Aptos 链接合集Sui WikiStarcoin Wiki
  • 🎉欢迎
  • 💡概览
    • 起源
    • 愿景
    • 主网路线图
    • 激励测试网(1)
    • 技术路线图
    • 激励测试网(2)
    • 激励测试网(3)
    • 激励测试网(4)
  • 📌基础
    • 账户(Account)
    • 事件(Events)
    • 证明(Proof)
    • Gas 和交易费
    • 节点网络和同步
    • 验证者节点
    • 全节点
    • 交易和状态
  • 🔄交易
    • 与 Aptos 区块链交互
  • 📓教程
    • Petra 钱包插件安装使用
    • 教程指引
    • 您的第一笔交易
    • 您的第一个 Move 模块
    • 您的第一个 NFT
    • 运行本地网络
    • 开发网 Devnet
      • 运行全节点(官方教程)
      • 运行全节点(社区教程)
        • Windows
        • Linux
    • 测试网 Testnet
      • 社区教程
        • AIT-3 新功能
        • 节点要求
        • AIT-3 步骤
        • 使用 Docker
        • 全节点搭建
        • 加入测试网
        • 离开激励测试网
  • 🖊️博客
    • Block-STM:我们如何在 Aptos 区块链上每秒执行超过 16 万笔交易
  • 🗻生态
    • Aptos 宣布资助计划!
    • Aptos 生态项目汇总
      • 聚合器
        • Hippo Labs
      • Defi
        • 🟢Pontem Network
        • 🟢Vial Protocol
        • 1KX Protocol
        • Seam Money
        • Aries Markets
        • Empo Finance
        • Ultima protocol
        • Econia
        • Zaptos Finance
        • Laminar Market
        • Thala Labs
        • AptoSwap
        • Aptoslend
        • ASwap
      • 基础设施
        • 🟢Aptos 域名服务
        • Nutrios
        • Dialect
        • Switchboard
      • NFT
        • 🟢Topaz NFT Marketplace
        • TokenMasksLabs
        • Clone Protocol
        • Aptos Ape Society
      • 钱包
        • 🟢Fewcha Wallet
        • 🟢Martian Wallet
        • Volt.id wallet
        • ONTO Wallet
        • Hive Wallet
        • Blocto
      • 工具
        • ChainIDE
        • Paymagic
        • Aptosphere
        • Saber Labs
  • ❓问题(FAQ)
    • 常见问题
    • 如何分享自己的节点
    • 如何运行多个节点
    • 如何修改节点配置文件
Powered by GitBook
On this page
  • Step 1) 编写和测试 Move 模块​
  • Step 2) 发布并与 Move 模块交互​
  1. 教程

您的第一个 Move 模块

Previous您的第一笔交易Next您的第一个 NFT

Last updated 3 years ago

本教程详细介绍了如何编写、编译、测试、发布以及与 Aptos 区块链上的 Move 模块交互。步骤如下:

  • 编写、编译和测试 Move 模块

  • 将 Move 模块发布到 Aptos 区块链

  • 初始化并与 Move 模块的资源交互

请从下面的代码仓库中 clone 最新代码!

本教程以“”为基础,并借用该代码作为本示例的库。 在下面下载示例代码:

对于本教程,将重点关注 hello_blockchain.py 并重用上一教程中的 first_transaction.py 库。

你可以在找到 python 项目。

对于本教程,将重点关注 hello_blockchain.py 并重用上一教程中的 first_transaction.py 库。

你可以在找到 Rust 项目。

对于本教程,将重点关注 hello_blockchain.py 并重用上一教程中的 first_transaction.py 库。

你可以在找到 typescript 项目。

Step 1) 编写和测试 Move 模块

Step 1.1) 下载 Aptos-core

为了简化本练习,Aptos-core 有一个 move-examples 目录,可以轻松构建和测试 Move 模块,而无需下载其他资源。 随着时间的推移,我们将扩展本节以描述如何利用 Move 工具进行开发。

现在,下载并准备 Aptos-core:

git clone https://github.com/aptos-labs/aptos-core.git
cd aptos-core
./scripts/dev_setup.sh
source ~/.cargo/env

在上面的终端窗口中,将目录更改为 aptos-move/move-examples。 在本教程的其余部分保留此终端窗口 - 后面我们将它称为“Move 窗口”。 本节的其余部分将查看文件 `sources/HelloBlockchain.move`。

该模块使用户可以在其帐户下创建 `String` 资源并进行设置。 用户只能设置自己的资源,不能设置他人的资源。

module HelloBlockchain::Message {
    use Std::ASCII;
    use Std::Errors;
    use Std::Signer;

    struct MessageHolder has key {
        message: ASCII::String,
    }

    public(script) fun set_message(account: signer, message_bytes: vector<u8>)
    acquires MessageHolder {
        let message = ASCII::string(message_bytes);
        let account_addr = Signer::address_of(&account);
        if (!exists<MessageHolder>(account_addr)) {
            move_to(&account, MessageHolder {
                message,
            })
        } else {
            let old_message_holder = borrow_global_mut<MessageHolder>(account_addr);
            old_message_holder.message = message;
        }
    }
}

在前面的代码中,两个重要的部分是 MessageHolder 结构体和函数 set_message。 set_message 是一个 script 函数,允许事务直接调用它。调用它时,该函数将确定当前帐户是否具有 MessageHolder 资源,如果不存在,则创建并存储该 message。 如果资源存在,则覆盖 MessageHolder 中的 message。

Move 允许内联测试,因此我们添加了 get_message 以方便获取 message,并添加了一个测试方法 sender_can_set_message 来验证端到端流。 这可以通过运行 cargo test 来验证。 在 sources/HelloBlockchainTest.move 下还有另一个测试,演示了另一种编写测试的方法。

注意:sender_can_set_message 是一个 script 函数,以便调用脚本函数 set_message。

    const ENO_MESSAGE: u64 = 0;

    public fun get_message(addr: address): ASCII::String acquires MessageHolder {
        assert!(exists<MessageHolder>(addr), Errors::not_published(ENO_MESSAGE));
        *&borrow_global<MessageHolder>(addr).message
    }

    #[test(account = @0x1)]
    public(script) fun sender_can_set_message(account: signer) acquires MessageHolder {
        let addr = Signer::address_of(&account);
        set_message(account,  b"Hello, Blockchain");

        assert!(
          get_message(addr) == ASCII::string(b"Hello, Blockchain"),
          0
        );
    }

现在我们回到我们的应用程序来部署 Atpos 区块链上的模块并与之交互。 如前所述,本教程建立在早期教程的基础上并共享通用代码。 因此,本教程仅讨论该库的新功能,包括发布、发送 set_message 交易和读取 MessageHolder::message 的能力。 发布模块和提交交易的唯一区别是负载(payload)类型。 请参阅以下内容:

class HelloBlockchainClient(RestClient):

    def publish_module(self, account_from: Account, module_hex: str) -> str:
        """Publish a new module to the blockchain within the specified account"""

        payload = {
            "type": "module_bundle_payload",
            "modules": [
                {"bytecode": f"0x{module_hex}"},
            ],
        }
        txn_request = self.generate_transaction(account_from.address(), payload)
        signed_txn = self.sign_transaction(account_from, txn_request)
        res = self.submit_transaction(signed_txn)
        return str(res["hash"])
    
pub struct HelloBlockchainClient {
    pub rest_client: RestClient,
}

impl HelloBlockchainClient {
    /// Represents an account as well as the private, public key-pair for the Aptos blockchain.
    pub fn new(url: String) -> Self {
        Self {
            rest_client: RestClient::new(url),
        }
    }

    /// Publish a new module to the blockchain within the specified account
    pub fn publish_module(&self, account_from: &mut Account, module_hex: &str) -> String {
        let payload = serde_json::json!({
            "type": "module_bundle_payload",
            "modules": [{"bytecode": format!("0x{}", module_hex)}],
        });
        let txn_request = self
            .rest_client
            .generate_transaction(&account_from.address(), payload);
        let signed_txn = self.rest_client.sign_transaction(account_from, txn_request);
        let res = self.rest_client.submit_transaction(&signed_txn);
        res.get("hash").unwrap().as_str().unwrap().to_string()
    }
    
class HelloBlockchainClient extends RestClient {

  /** Publish a new module to the blockchain within the specified account */
  async publishModule(accountFrom: Account, moduleHex: string): Promise<string> {
    const payload = {
      "type": "module_bundle_payload",
      "modules": [
        {"bytecode": `0x${moduleHex}`},
      ],
    };
    const txnRequest = await this.generateTransaction(accountFrom.address(), payload);
    const signedTxn = await this.signTransaction(accountFrom, txnRequest);
    const res = await this.submitTransaction(accountFrom, signedTxn);
    return res["hash"];
  }
  

    def get_message(self, contract_address: str, account_address: str) -> Optional[str]:
        """ Retrieve the resource Message::MessageHolder::message """

        resources = self.account_resources(account_address)
        for resource in resources:
            if resource["type"] == f"0x{contract_address}::Message::MessageHolder":
                return resource["data"]["message"]
        return None
    
    /// Retrieve the resource Message::MessageHolder::message
    pub fn get_message(&self, contract_address: &str, account_address: &str) -> Option<String> {
        let module_type = format!("0x{}::Message::MessageHolder", contract_address);
        self.rest_client
            .account_resources(account_address)
            .as_array()
            .unwrap()
            .iter()
            .find(|x| {
                x.get("type")
                    .map(|v| v.as_str().unwrap() == module_type)
                    .unwrap_or(false)
            })
            .and_then(|coin| {
                coin.get("data")
                    .unwrap()
                    .get("message")
                    .unwrap()
                    .as_str()
                    .and_then(|s| Some(s.to_string()))
            })
    }
    
  /** Retrieve the resource Message::MessageHolder::message */
  async getMessage(contractAddress: string, accountAddress: string): Promise<string> {
    const resources = await this.accountResources(accountAddress);
    for (const key in resources) {
      const resource = resources[key];
      if (resource["type"] == `0x${contractAddress}::Message::MessageHolder`) {
        return resource["data"]["message"];
      }
    }
  }
  

Move 模块必须公开用于初始化和操作资源的 script 函数。 然后可以从交易中调用该 script。

    def set_message(self, contract_address: str, account_from: Account, message: str) -> str:
        """ Potentially initialize and set the resource Message::MessageHolder::message """

        payload = {
            "type": "script_function_payload",
            "function": f"0x{contract_address}::Message::set_message",
            "type_arguments": [],
            "arguments": [
                message.encode("utf-8").hex(),
            ]
        }
        txn_request = self.generate_transaction(account_from.address(), payload)
        signed_txn = self.sign_transaction(account_from, txn_request)
        res = self.submit_transaction(signed_txn).json()
        return str(res["hash"])
    
    /// Potentially initialize and set the resource Message::MessageHolder::message
    pub fn set_message(
        &self,
        contract_address: &str,
        account_from: &mut Account,
        message: &str,
    ) -> String {
        let message_hex = hex::encode(message.as_bytes());
        let payload = serde_json::json!({
            "type": "script_function_payload",
            "function": format!("0x{}::Message::set_message", contract_address),
            "type_arguments": [],
            "arguments": [message_hex]
        });

        let txn_request = self
            .rest_client
            .generate_transaction(&account_from.address(), payload);
        let signed_txn = self.rest_client.sign_transaction(account_from, txn_request);
        self.rest_client
            .submit_transaction(&signed_txn)
            .get("hash")
            .unwrap()
            .as_str()
            .unwrap()
            .to_string()
    }
    
  /**  Potentially initialize and set the resource Message::MessageHolder::message */
  async setMessage(contractAddress: string, accountFrom: Account, message: string): Promise<string> {
    let payload: { function: string; arguments: string[]; type: string; type_arguments: any[] };
    payload = {
      "type": "script_function_payload",
      "function": `0x${contractAddress}::Message::set_message`,
      "type_arguments": [],
      "arguments": [
        Buffer.from(message, "utf-8").toString("hex")
      ]
    };

    const txnRequest = await this.generateTransaction(accountFrom.address(), payload);
    const signedTxn = await this.signTransaction(accountFrom, txnRequest);
    const res = await this.submitTransaction(accountFrom, signedTxn);
    return res["hash"];
  }

}

For Python3:

  • 打开您喜欢的终端并导航到您下载上述示例项目的位置

  • 安装要求的依赖包:pip3 install -r requirements.txt.

  • 执行示例命令:python3 hello_blockchain.py Message.mv

  • 片刻之后,它会输出 "Update the module with Alice's address, build, copy to the provided path, and press enter."

  • 在 "Move 窗口" 的终端中,对于我们之前看过的 Move 文件:

    • 编辑 Move.toml 并在 [addresses] 下将 0xe110 地址替换为 Alice 的地址,在提示上方输出。

    • 构建 cargo run -- sources

    • 拷贝 build/Examples/bytecode_modules/Message.mv 到与本教程项目代码相同的文件夹中

  • 返回到您的另一个终端窗口,并在提示符处按下“Enter”键以继续执行其余代码

输出的结果如下:

=== Addresses ===
Alice: a52671f10dc3479b09d0a11ce47694c0
Bob: ec6ec14e4abe10aaa6ad53b0b63a1806

=== Initial Balances ===
Alice: 10000000
Bob: 10000000

Update the module with Alice's address, build, copy to the provided path, and press enter.

=== Testing Alice ===
Publishing...
Initial value: None
Setting the message to "Hello, Blockchain"
New value: Hello, Blockchain

=== Testing Bob ===
Initial value: None
Setting the message to "Hello, Blockchain"
New value: Hello, Blockchain

结果表明,Alice 和 Bob 从没有资源变成了一个 message 设置为“Hello, Blockchain”的地址。

可以通过访问 REST 接口或区块浏览器来验证数据:

Step 1.2) 查看模块

Step 1.3) 测试模块

Step 2) 发布并与 Move 模块交互

Step 2.1) 发布 Move 模块

Step 2.2) 读取资源

该模块在一个地址发布。 这是下面的 contract_address。 这类似于前面的示例,其中 TestCoin 在 0x1地址下。 contract_address 将与发布它的地址相同。

Step 2.3) 修改资源

注意:虽然 REST 接口可以显示字符串,但由于 JSON 和 Move 的限制,它无法确定参数是字符串还是十六进制编码的字符串。 所以交易参数总是假设后者。 因此,在此示例中,消息被编码为十六进制字符串。

Step 3) 初始化并与 Move 模块交互

下载

Alice 的账户:

Bob 的账户:

📓
您的第一笔交易
这里
这里
这里
​
​
​
​
​
​
​
​
​
​
​
示例项目
REST 接口
区块浏览器