Aptos: Publish a module to save and retrieve a message on-chain

Aptos uses its own terminology for widely-known Web3 entities. Smart contracts are called Modules and are written in the Move language. Modules are also not deployed but published on the Aptos chain.

The objective of this tutorial is to familiarize you with the Aptos network, the Move language and modules written in it. In the end of this tutorial, you will be able to publish, test, and interact with Move modules in Aptos.

Specifically, in this tutorial, you will:

  • Initialize an Aptos project using the Aptos CLI.
  • Publish a module on the Aptos testnet.
  • Interact with the module to save a message.
  • Use the Aptos REST API to retrieve the message.



To get from zero to publishing your string via the module to Aptos testnet, do the following:

  1. With Chainstack, create a public chain project.
  2. With Chainstack, join Aptos testnet.
  3. With Chainstack, access your Aptos node credentials.
  4. Set up your Martian wallet to work through the Chainstack Aptos node.
  5. Fund your account through the Aptos testnet faucet.
  6. Install the Aptos CLI.
  7. Create a Move project.
  8. Create and configure your Aptos project.
  9. Create a module in the Move language.
  10. Compile and test the Move module.
  11. Publish the Move module.
  12. Save and retrieve a message on the Aptos chain.


Create a public chain project

See Create a project.

Join the Aptos testnet

See Join a public network.

Get node access and credentials

See View node access and credentials.

Set up Martian wallet

See Aptos tooling: Martian wallet.

Fund your account

Your account needs to pay fees in testnet APT to publish the module and interact with it. Fund your account with the Aptos testnet faucet.

Install the Aptos CLI

You need the Aptos CLI to interact with your Move module. Set up the Aptos CLI.

Create a Move project

  1. In your project directory, create a Move project:

    aptos move init --name save-message

    where save-message β€” name of the package.

    This creates a sources directory and a Move.toml file.

  2. Open your Move.toml file and edit it to add [addresses] and [dev-addresses], where:

    • dev = "_" β€” your default Aptos account.
    • dev = "0xC0FFEE" β€” an alternative Aptos account for tests.


    name = 'save-message'
    version = '1.0.0'
    dev = "_"
    dev = "0xC0FFEE"
    git = 'https://github.com/aptos-labs/aptos-core.git'
    rev = 'main'
    subdir = 'aptos-move/framework/aptos-framework'


    Packages naming

    Note that packages have one-time names. If you want to re-publish the package, you must change its name.

Create and configure an Aptos project

  1. In your project directory, run aptos init > custom. This will start a configuration process, during which you need to set up your Chainstack endpoint and Martian wallet private key. Adding the private key will retrieve your Aptos public address automatically.

  2. Add your Aptos node endpoint deployed with Chainstack.

  3. At the faucet URL request, type skip since you have already funded your account on the previous step.

  4. Paste your Martian wallet private key to finish configuring your project. The key is used to send transactions and retrieve your public address. Example of a successful result:

    Aptos CLI is now set up for account ...4474 as profile default!  Run `aptos --help` for more information about commands
      "Result": "Success"

    As a result, you get a .aptos directory with a config.yaml file inside it. In config.yaml, you will find your project setup.

Create a Move module

In your Move project directory, navigate to the sources directory. Create your Move module file message.move which allows you to call the set_message function and save a string on-chain:

module dev::message {
    use std::error;
    use std::signer;
    use std::string;
    use aptos_framework::account;
    use aptos_framework::event;

    struct MessageHolder has key {
        message: string::String,
        message_change_events: event::EventHandle<MessageChangeEvent>,

    struct MessageChangeEvent has drop, store {
        from_message: string::String,
        to_message: string::String,

    /// There is no message present
    const ENO_MESSAGE: u64 = 0;

    public fun get_message(addr: address): string::String acquires MessageHolder {
        assert!(exists<MessageHolder>(addr), error::not_found(ENO_MESSAGE));

    public entry fun set_message(account: signer, message: string::String)
    acquires MessageHolder {
        let account_addr = signer::address_of(&account);
        if (!exists<MessageHolder>(account_addr)) {
            move_to(&account, MessageHolder {
                message_change_events: account::new_event_handle<MessageChangeEvent>(&account),
        } else {
            let old_message_holder = borrow_global_mut<MessageHolder>(account_addr);
            let from_message = *&old_message_holder.message;
            event::emit_event(&mut old_message_holder.message_change_events, MessageChangeEvent {
                to_message: copy message,
            old_message_holder.message = message;

    #[test(account = @0x1)]
    public entry fun sender_can_set_message(account: signer) acquires MessageHolder {
        let addr = signer::address_of(&account);
        set_message(account,  string::utf8(b"Hello Chainstack dev"));

          get_message(addr) == string::utf8(b"Hello Chainstack dev"),

Compile and test the Move module

  1. To compile your Move module, run:

    aptos move compile --named-addresses dev=default
  2. After the module compiled, run a build-in test which checks if the set_message and get_message functions work:

    aptos move test

Publish the Move module

  1. Publish your compiled and tested Move module by running:

    aptos move publish --named-addresses dev=default
  2. Type yes to confirm publishing the transaction on the Aptos chain.

    The module will publish and the terminal will return the module information. You can use the transaction hash to retrieve transaction details. To do so, run:

    curl --location --request GET 'YOUR_CHAINSTACK_ENDPOINT/v1/transactions/by_hash/0x815cecb01a962331ca34904653a26715e6cd8e631d2d1b7da17b593adda1ea65' \
    --header 'Content-Type: application/json'

    where YOUR_CHAINSTACK_ENDPOINT is your Aptos node endpoint you used earlier.

Save and retrieve a message on the Aptos chain

  1. To save a message on the Aptos chain, run:

    aptos move run --function-id 'default::message::set_message' --args 'string:Hello Chainstack dev'


    • run β€” a Move command to call functions
    • function-id β€” a function to call
    • args β€” arguments of the function
  2. Type yes to confirm publishing the transaction on the Aptos chain.

  3. Retrieve the published message via the REST API by running:

    curl --location --request GET 'YOUR_CHAINSTACK_ENDPOINT/v1/accounts/c0e0ce57eaf9680ae67252fb3126f25aa86bb098b05f7b72cf0cf0de57c72a7f/resource/0xc0e0ce57eaf9680ae67252fb3126f25aa86bb098b05f7b72cf0cf0de57c72a7f::message::MessageHolder'    \
    --header 'Content-Type: application/json'

    where YOUR_CHAINSTACK_ENDPOINT is your Aptos node endpoint you used earlier.

Successful response example:

    "type": "0xc0e0ce57eaf9680ae67252fb3126f25aa86bb098b05f7b72cf0cf0de57c72a7f::message::MessageHolder",
    "data": {
        "message": "Hello Chainstack dev",
        "message_change_events": {
            "counter": "0",
            "guid": {
                "id": {
                    "addr": "0xc0e0ce57eaf9680ae67252fb3126f25aa86bb098b05f7b72cf0cf0de57c72a7f",
                    "creation_num": "4"


This tutorial guided you through the basics of creating, publishing, and testing a simple module that saves a string on the Aptos chain.

About the author

Davide Zambiasi

πŸ₯‘ Developer Advocate @ Chainstack
πŸ› οΈ BUIDLs on EVM, The Graph protocol, and Starknet
πŸ’» Helping people understand Web3 and blockchain development
Davide Zambiasi | GitHub Davide Zambiasi | Twitter Davide Zambiasi | LinkedIN