APTOS Developer’s Guide | Your First Dapp | Typescript

In this tutorial you will learn how to create a dapp on the Aptos blockchain. Usually a dapp consists of a user interface written in JavaScript that interacts with one or more Move modules.

In this tutorial, we will use the Move module HelloBlockchain described in Your First Move module, and focus on creating the user interface.

We’ll be using:

  • Aptos SDK.
  • Aptos Wallet, and
  • Aptos CLI to interact with the blockchain.

The end result will be a dapp that allows users to publish and share snippets of text on the Aptos blockchain.

The full source code for this tutorial is available here.

Prerequisites

Aptos Wallet.

Before starting this tutorial you need to install the Aptos Wallet extension.

After you install it:

  1. Open Wallet and click Create New Wallet. Then click Create account to create an Aptos account.
  2. Copy your private key. You will need it to set up the Aptos CLI in the next section.

NOTE
Make sure you have enough funds in your account for transactions by clicking the Faucet button.

Aptos CLI.

  1. Install the Aptos CLI.
  2. Run aptos init, and when it asks for your private key, paste the private key from the Aptos Wallet you copied earlier. This will initialize the Aptos CLI to use the same account used in the Aptos Wallet.
  3. Run aptos account list to make sure everything works.

Step 1: Create a single-page application

