> For the complete documentation index, see [llms.txt](https://wiki.aptos.movemove.org/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://wiki.aptos.movemove.org/jiao-cheng/nin-de-di-yi-ge-move-mo-kuai.md).

# 您的第一个 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)
