DEV Community

EgorMajj
EgorMajj

Posted on • Edited on

Руководство для разработчиков APTOS | Ваше первое Dapp | Typescript

В этом руководстве вы узнаете, как создать dapp на блокчейне Aptos. Обычно dapp состоит из пользовательского интерфейса, написанного на JavaScript, который взаимодействует с одним или несколькими Move-модулями.

В этом руководстве мы будем использовать Move-модуль HelloBlockchain, описанный в разделе Ваш первый модуль Move, и сосредоточимся на создании пользовательского интерфейса.

Мы будем использовать:

Конечным результатом будет dapp, позволяющий пользователям публиковать и делиться фрагментами текста на блокчейне Aptos.

Полный исходный код этого руководства доступен здесь.

Необходимые условия

Aptos Wallet

Перед началом этого урока необходимо установить расширение Aptos Wallet.

После того как вы его установите:

  1. Откройте Кошелек и нажмите Создать новый кошелек. Затем нажмите Создать аккаунт, чтобы создать аккаунт Aptos.
  2. Скопируйте приватный ключ. Он понадобится вам для настройки Aptos CLI в следующем разделе.

ПРИМЕЧАНИЕ
Убедитесь, что на вашем счете достаточно средств для проведения транзакций, нажав кнопку Faucet.

Aptos CLI

  1. Установите Aptos CLI.
  2. Запустите aptos init, и когда он запросит ваш закрытый ключ, вставьте закрытый ключ из Aptos Wallet, который вы скопировали ранее. Это инициализирует Aptos CLI для использования той же учетной записи, которая используется в Aptos Wallet.
  3. Запустите aptos account list, чтобы убедиться, что все работает.

Шаг 1: Создайте одностраничное приложение

Теперь мы настроим внешний пользовательский интерфейс для нашего dapp. В этом руководстве мы будем использовать [create-react-app](https://aptos.dev/tutorials/your-first-dapp#:~:text=We%20will%20use-,create%2Dreact%2Dapp,-to%20set%20up) для настройки приложения, но ни React, ни create-react-app не являются обязательными. Вы можете использовать ваш любимый JavaScript фреймворк.

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

Enter fullscreen mode Exit fullscreen mode

Теперь у вас есть базовое приложение React, запущенное в браузере.

Шаг 2: Интеграция API Web3 Aptos Wallet

Aptos Wallet предоставляет Web3 API для dapps в window.aptos. Вы можете увидеть, как он работает, открыв консоль браузера и выполнив await window.aptos.account(). Он выведет адрес, соответствующий учетной записи, которую вы создали в Aptos Wallet.

Далее мы обновим наше приложение, чтобы использовать этот API для отображения адреса учетной записи кошелька.

Подождите, пока window.aptos будет определен

Первым шагом при интеграции с API window.aptos является задержка отображения приложения до наступления события window.onload.

Откройте файл src/index.tsx и измените следующий фрагмент кода:

root.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);
Enter fullscreen mode Exit fullscreen mode

в этом:

window.addEventListener('load', () => {
  root.render(
    <React.StrictMode>
      <App />
    </React.StrictMode>
  );
});
Enter fullscreen mode Exit fullscreen mode

Это изменение гарантирует, что API window.aptos будет инициализирован к моменту отображения приложения (если мы выполним отображение слишком рано, расширение Wallet, возможно, еще не успеет инициализировать API и, таким образом, window.aptos будет undefined).

(Необязательно) Настройка TypeScript для window.aptos

Если вы используете TypeScript, вы также можете сообщить компилятору о существовании API window.aptos. Добавьте следующее в src/index.tsx:

declare global {
  interface Window { aptos: any; }
}
Enter fullscreen mode Exit fullscreen mode

Это позволяет нам использовать API window.aptos без необходимости делать (window as any).aptos.

Отображение window.aptos.account() в приложении

Теперь наше приложение готово к использованию API window.aptos. Мы изменим src/App.tsx, чтобы получить значение window.aptos.account() ( учетная запись кошелька) при начальном отображении, сохранить его в состоянии, а затем отобразить:

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;
Enter fullscreen mode Exit fullscreen mode

Обновите страницу, и вы увидите адрес своей учетной записи.

Добавим некоторые CSS

Затем замените содержимое src/App.css:

a, input, textarea {
  display: block;
}

textarea {
  border: 0;
  min-height: 50vh;
  outline: 0;
  padding: 0;
  width: 100%;
}
Enter fullscreen mode Exit fullscreen mode

Шаг 3: Используйте SDK для получения данных из блокчейна

Кошелек теперь интегрирован в наш dapp. Далее мы интегрируем Aptos SDK для получения данных из блокчейна. Мы будем использовать Aptos SDK для получения информации о нашей учетной записи и отображения этой информации на странице.

Добавьте aptos в package.json