Now we will set up the front-end UI for our dapp. In this tutorial we will use [create-react-app](https://aptos.dev/tutorials/your-first-dapp#:~:text=We%20will%20use-,create%2Dreact%2Dapp,-to%20set%20up) to customize the app, but neither React nor create-react-app are required. You can use your favorite JavaScript framework.

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

Enter fullscreen mode Exit fullscreen mode

You now have a basic React application running in the browser.

Step 2: Integrate the Web3 Aptos Wallet API

Aptos Wallet provides the Web3 API for dapps in window.aptos. You can see how it works by opening the browser console and running await window.aptos.account(). It will display the address that corresponds to the account you created in Aptos Wallet.

Next we'll update our application to use this API to display the wallet account address.

Wait until window.aptos is defined

The first step in integrating with the window.aptos API is to wait for the window.onload event to occur.

Open the src/index.tsx file and modify the following code snippet:

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

In this:

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

This change ensures that the window.aptos API is initialized when the application is rendered (if we render too early, the Wallet extension might not have time to initialize the API yet, so the window.aptos is undefined).

(Optional) TypeScript configuration for window.aptos<###code>

If you use TypeScript, you can also tell the compiler that the window.aptos API exists. Add the following to src/index.tsx:

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

This allows us to use the window.aptos API without having to do (window as any).aptos.

Displaying window.aptos.account() in the application

Now our application is ready to use the window.aptos. API We change src/App.tsx to get the window.aptos.account() (wallet account) on initial display, save it in state, and then display it:

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

Refresh the page and you will see your account address.

Let's add some CSS

Then replace the content of 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

Step 3: Use SDK to retrieve data from the blockchain

The wallet is now integrated into our dapp. Next we'll integrate the Aptos SDK to retrieve data from the blockchain. We will use the Aptos SDK to get our account information and display that information on the page.

Add aptos to package.json.

Add the SDK to the project first:

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

You will now see "aptos": "^0.0.20" (or similar) in your package.json.

Create AptosClient.
Now we can import the SDK and create AptosClient to interact with the blockchain (technically it interacts with the REST API which interacts with the blockchain).

Since our wallet account is in devnet, we will set up AptosClient to interact with devnet. Add the following to 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

Now, in addition to displaying the account's address, the application will also display sequence_number of the account. This sequence_number is the sequence number of the next transaction to prevent transaction replay attacks. You will see this number increase as the account transactions are performed.

Step 4: Publish the Move module

Our dapp is now set up to read from the blockchain. The next step is to write to the blockchain. To do this, we will publish the Move module to our account.

The Move module provides a place to store this data. Specifically, we will use the HelloBlockchain<###code> module from your first Move module, which provides a MessageHolder resource that stores a string (called message).

Publishing the HelloBlockchain module with the Aptos CLI

We will be using the Aptos CLI to compile and publish the HelloBlockchain module.

  1. Download the hello_blockchain package.
  2. Then use aptos move publish (replace /path/to/hello_blockchain/ and <address>):
$ aptos move publish --package-dir /path/to/hello_blockchain/ --named-addresses HelloBlockchain=<address>
Enter fullscreen mode Exit fullscreen mode

For example:

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

The --named-addresses parameter replaces the named address of HelloBlockchain in HelloBlockchain.move with the specified address. For example, if we specify --named-addresses HelloBlockchain=0x5af503b5c379bd69f46f418430494975e1ef1fa57f422dd193cdad67dc139d532481, the following happens:

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

will become:

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

This allows you to publish a module for a given account (in this case our Wallet account, 0x5af503b5c379bd69f46184304975e1ef1fa57f422dd193cdad67dc139d532481).

Assuming your account has enough funds to perform the transaction, you can now publish the HelloBlockchain module to your account. If you refresh the application, you will see that the account sequence number has increased from 0 to 1.

You can also check that the module has been published by going into Aptos Explorer and finding your account. If you scroll down to the "Account Modules" section, you should see something like the following:

{
  "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

Make a note of "name": "Message", we'll use it in the next section.

Add instructions for publishing modules to the dapp

For the users' convenience, we can make the application display the aptos move publish command if the module does not exist. To do this, we'll use the Aptos SDK to get the account modules and look for one where module.abi.name is "Message" (i.e. "name": "Message" which we saw in Aptos Explorer).

Update 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

New users will be able to use this command to create a page for their account.

Step 5: Write the message in the blockchain

Now that the module is published, we are ready to use it to write the message in the blockchain. To do this we will use the set_message function opened by the module.

The transaction calling the set_message function

The caption for set_message is as follows:

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

To call this function, we have to use the window.aptos API provided by the wallet to send the transaction. Specifically, we will create a script_function_payload transaction that will look like this:

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

There is no need to specify the account: signer argument. Aptos provides it automatically.

However we have to specify the message_bytes argument: it is "<hex encoded utf-8 message>" in the transaction. We need a way to convert the JS string to this format. We can do this by using TextEncoder to convert to utf-8 bytes and then a one-line sentence to hexadecimal encode the bytes.

Add this function to 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

Using this function, our transaction payload becomes:

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

Use the window.aptos API to send a set_message transaction

Now that we understand how to use a transaction to call the set_message function, we will call this function from our application using window.aptos.signAndSubmitTransaction().

We will add:

  • Where <textarea> the user can enter a message, and

Update 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

To test:

  • Type something in <textarea> and submit the form.
  • Find your account in Aptos Explorer and now you will see MessageHolder resource under Account Resources with the message you wrote.

If you don't see it, try using a shorter message. Long messages can cause the transaction to fail because long messages take more gas.

Step 6: Displaying the message in dapp

Now that the MessageHolder resource has been created, we can use the Aptos SDK to fetch it and display the message.

To get a message about the status of a wallet account

To get the message we will do the following:

  • First, we'll use the AptosClient.getAccountResources() function to get the account resources and store them in state.
  • Then we will look for the one typewhich MessageHolder. The full type is $address::Message::MessageHolder, since it is part of the $address::Message module.

In our example it is:

0x5af503b5c379bd69f46184304975e1ef1fa57f422dd193cdad67dc139d532481::Message::MessageHolder
Enter fullscreen mode Exit fullscreen mode
  • We will use it for the <textarea> initial value.

Update 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

To test:

  • Refresh the page and see the message you wrote earlier.
  • Change the text, submit the form and refresh the page again. You will see that the page content has been updated with your new message.

This confirms that you are reading and writing messages on the Aptos blockchain.

Displaying messages from other accounts

At this point, we've created a "single-user" dapp where you can read and write messages on your account. Next we'll make it so that other people can read messages, including people who don't have Aptos Wallet installed.

We will set it up so that clicking on the URL /<account address> will display the message stored at /<account address> (if it exists).

  • If the application is loaded at /<account address>, we will also disable editing.
  • If editing is enabled, we'll show a "Get public URL" link so you can share your post

Update 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

This completes this tutorial.

Оцените статью
devanswers.ru
Добавить комментарий