> For the complete documentation index, see [llms.txt](https://gushi10546.gitbook.io/aptos-kai-fa-zhe-wen-dang/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://gushi10546.gitbook.io/aptos-kai-fa-zhe-wen-dang/kai-fa-zhe-jiao-cheng/ni-de-di-yi-ge-dapp.md).

# 你的第一个 DApp

在本教程中，你将学习如何在 Aptos 区块链上建立一个 [DApp](https://en.wikipedia.org/wiki/Decentralized_application)。一个 DApp 通常由一个用 JavaScript 编写的用户界面组成，它与一个或多个 Move 模块进行交互。

我们将使用[你的第一个 Move 模块](/aptos-kai-fa-zhe-wen-dang/kai-fa-zhe-jiao-cheng/ni-de-di-yi-ge-move-mo-kuai.md)中使用的 Move 模块 `HelloBlockchain`，并着重于构建用户界面。

我们将使用以下工具：

* [Aptos SDK](https://github.com/aptos-labs/aptos-core/tree/main/ecosystem/typescript/sdk)
* [Aptos 钱包](https://github.com/aptos-labs/aptos-core/tree/main/ecosystem/web-wallet)，以及
* 可以与区块链进行交互的 [Aptos CLI](https://github.com/aptos-labs/aptos-core/tree/main/crates/aptos)

最终的结果是一个让用户在 Aptos 区块链上发布和分享文本片段的 DApp。

本教程的完整源代码可[在此获得](https://github.com/aptos-labs/aptos-core/tree/main/developer-docs-site/static/examples/typescript/dapp-example)。

### 前置准备

#### Aptos 钱包

在开始教程之前，我们应当先安装 Aptos 钱包扩展程序。

在你安装完钱包之后

1. 打开钱包并且点击 **Create a new wallet**，然后点击 **Create account** 来创建一个 Aptos 的钱包账户。
2. 复制私钥，你将会在后续章节中的用到它来设置 Aptos CLI

{% hint style="info" %}
💡 **注意**

点击 **Faucet** 按钮，确保你的账户有足够的资金进行交易。
{% endhint %}

#### Aptos CLI

1. 安装 [Aptos CLI](https://github.com/aptos-labs/aptos-core/tree/main/crates/aptos).
2. 运行 `aptos init` 命令，当屏幕提示你输入私钥时，把之前你从钱包复制过来的私钥粘贴进去。这将让 Aptos CLI 程序以与你钱包的相同的账户初始化。
3. 运行 `aptos account list` 命令来确认 Aptos CLI 是正常运行的

{% hint style="info" %}
&#x20;💡 **译者注：**

截至翻译完成时期，Aptos 代码仍在高强度更新中，可能会出现不兼容的情况。因此，请使用最新版本的 Aptos CLI，来进行以下的操作。本次使用的 Aptos CLI 版本为 0.3.2，你可以使用 `aptos --version` 来查看自身的 Aptos CLI 版本。
{% endhint %}

### 第 1 步：初始化一个单页应用

我们现在将为我们的 DApp 设置前端的用户界面。在本教程中，我们将使用`create-react-app`来初始化应用，但 React 和 `create-react-app` 都不是必需的。你可以选择使用你喜欢的 JavaScript 框架。

```bash
$ npx create-react-app first-dapp --template typescript
$ cd first-dapp
$ npm start
```

至此我们将有一个可以在浏览器中运行的基础 React 应用。

### 第 2 步：**集成 Aptos Wallet Web3 API**

Aptos 钱包通过 `window.aptos` 接口为 dApp 提供了一个 Web3 API。你可以在第一步中由 `npm start` 打开的浏览器 console 中运行 `await window.aptos.account()` 来了解它是如何工作的（注意一定要是通过 `npm start` 打开的浏览器来调用 `window.aptos` 接口）。它将显示出与你在 Aptos 钱包中设置的帐户相对应的地址，如下图所示：

<figure><img src="/files/VjhzfIz1J0WvWp3qAJbc" alt=""><figcaption></figcaption></figure>

接下来，我们将更新我们的应用程序，使用这个 API 来显示钱包帐户的地址。

#### 等待 window\.aptos 接口被注入到 window DOM 下

在浏览器中集成 `window.aptos` API 的第一步是在 `window.onload` 事件触发之后，才开始渲染你的前端界面。

打开 `src/index.tsx`，更改以下代码片段:

```tsx
root.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);
```

为

```tsx
window.addEventListener('load', () => {
  root.render(
    <React.StrictMode>
      <App />
    </React.StrictMode>
  );
});
```

这一更改将确保在我们渲染前端元素时 `window.aptos` API 已经初始化（如果我们渲染得太早，钱包扩展可能还没有机会初始化 API，因此 `window.aptos` 将变成 `undefiend`）。

#### （可选）TypeScript 语言下 `window.aptos` 的设置

如果你使用的是 TypeScript 语言，你可能还需要通知编译器 `window.aptos` API 的存在。将以下代码添加到 `src/index.tsx`中:

```tsx
declare global {
  interface Window { aptos: any; }
}
```

这将使我们不再依赖 `(window as any).aptos` 命令，而直接可以调用 `window.aptos` API

#### 在应用中展示 `window.aptos.account()` 结果

我们的应用现在已经可以调用 `window.aptos` API，我们将改动 `src/App.tsx` 的代码从而在初始化渲染时获取到`window.aptos.account()`（你的钱包账户）的值，存储到 React state 下，然后把它展示在前端，代码改动如下：

```tsx
import React from 'react';
import './App.css';

function App() {
// Retrieve aptos.account on initial render and store it.
  const [address, setAddress] = React.useState<string | null>(null);
  React.useEffect(() => {
    window.aptos.account().then((data : {address: string}) => setAddress(data.address));
  }, []);

  return (
    <div className="App">
      <p><code>{ address }</code></p>
    </div>
  );
}

export default App;
```

刷新页面即可看到您的帐户地址。

#### 增加 CSS 样式

接下来，替换 `src/App.css` 代码

```css
a, input, textarea {
  display: block;
}

textarea {
    margin: 4px;
    padding: 8px;
    border: solid 1px #333;
    width: 80%;
}
```

我们将在测试页面看到自己的 Aptos 钱包地址:

<figure><img src="/files/8ugphpDCLvLiaJ6a6Hus" alt=""><figcaption></figcaption></figure>

### 第 3 步：通过 SDK 获取链上数据

现在我们的应用已经集成了钱包的能力，下一步，我们将集成链上数据来获取链上数据。我们将使用 Aptos SDK 来获取我们账户的链上信息并且将其展示在前端页面上

#### 在 `package.json` 中增加 `aptos` 依赖

首先，在工程依赖中增加 Aptos SDK 相关依赖：

```bash
$ npm install --save aptos
```

你将会在 `package.json` 中看到 `"aptos": "^0.0.20"` （或者类似的依赖信息）。

#### 创建一个 `AptosClient`

现在我们可以引入 SDK 并且创建一个可以与 Aptos 区块链交互的 `AptosClient` 客户端 （准确来说是与 [Aptos REST API](https://github.com/aptos-labs/aptos-core/tree/main/api) 交互，这才是直接与 Aptos 区块链交互的基础设施）

只要我们的钱包在测试网（devnet）下，我们就可以搭建一个 `AptosClient` 来与测试网环境交互，在 `src/App.tsx` 文件下，增加以下代码：

```tsx
import React from 'react';
import './App.css';
import { Types, AptosClient } from 'aptos';

// Create an AptosClient to interact with devnet.
const client = new AptosClient('<https://fullnode.devnet.aptoslabs.com/v1>');

function App() {
  // Retrieve aptos.account on initial render and store it.
  const [address, setAddress] = React.useState<string | null>(null);
  React.useEffect(() => {
    window.aptos.account().then((data : {address: string}) => setAddress(data.address));
  }, []);

  // Use the AptosClient to retrieve details about the account.
  const [account, setAccount] = React.useState<Types.AccountData | null>(null);
  React.useEffect(() => {
    if (!address) return;
    client.getAccount(address).then(setAccount);
  }, [address]);

  return (
    <div className="App">
      <p><code>address: { address }</code></p>
      <p><code>sequence: { account?.sequence_number }</code></p>
    </div>
  );
}

export default App;
```

现在，前端界面除了展示账户地址，还会展示账户的 `sequence_number`。该 `sequence_number` 代表了下一笔交易的 `sequence_number` ，以用于避免重放攻击。随着你通过该账户执行的交易的增加，该 `sequence_number` 也会随之增加。

在这一步结束后，你将得到以下的页面：

<figure><img src="/files/G5odFEjVAMPTJFdpgRUP" alt=""><figcaption></figcaption></figure>

### 第 4 步：发布一个 Move 模块

我们的 DApp 现在已经可以读取链上数据，下一步则是在链上写入数据。为了完成这个目标，我们将在自己的账户地址上，发布一个 Move 模块。

Move 模块提供了（我们将要写入的数据）存储的空间。具体来说，我们将使用[你的第一个 Move 模块](/aptos-kai-fa-zhe-wen-dang/kai-fa-zhe-jiao-cheng/ni-de-di-yi-ge-move-mo-kuai.md)中的 `hello_blockchain` 模块，它提供了一个名为 `MessageHolder` 的资源，该资源持有一个字符串（称为 `message`）。

#### 使用 Aptos CLI 发布 `hello_blockchain` **模块**

我们将使用 Aptos CLI 来编译并发布 `hello_blockchain` **模块。**

{% hint style="info" %}
💡 **译者注：**

截至翻译完成时期，Aptos 代码仍在高强度更新中，可能会出现不兼容的情况。因此，请使用最新版本的 Aptos CLI 和 Petra 钱包，来进行以下的操作。本次使用的 Aptos CLI 版本为 0.3.2，交互钱包为 Petra 0.1.8 你可以使用 `aptos --version` 来查看自身的 Aptos CLI 版本。
{% endhint %}

1. 下载 [`[hello_blockchain` 程序包\]](https://github.com/aptos-labs/aptos-core/tree/main/aptos-move/move-examples/hello_blockchain)
2. 接着（在终端）调用 `aptos move publish` 命令（记得把 `/path/to/hello_blockchain/` 替换成你的下载路径，并且将 `<address>` 替换为你的 Aptos 账户地址；同时以最新版本的 `move-examples/hello_blockchain/sources/hello_blockchain.move` 下的命名地址为准，比如 Aptos CLI 0.3.2 版本下的命名地址即为 `hello_blockchain`）

```bash
$ aptos move publish --package-dir /path/to/hello_blockchain/ --named-addresses hello_blockchain=<address>
```

比如：

```bash
$ aptos move publish --package-dir ~/code/aptos-core/aptos-move/move-examples/hello_blockchain/ --named-addresses HelloBlockchain=0x5af503b5c379bd69f46184304975e1ef1fa57f422dd193cdad67dc139d532481
```

`—name-addresses` 将会把 `hello_blockchain.move` 中的命名地址 `hello_blockchain` 替换为用户在命令行中指定的地址。举个例子，如果我们指定 `-named-addresses hello_blockchain=0x5af503b5c379bd69f46184304975e1ef1fa57f422dd193cdad67dc139d532481`，那么以下代码：

```rust
module hello_blockchain::Message {
```

将等价为：

```rust
module 0x5af503b5c379bd69f46184304975e1ef1fa57f422dd193cdad67dc139d532481::Message {
```

这使得它可以在指定的账户地址上（在这种情况下是我们的钱包账户地址）发布模块。

假设你的账户有足够的资金来执行交易，你现在可以在你的账户中发布 `HelloBlockchain` 模块。如果你刷新你的 DApp 前端页面，你会看到账户的序列号已经从 0 增加到 1。

你也可以通过进入 Aptos Explorer 查找你的账户来验证该模块是否已发布。如果你向下滚动到账户模块部分，你应该看到类似以下内容。

```json
{
  "address": "0x5af503b5c379bd69f46184304975e1ef1fa57f422dd193cdad67dc139d532481",
  "name": "Message",
  "friends": [],
  "exposedFunctions": [
    {
      "name": "get_message",
      "visibility": "public",
      "genericTypeParams": [],
      "params": [
        "address"
      ],
      "_return": [
        "0x1::string::String"
      ]
    },
    {
      "name": "set_message",
      "visibility": "script",
      "genericTypeParams": [],
      "params": [
        "signer",
        "vector"
      ],
      "_return": []
    }
  ],
  "structs": [
    {
      "name": "MessageChangeEvent",
      "isNative": false,
      "abilities": [
        "drop",
        "store"
      ],
      "genericTypeParams": [],
      "fields": [
        {
          "name": "from_message",
          "type": "0x1::string::String"
        },
        {
          "name": "to_message",
          "type": "0x1::string::String"
        }
      ]
    },
    {
      "name": "MessageHolder",
      "isNative": false,
      "abilities": [
        "key"
      ],
      "genericTypeParams": [],
      "fields": [
        {
          "name": "message",
          "type": "0x1::string::String"
        },
        {
          "name": "message_change_events",
          "type": "0x1::event::EventHandle<0x5af503b5c379bd69f46184304975e1ef1fa57f422dd193cdad67dc139d532481::Message::MessageChangeEvent>"
        }
      ]
    }
  ]
}
```

记下 "name”: "message"，我们将在下一节使用它。

<figure><img src="/files/3JIwl0YLUp7y3aTsA5q1" alt=""><figcaption></figcaption></figure>

#### 在 DApp 中增加模块发布命令

为了方便用户，如果模块不存在，我们可以在 DApp 界面上显示 `aptos move publish` 命令。为此，我们将使用 Aptos SDK 来检索账户模块，并寻找其中 `[module.abi.name](<http://module.abi.name>)` 等于 "Message "的模块（即我们在 Aptos Explorer 中看到的 `"name": "message"`）。

将以下代码更新到 `src/App.tsx` 中：

```tsx
import React from 'react';
import './App.css';
import { Types, AptosClient } from 'aptos';

// Create an AptosClient to interact with devnet.
const client = new AptosClient('<https://fullnode.devnet.aptoslabs.com/v1>');

/** Convert string to hex-encoded utf-8 bytes. */
function stringToHex(text: string) {
  const encoder = new TextEncoder();
  const encoded = encoder.encode(text);
  return Array.from(encoded, (i) => i.toString(16).padStart(2, "0")).join("");
}

function App() {
  // Retrieve aptos.account on initial render and store it.
  const [address, setAddress] = React.useState<string | null>(null);
  React.useEffect(() => {
    window.aptos.account().then((data : {address: string}) => setAddress(data.address));
  }, []);

  // Use the AptosClient to retrieve details about the account.
  const [account, setAccount] = React.useState<Types.AccountData | null>(null);
  React.useEffect(() => {
    if (!address) return;
    client.getAccount(address).then(setAccount);
  }, [address]);

  // Check for the module; show publish instructions if not present.
  const [modules, setModules] = React.useState<Types.MoveModuleBytecode[]>([]);
  React.useEffect(() => {
    if (!address) return;
    client.getAccountModules(address).then(setModules); 
  }, [address]);

  const hasModule = modules.some((m) => m.abi?.name === 'message');
  const publishInstructions = (
    <pre>
      Run this command to publish the module:
      <br />
      aptos move publish --package-dir /path/to/hello_blockchain/
      --named-addresses hello_blockchain={address}
    </pre>
  );
  const hasModuleText = (
    <pre>
      you have a module called "message" defined!
    </pre>
  )

  return (
    <div className="App">
      <p><code>address: { address }</code></p>
      <p><code>sequence: { account?.sequence_number }</code></p>
      {hasModule ? (
        hasModuleText
      ) : publishInstructions}
    </div>
  );
}

export default App;
```

此时，如果你的钱包地址下已经发布了名称为 `message` 的模块，你将会看到：

<figure><img src="/files/VEeQ4OqO6aQoRmt5Y5v2" alt=""><figcaption></figcaption></figure>

如果没有发布过对应名称的模块，则会提醒你使用以下命令去发布一个 Move 模块：

<figure><img src="/files/qIYuzdMDqnKGRTjvZXvj" alt=""><figcaption></figcaption></figure>

### 第 5 步：在链上写入消息

现在我们的 Move 模块已经发布到链上，我们已经可以在链上写入数据了！在这一步我们将使用模块中暴露的 `set_message` 函数来在链上写入一条消息

#### 一笔调用了 `set_message` 函数的交易

`set_message` 的签名如下所示：

```rust
public entry fun set_message(account: signer, message: string::String)
    acquires MessageHolder
```

为了调用这个函数，我们需要使用钱包提供的 `window.aptos` API来提交一个交易。具体来说，我们将创建一个 `entry_function_payload` 交易，看起来像这样。

```json
{
  type: "entry_function_payload",
  function: "<address>::Message::set_message",
  arguments: ["message>"],
  type_arguments: []
}
```

不需要提供 `account: signer` 参数。Aptos 钱包会自动提供它。

#### 使用 `window.aptos` API 提交 `set_message` 交易

现在我们明白了如何使用交易来调用 `set_message` 函数，接下来我们使用`window.aptos.signAndSubmitTransaction()` 从我们的 DApp 中调用这个函数。

我们将增加如下功能：

* 一个可以输入信息详情的 `<textarea>`
* 一个 `<button>`，用 \`\<textarea>的内容调用set\_message函数。

{% hint style="info" %}
💡 **译者注：**

后续的逻辑涉及到 `signAndSubmitTransaction` 操作，Petra 钱包做了权限的控制，因此，你必须要在 `window.aptos` 注入完成后，调用 `window.aptos.connect()` ，才能完成后续的操作。
{% endhint %}

更新 `src/App.tsx` 的代码：

```tsx
import React from 'react';
import './App.css';
import { Types, AptosClient } from 'aptos';

// Create an AptosClient to interact with devnet.
const client = new AptosClient('<https://fullnode.devnet.aptoslabs.com/v1>');

const connectToWallet = async() =>{
  const status = await(window as any).aptos.isConnected();
  if (!status){
    await window.aptos.connect();
  }
}

function App() {
  // Retrieve aptos.account on initial render and store it.
  const [address, setAddress] = React.useState<string | null>(null);
  React.useEffect(() => {
    connectToWallet().then(() => {
      window.aptos.account().then((data : {address: string}) => setAddress(data.address));
    })
  }, []);

  // Use the AptosClient to retrieve details about the account.
  const [account, setAccount] = React.useState<Types.AccountData | null>(null);

  // Check for the module; show publish instructions if not present.
  const [modules, setModules] = React.useState<Types.MoveModuleBytecode[]>([]);
  React.useEffect(() => {
    if (!address) return;
    client.getAccount(address).then(setAccount);
    client.getAccountModules(address).then(setModules); 
  }, [address]);

  const hasModule = modules.some((m) => m.abi?.name === 'message');
  const publishInstructions = (
    <pre>
      Run this command to publish the module:
      <br />
      aptos move publish --package-dir /path/to/hello_blockchain/
      --named-addresses hello_blockchain={address}
    </pre>
  );

  // Call set_message with the textarea value on submit.
  const ref = React.createRef<HTMLTextAreaElement>();
  const [isSaving, setIsSaving] = React.useState(false);
  const handleSubmit = async (e: any) => {
    e.preventDefault();
    if (!ref.current) return;

    const message = ref.current.value;
    const transaction = {
      type: "entry_function_payload",
      function: `${address}::message::set_message`,
      arguments: [message],
      type_arguments: [],
    };

    try {
      setIsSaving(true);
      await window.aptos.signAndSubmitTransaction(transaction);
    } finally {
      setIsSaving(false);
    }
  };

  return (
    <div className="App">
      <p><code>address: { address }</code></p>
      <p><code>sequence: { account?.sequence_number }</code></p>
      {hasModule ? (
        <form onSubmit={handleSubmit}>
          <textarea ref={ref} />
          <input disabled={isSaving} type="submit" />
        </form>
      ) : publishInstructions}
    </div>
  );
}

export default App;
```

运行你的 DApp 之后，你将看到以下的界面：

<figure><img src="/files/1U2UoaRzI7JKdAl0oVHz" alt=""><figcaption></figcaption></figure>

测试你的 DApp：

* 在 `<textarea>` 中输入一些信息并且点击 `提交` 按钮提交该表单
* 在 [Aptos Explorer](https://explorer.devnet.aptos.dev/) 中搜索你的账户地址，现在你会在账户资源下看到一个 MessageHolder 资源，上面有你刚刚提交的信息。

<figure><img src="/files/Rak4HRXDd22OU1HisZNa" alt=""><figcaption></figcaption></figure>

如果你没有看到，请尝试一个较短的信息。长信息可能会导致交易失败，因为长信息需要消耗更多的 gas 费用。

### 第 6 步：在 DApp 中展示刚刚设置的信息

现在我们已经创建了 `MessageHolder` 资源，可以使用 Aptos SDK 来获取它并显示消息。

#### 获取钱包地址下的信息

为了获取这条信息，我们将：

* 首先使用 `AptosClient.getAccountResources()` 函数获取该地址下的资源，并且将它缓存在前端的 state 中
* 接下来我们要寻找 `type` 为 `MessageHolder` 的资源，该类型的完整路径地址为 `$address::message::MessageHolder`，是 `$address::message` 模块的一部分，在我们的例子中，完整的路径地址为：

  ```bash
  0xaedd72868bc24fcc42eb481b874a1aadd0a648779aaacb69498b69c5c10fc47a::message::MessageHolder
  ```
* 我们将使用这个地址下的现值作为 DApp 的 `<textarea>` 的默认缺省值

将 `src/App.tsx` 代码更新为：

```tsx
import React from 'react';
import './App.css';
import { Types, AptosClient } from 'aptos';

// Create an AptosClient to interact with devnet.
const client = new AptosClient('<https://fullnode.devnet.aptoslabs.com/v1>');

const connectToWallet = async() =>{
  const status = await(window as any).aptos.isConnected();
  if (!status){
    await window.aptos.connect();
  }
}

function App() {
  // Retrieve aptos.account on initial render and store it.
  const [address, setAddress] = React.useState<string | null>(null);
  React.useEffect(() => {
    connectToWallet().then(() => {
      window.aptos.account().then((data : {address: string}) => setAddress(data.address));
    })
  }, []);

  // Use the AptosClient to retrieve details about the account.
  const [account, setAccount] = React.useState<Types.AccountData | null>(null);

  // Check for the module; show publish instructions if not present.
  const [modules, setModules] = React.useState<Types.MoveModuleBytecode[]>([]);
  React.useEffect(() => {
    if (!address) return;
    client.getAccount(address).then(setAccount);
    client.getAccountModules(address).then(setModules); 
  }, [address]);

  const hasModule = modules.some((m) => m.abi?.name === 'message');
  const publishInstructions = (
    <pre>
      Run this command to publish the module:
      <br />
      aptos move publish --package-dir /path/to/hello_blockchain/
      --named-addresses hello_blockchain={address}
    </pre>
  );

  const [resources, setResources] = React.useState<Types.MoveResource[]>([]);
  React.useEffect(() => {
    if (!address) return;
    client.getAccountResources(address).then(setResources);
  }, [address]);
  const resourceType = `${address}::message::MessageHolder`;
  const resource = resources.find((r) => r.type === resourceType);
  const data = resource?.data as {message: string} | undefined;
  const message = data?.message;

  // Call set_message with the textarea value on submit.
  const ref = React.createRef<HTMLTextAreaElement>();
  const [isSaving, setIsSaving] = React.useState(false);
  const handleSubmit = async (e: any) => {
    e.preventDefault();
    if (!ref.current) return;

    const message = ref.current.value;
    const transaction = {
      type: "entry_function_payload",
      function: `${address}::message::set_message`,
      arguments: [message],
      type_arguments: [],
    };

    try {
      setIsSaving(true);
      await window.aptos.signAndSubmitTransaction(transaction);
    } finally {
      setIsSaving(false);
    }
  };

  return (
    <div className="App">
      <p><code>address: { address }</code></p>
      <p><code>sequence: { account?.sequence_number }</code></p>
      {hasModule ? (
        <form onSubmit={handleSubmit}>
          <textarea ref={ref} defaultValue={message} />
          <input disabled={isSaving} type="submit" />
        </form>
      ) : publishInstructions}
    </div>
  );
}

export default App;
```

为了验证我们的代码功能：

* 刷新 DApp 前端页面，我们将会看到我们之前写入的信息被填进 `<textarea>` 中

<figure><img src="/files/TtqaLHO5LuKS9UrKFAiq" alt=""><figcaption></figcaption></figure>

* 更新 `<textarea>` 中的文字，并且提交表单，最后再刷新一下页面。你将看到 `<textarea>` 更新成了你刚刚提交的新信息

<figure><img src="/files/UVcLlEXe6WtLkd86UCTf" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/VgceUTGkOP6nQ8ZbDefr" alt=""><figcaption></figcaption></figure>

这也验证了你正在读取/写入 Aptos 区块链上的信息。

#### 展示其他账户上存储的信息

到目前为止，我们已经建立了一个 "只有一个用户的" DApp，你可以在你自己的账户上阅读和编写信息。接下来，我们将使其他人也能阅读信息，包括没有安装 Aptos 钱包的人。

我们将对其进行设置，以便进入地址尾缀为 `/<账户地址>` 的页面显示存储在 `<账户地址>` 下的信息（如果它存在的话）。

* 如果应用程序在 `/<账户地址>` 处加载，我们也将禁止编辑。
* 如果编辑功能被启用，我们将显示一个"获取公共URL"的链接，以便你可以分享你的信息。

更新 `src/App.tsx`:

```tsx
import React from 'react';
import './App.css';
import { Types, AptosClient } from 'aptos';

// Create an AptosClient to interact with devnet.
const client = new AptosClient('<https://fullnode.devnet.aptoslabs.com/v1>');

const connectToWallet = async() =>{
  const status = await(window as any).aptos.isConnected();
  if (!status){
    await window.aptos.connect();
  }
}

function App() {

  // Retrieve address first on URL
  const urlAddress = window.location.pathname.slice(1);
  const isEditable = !urlAddress;

  // Retrieve aptos.account on initial render and store it.
  const [address, setAddress] = React.useState<string | null>(null);
  React.useEffect(() => {
    if (urlAddress) {
      setAddress(urlAddress)
    } else {
      connectToWallet().then(() => {
        window.aptos.account().then((data : {address: string}) => setAddress(data.address));
      })
    }
  }, [urlAddress]);

  // Use the AptosClient to retrieve details about the account.
  const [account, setAccount] = React.useState<Types.AccountData | null>(null);

  // Check for the module; show publish instructions if not present.
  const [modules, setModules] = React.useState<Types.MoveModuleBytecode[]>([]);
  React.useEffect(() => {
    if (!address) return;
    client.getAccount(address).then(setAccount);
    client.getAccountModules(address).then(setModules); 
  }, [address]);

  const hasModule = modules.some((m) => m.abi?.name === 'message');
  const publishInstructions = (
    <pre>
      Run this command to publish the module:
      <br />
      aptos move publish --package-dir /path/to/hello_blockchain/
      --named-addresses hello_blockchain={address}
    </pre>
  );

  const [resources, setResources] = React.useState<Types.MoveResource[]>([]);
  React.useEffect(() => {
    if (!address) return;
    client.getAccountResources(address).then(setResources);
  }, [address]);
  const resourceType = `${address}::message::MessageHolder`;
  const resource = resources.find((r) => r.type === resourceType);
  const data = resource?.data as {message: string} | undefined;
  const message = data?.message;

  // Call set_message with the textarea value on submit.
  const ref = React.createRef<HTMLTextAreaElement>();
  const [isSaving, setIsSaving] = React.useState(false);
  const handleSubmit = async (e: any) => {
    e.preventDefault();
    if (!ref.current) return;

    const message = ref.current.value;
    const transaction = {
      type: "entry_function_payload",
      function: `${address}::message::set_message`,
      arguments: [message],
      type_arguments: [],
    };

    try {
      setIsSaving(true);
      await window.aptos.signAndSubmitTransaction(transaction);
    } finally {
      setIsSaving(false);
    }
  };

  return (
    <div className="App">
      <p><code>address: { address }</code></p>
      <p><code>sequence: { account?.sequence_number }</code></p>
      {hasModule ? (
        <form onSubmit={handleSubmit}>
          <textarea ref={ref} defaultValue={message} readOnly={!isEditable} disabled={!isEditable}/>
          {isEditable && (<input disabled={isSaving} type="submit" />)}
          {isEditable && (<a href={address!}>Get public URL</a>)}
        </form>
      ) : publishInstructions}
    </div>
  );
}

export default App;
```

我们可以看到对于加了账户地址后缀的 url，进入时 `<textarea>` 被禁用，且展示了当前账户地址下的 message 信息：

<figure><img src="/files/RQcMeTThk7lif9H2CxeZ" alt=""><figcaption></figcaption></figure>

而没有加账户地址后缀时，则依然可以输入信息，提交表单，修改链上当前钱包账户地址下的数据。

<figure><img src="/files/QyPchnaK1dMDoFTgZvlW" alt=""><figcaption></figcaption></figure>

至此，我们的 DApp 内容结束 🎉