Сначала добавьте SDK в проект:

$ npm install --save aptos
Enter fullscreen mode Exit fullscreen mode

Теперь вы увидите "aptos": "^0.0.20" (или подобное) в вашем package.json.

Создайте AptosClient
Теперь мы можем импортировать SDK и создать AptosClient для взаимодействия с блокчейном (технически он взаимодействует с REST API, который взаимодействует с блокчейном).

Поскольку наша учетная запись кошелька находится в devnet, мы настроим AptosClient для взаимодействия с devnet. Добавьте следующее в src/App.tsx:

import { Types, AptosClient } from 'aptos';

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

function App() {
  // ...

  // 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 }</code></p>
      <p><code>{ account?.sequence_number }</code></p>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Теперь, помимо отображения адреса учетной записи, приложение будет также отображать sequence_number учетной записи. Этот sequence_number представляет собой порядковый номер следующей транзакции для предотвращения атак воспроизведения транзакций. Вы увидите, что этот номер увеличивается по мере совершения операций с учетной записью.

Шаг 4: Публикация модуля Move

Теперь наш dapp настроен на чтение из блокчейна. Следующим шагом будет запись в блокчейн. Для этого мы опубликуем модуль Move на нашей учетной записи.

Модуль Move предоставляет место для хранения этих данных. В частности, мы будем использовать модуль HelloBlockchain из вашего первого модуля Move, который предоставляет ресурс MessageHolder, хранящий строку (называемую message).

Публикация модуля HelloBlockchain с помощью Aptos CLI

Мы будем использовать Aptos CLI для компиляции и публикации модуля HelloBlockchain.

  1. Скачайте hello_blockchain package.
  2. Затем используйте команду aptos move publish (заменив /path/to/hello_blockchain/ и <address>):
$ aptos move publish --package-dir /path/to/hello_blockchain/ --named-addresses HelloBlockchain=<address>
Enter fullscreen mode Exit fullscreen mode

Например:

$ aptos move publish --package-dir ~/code/aptos-core/aptos-move/move-examples/hello_blockchain/ --named-addresses HelloBlockchain=0x5af503b5c379bd69f46184304975e1ef1fa57f422dd193cdad67dc139d532481
Enter fullscreen mode Exit fullscreen mode

Параметр --named-addresses заменяет именованный адрес HelloBlockchain в HelloBlockchain.move на указанный адрес. Например, если мы укажем --named-addresses HelloBlockchain=0x5af503b5c379bd69f46184304975e1ef1fa57f422dd193cdad67dc139d532481, то произойдет следующее:

module HelloBlockchain::Message {
Enter fullscreen mode Exit fullscreen mode

станет:

module 0x5af503b5c379bd69f46184304975e1ef1fa57f422dd193cdad67dc139d532481::Message {
Enter fullscreen mode Exit fullscreen mode

Это позволяет опубликовать модуль для данной учетной записи (в данном случае нашей учетной записи кошелька, 0x5af503b5c379bd69f46184304975e1ef1fa57f422dd193cdad67dc139d532481).

Предполагая, что на вашей учетной записи достаточно средств для выполнения транзакции, теперь вы можете опубликовать модуль HelloBlockchain в своей учетной записи. Если вы обновите приложение, то увидите, что порядковый номер счета увеличился с 0 до 1.

Вы также можете проверить, что модуль был опубликован, зайдя в Aptos Explorer и найдя свою учетную запись. Если вы прокрутите вниз до раздела "Модули учетной записи", вы должны увидеть что-то вроде следующего:

{
  "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>"
        }
      ]
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

Запишите "name": "Message", мы будем использовать его в следующем разделе.

Добавьте инструкции по публикации модулей в dapp

Для удобства пользователей мы можем заставить приложение отображать команду aptos move publish, если модуль не существует. Для этого мы воспользуемся Aptos SDK для получения модулей учетных записей и поищем тот, в котором module.abi.name равен "Message" (т.е. "name": "Message", который мы видели в Aptos Explorer).

Обновите src/App.tsx:

function App() {
  // ...

  // Check for the module; show publish instructions if not present.
  const [modules, setModules] = React.useState<Types.MoveModule[]>([]);
  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 HelloBlockchain={address}
    </pre>
  );

  return (
    <div className="App">
      {!hasModule && publishInstructions}
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Новые пользователи смогут использовать эту команду для создания страницы для своей учетной записи.

Шаг 5. Записать сообщение в блокчейне

Теперь, когда модуль опубликован, мы готовы использовать его для записи сообщения в блокчейн. Для этого мы будем использовать функцию set_message, открытую модулем.

Транзакция, вызывающая функцию set_message

Подпись для set_message выглядит следующим образом:

public(script) fun set_message(account: signer, message_bytes: vector<u8>)
Enter fullscreen mode Exit fullscreen mode

Чтобы вызвать эту функцию, нам нужно использовать API window.aptos, предоставляемый кошельком для отправки транзакции. В частности, мы создадим транзакцию script_function_payload, которая будет выглядеть следующим образом:

{
  type: "script_function_payload",
  function: "<address>::Message::set_message",
  arguments: ["<hex encoded utf-8 message>"],
  type_arguments: []
}
Enter fullscreen mode Exit fullscreen mode

Нет необходимости указывать аргумент account: signer. Aptos предоставляет его автоматически.

Однако нам необходимо указать аргумент message_bytes: это "<hex encoded utf-8 message>" в транзакции. Нам нужен способ преобразовать строку JS в этот формат. Мы можем сделать это, используя TextEncoder для преобразования в байты utf-8, а затем однострочное предложение для шестнадцатеричной кодировки байтов.

Добавьте эту функцию в src/App.tsx:

/** 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("");
}
Enter fullscreen mode Exit fullscreen mode

Используя эту функцию, наша полезная нагрузка транзакции становится:

{
  type: "script_function_payload",
  function: "<address>::Message::set_message",
  arguments: [stringToHex(message)],
  type_arguments: []
}
Enter fullscreen mode Exit fullscreen mode

Используйте API window.aptos для отправки транзакции set_message

Теперь, когда мы поняли, как использовать транзакцию для вызова функции set_message, мы вызовем эту функцию из нашего приложения с помощью window.aptos.signAndSubmitTransaction().

Мы добавим:

  • где <textarea> пользователь может ввести сообщение, и
  • <button>, который вызывает set_message функцию с содержимым файла <textarea>.

Обновите src/App.tsx:

function App() {
  // ...

  // 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: "script_function_payload",
      function: `${address}::Message::set_message`,
      arguments: [stringToHex(message)],
      type_arguments: [],
    };

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

  return (
    <div className="App">
      {hasModule ? (
        <form onSubmit={handleSubmit}>
          <textarea ref={ref} />
          <input disabled={isSaving} type="submit" />
        </form>
      ) : publishInstructions}
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Для проверки:

  • Введите что-нибудь в <textarea> и отправьте форму.
  • Найдите свою учетную запись в Aptos Explorer, и теперь вы увидите ресурс MessageHolder в разделе Account Resources с написанным вами message.

Если вы его не видите, попробуйте использовать более короткое сообщение. Длинные сообщения могут привести к сбою транзакции, поскольку длинные сообщения требуют больше газа.

Шаг 6. Отображение сообщения в dapp

Теперь, когда ресурс MessageHolder создан, мы можем использовать Aptos SDK для его получения и отображения сообщения.

Получить сообщение о состоянии учетной записи кошелька

Чтобы получить сообщение, мы сделаем следующее:

  • Сначала воспользуемся функцией AptosClient.getAccountResources() для получения ресурсов учетной записи и хранения их в состоянии.
  • Затем мы будем искать тот, type которого MessageHolder. Полный тип - $address::Message::MessageHolder, поскольку он является частью модуля $address::Message.

В нашем примере это:

0x5af503b5c379bd69f46184304975e1ef1fa57f422dd193cdad67dc139d532481::Message::MessageHolder
Enter fullscreen mode Exit fullscreen mode
  • Мы будем использовать его для начального значения <textarea>.

Обновите src/App.tsx:

function App() {
  // ...

  // Get the message from account resources.
  const [resources, setResources] = React.useState<Types.AccountResource[]>([]);
  React.useEffect(() => {
    if (!address) return;
    client.getAccountResources(address).then(setResourdces);
  }, [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;

  return (
    // ...
          <textarea ref={ref} defaultValue={message} />
    // ...
  );
}
Enter fullscreen mode Exit fullscreen mode

Для проверки:

  • Обновите страницу и увидите сообщение, которое вы написали ранее.
  • Измените текст, отправьте форму и снова обновите страницу. Вы увидите, что содержимое страницы было обновлено вашим новым сообщением.

Это подтверждает, что вы читаете и записываете сообщения на блокчейне Aptos.

Отображение сообщений с других учетных записей

На данный момент мы создали "однопользовательский" dapp, в котором вы можете читать и писать сообщения на своей учетной записи. Далее мы сделаем так, чтобы другие люди могли читать сообщения, включая тех, у кого не установлен Aptos Wallet.

Мы настроим его так, чтобы переход по URL /<account address> отображал сообщение, хранящееся по адресу /<account address> (если оно существует).

  • Если приложение загружено по адресу /<account address>, мы также отключим редактирование.
  • Если редактирование включено, мы покажем ссылку "Get public URL", чтобы вы могли поделиться своим сообщением

Обновите src/App.tsx:

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

  // ...

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

На этом мы завершаем данное руководство.

Top comments (0)