# 您的第一个 Move 模块

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

* 编写、编译和测试 Move 模块
* 将 Move 模块发布到 Aptos 区块链
* 初始化并与 Move 模块的资源交互

{% hint style="info" %}
请从下面的代码仓库中 clone 最新代码！
{% endhint %}

本教程以“[您的第一笔交易](/jiao-cheng/nin-de-di-yi-bi-jiao-yi.md)”为基础，并借用该代码作为本示例的库。 在下面下载示例代码：

{% tabs %}
{% tab title="Python" %}
对于本教程，将重点关注 **hello\_blockchain.py** 并重用上一教程中的 **first\_transaction.py** 库。&#x20;

你可以在[这里](https://github.com/aptos-labs/aptos-core/tree/main/developer-docs-site/static/examples/python)找到 python 项目。
{% endtab %}

{% tab title="Rust" %}
对于本教程，将重点关注 **hello\_blockchain.py** 并重用上一教程中的 **first\_transaction.py** 库。&#x20;

你可以在[这里](https://github.com/aptos-labs/aptos-core/tree/main/developer-docs-site/static/examples/rust)找到 Rust 项目。
{% endtab %}

{% tab title="Typescript" %}
对于本教程，将重点关注 **hello\_blockchain.py** 并重用上一教程中的 **first\_transaction.py** 库。&#x20;

你可以在[这里](https://github.com/aptos-labs/aptos-core/tree/main/developer-docs-site/static/examples/typescript)找到 typescript 项目。
{% endtab %}
{% endtabs %}

### Step 1) 编写和测试 Move 模块[​](https://aptos.dev/tutorials/your-first-move-module#step-1-write-and-test-the-move-module) <a href="#step-1-write-and-test-the-move-module" id="step-1-write-and-test-the-move-module"></a>

#### Step 1.1) 下载 Aptos-core[​](https://aptos.dev/tutorials/your-first-move-module#step-11-download-aptos-core) <a href="#step-11-download-aptos-core" id="step-11-download-aptos-core"></a>

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

现在，下载并准备 Aptos-core：

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

#### Step 1.2) 查看模块[​](https://aptos.dev/tutorials/your-first-move-module#step-12-review-the-module) <a href="#step-12-review-the-module" id="step-12-review-the-module"></a>

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

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

```rust
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`。

#### Step 1.3) 测试模块[​](https://aptos.dev/tutorials/your-first-move-module#step-13-testing-the-module) <a href="#step-13-testing-the-module" id="step-13-testing-the-module"></a>

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

注意：`sender_can_set_message` 是一个 `script` 函数，以便调用脚本函数 `set_message`。

```rust
    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
        );
    }
```

### Step 2) 发布并与 Move 模块交互[​](https://aptos.dev/tutorials/your-first-move-module#step-2-publishing-and-interacting-with-the-move-module) <a href="#step-2-publishing-and-interacting-with-the-move-module" id="step-2-publishing-and-interacting-with-the-move-module"></a>

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

#### Step 2.1) 发布  Move 模块[​](https://aptos.dev/tutorials/your-first-move-module#step-21-publishing-the-move-module) <a href="#step-21-publishing-the-move-module" id="step-21-publishing-the-move-module"></a>

{% tabs %}
{% tab title="Python" %}

```python
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"])
    
```

{% endtab %}

{% tab title="Rust" %}

```rust
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()
    }
    
```

{% endtab %}

{% tab title="Typescript" %}

```typescript
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"];
  }
  
```

{% endtab %}
{% endtabs %}

#### Step 2.2) 读取资源[​](https://aptos.dev/tutorials/your-first-move-module#step-22-reading-a-resource) <a href="#step-22-reading-a-resource" id="step-22-reading-a-resource"></a>

该模块在一个地址发布。 这是下面的 `contract_address`。 这类似于前面的示例，其中 `TestCoin` 在 `0x1`地址下。 `contract_address` 将与发布它的地址相同。[​](https://aptos.dev/tutorials/your-first-move-module#step-21-publishing-the-move-module)

{% tabs %}
{% tab title="Python" %}

```python
    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
    
```

{% endtab %}

{% tab title="Rust" %}

```rust
    /// 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()))
            })
    }
    
```

{% endtab %}

{% tab title="Typescript" %}

```typescript
  /** 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"];
      }
    }
  }
  
```

{% endtab %}
{% endtabs %}

#### Step 2.3) 修改资源[​](https://aptos.dev/tutorials/your-first-move-module#step-23-modifying-a-resource) <a href="#step-23-modifying-a-resource" id="step-23-modifying-a-resource"></a>

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

注意：虽然 REST 接口可以显示字符串，但由于 JSON 和 Move 的限制，它无法确定参数是字符串还是十六进制编码的字符串。 所以交易参数总是假设后者。 因此，在此示例中，消息被编码为十六进制字符串。[​](https://aptos.dev/tutorials/your-first-move-module#step-21-publishing-the-move-module)

{% tabs %}
{% tab title="Python" %}

```python
    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"])
    
```

{% endtab %}

{% tab title="Rust" %}

```rust
    /// 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()
    }
    
```

{% endtab %}

{% tab title="Typescript" %}

```typescript
  /**  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"];
  }

}
```

{% endtab %}
{% endtabs %}

#### Step 3) 初始化并与 Move 模块交互[​](https://aptos.dev/tutorials/your-first-move-module#step-3-initialize-and-interact-with-the-move-module) <a href="#step-3-initialize-and-interact-with-the-move-module" id="step-3-initialize-and-interact-with-the-move-module"></a>

{% tabs %}
{% tab title="Python" %}
For Python3:

* 下载[示例项目](https://github.com/aptos-labs/aptos-core/tree/main/developer-docs-site/static/examples/python)
* 打开您喜欢的终端并导航到您下载上述示例项目的位置
* 安装要求的依赖包：`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”键以继续执行其余代码
  {% endtab %}

{% tab title="Rust" %}

{% endtab %}

{% tab title="Typescript" %}

{% endtab %}
{% endtabs %}

输出的结果如下：

```
=== 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 接口或区块浏览器来验证数据：

* Alice 的账户：[REST 接口](https://fullnode.devnet.aptoslabs.com/accounts/a52671f10dc3479b09d0a11ce47694c0/)
* Bob 的账户：[区块浏览器](https://explorer.devnet.aptos.dev/account/ec6ec14e4abe10aaa6ad53b0b63a1806)


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://wiki.aptos.movemove.org/jiao-cheng/nin-de-di-yi-ge-move-mo-kuai.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
