# Content platform

## Endless CMS: A Step-by-Step Guide <a href="#endless-cms-a-step-by-step-guide" id="endless-cms-a-step-by-step-guide"></a>

This document provides a complete, step-by-step guide to developing, deploying, and interacting with a simple Content Management System (CMS) DApp on the Endless blockchain. We will cover:

1. **Developing and Deploying the Move Smart Contract.**
2. **Building a Rust CLI to Interact with the Deployed Contract.**

By the end of this guide, you will have a fully functional decentralized application and a clear understanding of the end-to-end development process.

***

### Part 1: The Move Smart Contract <a href="#part-1-the-move-smart-contract" id="part-1-the-move-smart-contract"></a>

In this section, we'll create, configure, and deploy the Move contract that powers our CMS.

#### Step 1.1: Initialize the Move Project <a href="#step-11-initialize-the-move-project" id="step-11-initialize-the-move-project"></a>

First, let's set up the project structure for our Move contract.

1. Open your terminal in the `endless-cms` directory.
2. Initialize a new Move package named `endless-cms`:

   ```bash
   # Make sure you are in the root project directory
   cd move
   endless move init --name endless-cms
   ```

   This command creates a standard Move project layout inside the `move` directory, including a `sources` folder for your contract code and a `Move.toml` manifest file.

#### Step 1.2: Create and Fund a Deployer Account <a href="#step-12-create-and-fund-a-deployer-account" id="step-12-create-and-fund-a-deployer-account"></a>

We need an account to deploy our contract. The `endless` CLI can create one for us.

1. Run the initialization command:

   ```bash
   endless init
   ```
2. Follow the prompts:

   * Choose `testnet` for the network.
   * Press `Enter` when asked for a private key to generate a new one.

   The CLI will generate a new account, save its configuration in `move/.endless/config.yaml`, and automatically fund it with testnet `EDS` tokens.
3. **IMPORTANT**: Copy the `account` address from the output. You will need it in the next step. It will look something like `EGiHscDkCe9Giv7kwv8ha7bk61woSic4xpJ66UMc4XrA`.

#### Step 1.3: Configure the Contract Manifest (`Move.toml`) <a href="#step-13-configure-the-contract-manifest-movetoml" id="step-13-configure-the-contract-manifest-movetoml"></a>

Now, we need to tell our contract where it will live and what other contracts it depends on.

1. Open the `move/Move.toml` file.
2. Add a named address for your contract under the `[addresses]` section. Use the account address you copied in the previous step. Let's call it `cms_address`.

   ```toml
   [addresses]
   cms_address = "EGiHscDkCe9Giv7kwv8ha7bk61woSic4xpJ66UMc4XrA" # <-- PASTE YOUR ADDRESS HERE
   ```
3. Add the `EndlessFramework` as a dependency under `[dependencies]`. This framework provides essential tools for building on Endless, like the `Object` model.

   ```toml
   [dependencies]
   EndlessFramework = { git = "https://github.com/endless-labs/endless-move-framework.git", subdir = "endless-framework", rev = "main" }
   ```

#### Step 1.4: Write the Smart Contract (`cms.move`) <a href="#step-14-write-the-smart-contract-cmsmove" id="step-14-write-the-smart-contract-cmsmove"></a>

Create a new file `move/sources/cms.move` and add the provided contract code.

*(For brevity, the full code is omitted here. Copy the content from the file provided in the prompt.)*

**Key Functions Explained:**

* `init_module(admin_signer: &signer)`: Called once upon deployment. It creates the `PostCounter` resource and stores it under the deployer's (admin's) account.
* `publish_post(admin_signer: &signer, author_signer: &signer, ...)`: This is a **multi-agent function**. It requires two signatures to execute:
  * `admin_signer`: To prove the caller has access to the `PostCounter` to assign a unique ID.
  * `author_signer`: To designate the post's owner. It creates a new `Post` as a separate `Object` on the chain.
* `edit_post(author_signer: &signer, ...)`: A single-signer function that allows the original author to modify a post's title and content. It verifies ownership by checking `post_ref.author == signer::address_of(author_signer)`.
* `delete_post(author_signer: &signer, ...)`: Allows the author to permanently delete their post. It uses `move_from` to remove the `Post` resource and `object::delete` to destroy the `Object` itself.
* `get_post(post_address: address)`: A read-only `#[view]` function. It doesn't require a signature or a transaction. It fetches post data from the chain and returns it in a `PostData` struct.

#### Step 1.5: Compile and Deploy <a href="#step-15-compile-and-deploy" id="step-15-compile-and-deploy"></a>

Now, let's get the contract on-chain.

1. **Compile the code:**

   ```bash
   endless move compile
   ```

   This command checks for errors and builds the bytecode.
2. **Publish to the testnet:**

   ```bash
   endless move publish
   ```

   ```bash
   Compiling, may take a little while to download git dependencies...
   INCLUDING DEPENDENCY EndlessFramework
   INCLUDING DEPENDENCY EndlessStdlib
   INCLUDING DEPENDENCY MoveStdlib
   BUILDING endless-cms
   package size 4639 bytes
   Do you want to submit a transaction for a range of [6900 - 10300] Veins at a gas unit price of 100 Veins? [yes/no] >
   yes
   transaction_hash:7ap6VL4AyqF4UqWHFhjox6Vup59VgXYirERh3SsqVDMJ
   gas_used:69
   gas_unit_price:100
   sender:c52e5477ea9cb1a6418008c87d8944dc387ffc150fa22e168e4c9e30f2887dc3
   sequence_number:4
   success:true
   timestamp_us:1751368521594869
   version:249142591
   vm_status:Executed successfully
   ```

   Confirm the transaction when prompted. After a few moments, the contract will be live! The output will show the transaction hash and confirm success. Your `cms_address` is now a published module.

***

### Part 2: The Rust Client <a href="#part-2-the-rust-client" id="part-2-the-rust-client"></a>

With the contract live, we need an application to talk to it. We'll build a Rust CLI.

#### Step 2.1: Initialize the Rust Project <a href="#step-21-initialize-the-rust-project" id="step-21-initialize-the-rust-project"></a>

1. Navigate to the root of your project (`endless-cms`).
2. Create a new Rust binary project.

   ```bash
   # cargo init endless-cms
   # This command should be run from the `endless-cms` parent directory first.
   # The provided src directory will be used
   ```

   *(Assuming the `src` directory is already set up as a cargo project)*

#### Step 2.2: Add Dependencies (`Cargo.toml`) <a href="#step-22-add-dependencies-cargotoml" id="step-22-add-dependencies-cargotoml"></a>

Open `Cargo.toml` and ensure the following dependencies are listed. They provide the tools for blockchain communication, command-line parsing, and data handling.

```toml
[package]
name = "endless-cms"
version = "0.1.0"
edition = "2024"

[dependencies]
anyhow = "1.0.71"
bcs = { git = "https://github.com/aptos-labs/bcs.git", rev = "d31fab9d81748e2594be5cd5cdf845786a30562d" }
clap = { version = "4.5.40", features = ["cargo"] }
serde = { version = "1.0.193", features = ["derive", "rc"] }
once_cell = "1.10.0"
url = { version = "2.4.0", features = ["serde"] }
tokio = { version = "1.35.1", features = ["full"] }
hex = "0.4.3"
rand_core = "0.5.1"
rand = "0.7.3"
serde_json = { version = "1.0.81", features = ["preserve_order", "arbitrary_precision"] }   

# endless sdk dependencies
endless-sdk = { git = "https://github.com/endless-labs/endless-rust-sdk" }

[patch.crates-io]
serde-reflection = { git = "https://github.com/aptos-labs/serde-reflection", rev = "73b6bbf748334b71ff6d7d09d06a29e3062ca075" }
merlin = { git = "https://github.com/aptos-labs/merlin" }
x25519-dalek = { git = "https://github.com/aptos-labs/x25519-dalek", branch = "zeroize_v1" }
```

The `[patch.crates-io]` section is crucial. It tells Cargo to use specific versions of dependencies from git repositories instead of the default versions from crates.io. This is often necessary when working with rapidly developing blockchain SDKs to ensure all components are compatible.

#### Step 2.3: The Rust Code <a href="#step-23-the-rust-code" id="step-23-the-rust-code"></a>

Your project has four key files in `src/`:

* `main.rs`: The entry point. Uses `clap` to define and parse CLI commands (`create-account`, `publish-post`, etc.).
* `cms.rs`: The core logic for interacting with the smart contract.
* `event.rs`: Defines Rust structs that match the Move events, allowing for easy deserialization.
* `post.rs`: Contains default post content for easier testing.

#### Step 2.4: How the Client Calls the Contract <a href="#step-24-how-the-client-calls-the-contract" id="step-24-how-the-client-calls-the-contract"></a>

This is the most critical part. The functions in `src/cms.rs` are responsible for all on-chain communication.

**IMPORTANT**: Before running the client, open `src/cms.rs` and update the `MODULE_ADDRESS` constant to your contract address from **Step 1.2**.

```rust
// src/cms.rs

// MAKE SURE THIS ADDRESS MATCHES YOUR DEPLOYED CONTRACT ADDRESS
const MODULE_ADDRESS: Lazy<AccountAddress> =
    Lazy::new(|| AccountAddress::from_str("EGiHscDkCe9Giv7kwv8ha7bk61woSic4xpJ66UMc4XrA").unwrap());
```

**1. Calling a View Function (e.g., `get_post`)**

Read-only calls are simple and don't cost gas.

```rust
// In src/cms.rs
pub async fn get_post(address: &str) -> Result<PostData> {
    let rest_client = Client::new(NODE_URL.clone());
    let post_address = AccountAddress::from_str(address).unwrap();

    // 1. Create a ViewRequest with the function ID and arguments
    let result = rest_client
        .view(
            &ViewRequest {
                function: GET_POST_VIEW_FUNCTION.clone(), // "address::CMS::get_post"
                type_arguments: vec![],
                arguments: vec![serde_json::Value::String(post_address.to_string())],
            },
            Option::None,
        )
        .await?;

    // 2. Deserialize the JSON response into the PostData struct
    let v = result.inner()[0].clone();
    let post_data: PostData = from_value(v)?;

    Ok(post_data)
}
```

**2. Calling a Single-Signer Function (e.g., `edit_post`)**

For transactions that require only one signature, the `HelperClient` simplifies the process.

```rust
// In src/cms.rs
pub async fn edit_post(...) -> Result<PostEditedEvent> {
    let helper_client = HelperClient::new(&rest_client);
    let author_signer = load_account(author).unwrap();
    // ...
    let args = vec![
        bcs::to_bytes(&post_address).unwrap(),
        bcs::to_bytes(&new_title).unwrap(),
        // ...
    ];

    // 1. The helper_client builds, signs, and submits the transaction
    let txn_hash = helper_client
        .entry_function(
            MODULE_ADDRESS.clone(),
            MODULE_NAME,
            "edit_post",
            &author_signer,
            args, // Arguments are BCS serialized
            vec![],
            None,
        )
        .await?;
    // ... wait for transaction and get event
}
```

**3. Calling a Multi-Agent Function (e.g., `publish_post`)**

This is the most complex interaction, requiring manual transaction construction.

```rust
// In src/cms.rs
pub async fn publish_post(...) -> Result<PostPublishedEvent> {
    let admin_signer = load_account(admin).unwrap();
    let author_signer = load_account(author).unwrap();
    // ...

    // 1. Build the basic transaction payload for the primary signer (admin)
    let txn = RawTransaction::new(
        admin_signer.address(),
        // ... sequence number, gas, etc.
        TransactionPayload::EntryFunction(EntryFunction::new(
            // ... module and function name
            vec![],
            vec![ // BCS serialized arguments
                bcs::to_bytes(title).unwrap(),
                bcs::to_bytes(content).unwrap(),
            ],
        )),
        // ...
    );

    // 2. Specify the other signers (author)
    let data: RawTransactionWithData =
        RawTransactionWithData::new_multi_agent(txn.clone(), vec![author_signer.address()]);

    // 3. Sign the transaction data with EACH private key
    let admin_sig = admin_signer.private_key().sign(&data).unwrap();
    let author_sig = author_signer.private_key().sign(&data).unwrap();

    // 4. Create an authenticator containing both signatures
    let admin_account_authenticator =
        AccountAuthenticator::ed25519(admin_signer.public_key().clone(), admin_sig);
    let author_account_authenticator =
        AccountAuthenticator::ed25519(author_signer.public_key().clone(), author_sig);

    let txn_auth = TransactionAuthenticator::multi_agent(
        admin_account_authenticator.clone(),
        vec![author_signer.address()],
        vec![author_account_authenticator],
    );

    // 5. Submit the fully signed transaction
    let signed_txn = SignedTransaction::new_with_authenticator(txn, txn_auth);
    let pending_transaction = rest_client.submit(&signed_txn).await?;
    // ... wait for transaction and get event
}
```

***

### Part 3: Putting It All Together <a href="#part-3-putting-it-all-together" id="part-3-putting-it-all-together"></a>

Now you can use the Rust CLI to interact with your live contract.

1. **Create User Accounts:** You need an admin (the same one that deployed the contract) and an author. Create JSON files for them.

   ```bash
   # Create a file named `admin.json` with the private key of your deployer account.
   # Then create a new author account:
   cargo run -- create-account --name author
   ```

   ```bash
   Finished `dev` profile [unoptimized + debuginfo] target(s) in 35.07s
   Running `target\debug\endless-cms.exe create-account --name author`
   Create author account 3ABb3G1gyPJEqpVWPBNSyhQ5JyXsyxw5mCsGELqa4TVr
   ```
2. **Fund the Author Account:**

   ```bash
   cargo run -- faucet-account --name author
   ```

   ```bash
   Finished `dev` profile [unoptimized + debuginfo] target(s) in 1.17s
   Running `target\debug\endless-cms.exe faucet-account --name author`
   Successfully fauceted account 0x2011925e9870a742c19bb793fc0172b3d7acc85410f47735048fdc0362b815f9 : 1000000000
   Fauceting account author:1000000000
   ```
3. **Publish a Post:**

   ```bash
   cargo run -- publish-post --admin admin --author author --title "Hello from Endless" --content "This is my first post on my decentralized CMS!"
   ```

   ```bash
   Finished `dev` profile [unoptimized + debuginfo] target(s) in 1.15s
    Running `target\debug\endless-cms.exe publish-post --admin admin --author author --title "Hello from Endless" --content "This is my first post on my decentralized CMS!"`
   Found matching event type: EGiHscDkCe9Giv7kwv8ha7bk61woSic4xpJ66UMc4XrA::CMS::PostPublishedEvent
   Publish post 'PostPublishedEvent { post_id: "1", author: "3ABb3G1gyPJEqpVWPBNSyhQ5JyXsyxw5mCsGELqa4TVr", post_address: "E7oERdbBYeFMPzu2MFUVaCRfEwxX6ELFcuZo9H4TXvsm", title: "Hello from Endless" }'
   ```

   The command will print the `PostPublishedEvent`, which includes the `post_address`. **Copy this address!**

   ```
   ## https://scan.endless.link/object/E7oERdbBYeFMPzu2MFUVaCRfEwxX6ELFcuZo9H4TXvsm/resources?network=testnet
   EGiHscDkCe9Giv7kwv8ha7bk61woSic4xpJ66UMc4XrA::CMS::Post
   {
       "author": "3ABb3G1gyPJEqpVWPBNSyhQ5JyXsyxw5mCsGELqa4TVr",
       "content": "This is my first post on my decentralized CMS!",
       "created_at": "1751369126",
       "del_ref": {
           "self": "E7oERdbBYeFMPzu2MFUVaCRfEwxX6ELFcuZo9H4TXvsm"
       },
       "post_id": "1",
       "title": "Hello from Endless",
       "updated_at": "1751369126"
   }
   ```
4. **Get the Post:**

   ```bash
   cargo run -- get-post --address E7oERdbBYeFMPzu2MFUVaCRfEwxX6ELFcuZo9H4TXvsm
   ```

   ```bash
   Finished `dev` profile [unoptimized + debuginfo] target(s) in 1.26s
   Running `target\debug\endless-cms.exe get-post --address E7oERdbBYeFMPzu2MFUVaCRfEwxX6ELFcuZo9H4TXvsm`
   Getting post with Address: E7oERdbBYeFMPzu2MFUVaCRfEwxX6ELFcuZo9H4TXvsm
   PostData { post_id: "1", author: 2011925e9870a742c19bb793fc0172b3d7acc85410f47735048fdc0362b815f9, title: "Hello from Endless", content: "This is my first post on my decentralized CMS!", created_at: "1751369126", updated_at: "1751369126" }
   ```
5. **Edit the Post:**

   ```bash
   cargo run -- edit-post --author author --address E7oERdbBYeFMPzu2MFUVaCRfEwxX6ELFcuZo9H4TXvsm --title "An Edited Title" --content "I have updated the content."
   ```

   ```bash
   Finished `dev` profile [unoptimized + debuginfo] target(s) in 1.20s
   Running `target\debug\endless-cms.exe edit-post --author author --address E7oERdbBYeFMPzu2MFUVaCRfEwxX6ELFcuZo9H4TXvsm --title "An Edited Title" --content "I have updated the content."`
   Found matching event type: EGiHscDkCe9Giv7kwv8ha7bk61woSic4xpJ66UMc4XrA::CMS::PostEditedEvent
   Edit post 'PostEditedEvent { post_id: "1", author: "3ABb3G1gyPJEqpVWPBNSyhQ5JyXsyxw5mCsGELqa4TVr", post_address: "E7oERdbBYeFMPzu2MFUVaCRfEwxX6ELFcuZo9H4TXvsm" }'
   ```

   Post's title and content is update!

   ```bash
   ## https://scan.endless.link/object/E7oERdbBYeFMPzu2MFUVaCRfEwxX6ELFcuZo9H4TXvsm/resources?network=testnet
   EGiHscDkCe9Giv7kwv8ha7bk61woSic4xpJ66UMc4XrA::CMS::Post
   {
       "author": "3ABb3G1gyPJEqpVWPBNSyhQ5JyXsyxw5mCsGELqa4TVr",
       "content": "I have updated the content.",
       "created_at": "1751369126",
       "del_ref": {
           "self": "E7oERdbBYeFMPzu2MFUVaCRfEwxX6ELFcuZo9H4TXvsm"
       },
       "post_id": "1",
       "title": "An Edited Title",
       "updated_at": "1751369443"
   }
   ```
6. **Delete the Post:**

   ```bash
   cargo run -- delete-post --author author --address E7oERdbBYeFMPzu2MFUVaCRfEwxX6ELFcuZo9H4TXvsm
   ```

   ```bash
   Finished `dev` profile [unoptimized + debuginfo] target(s) in 1.20s
   Running `target\debug\endless-cms.exe delete-post --author author --address E7oERdbBYeFMPzu2MFUVaCRfEwxX6ELFcuZo9H4TXvsm`
   Found matching event type: EGiHscDkCe9Giv7kwv8ha7bk61woSic4xpJ66UMc4XrA::CMS::PostDeletedEvent
   Delet post : PostDeletedEvent { post_id: "1", author: "3ABb3G1gyPJEqpVWPBNSyhQ5JyXsyxw5mCsGELqa4TVr", post_address: "E7oERdbBYeFMPzu2MFUVaCRfEwxX6ELFcuZo9H4TXvsm" }
   ```

   On <https://scan.endless.link/> E7oERdbBYeFMPzu2MFUVaCRfEwxX6ELFcuZo9H4TXvsm is deleted.

   ```
   ## https://scan.endless.link/object/E7oERdbBYeFMPzu2MFUVaCRfEwxX6ELFcuZo9H4TXvsm/resources?network=testnet
   Account not found: E7oERdbBYeFMPzu2MFUVaCRfEwxX6ELFcuZo9H4TXvsm. The account may still have tokens or objects associated.
   ```

Congratulations! You have successfully built, deployed, and interacted with a full-stack decentralized application on the Endless blockchain.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.endless.link/endless/devbuild/build/show-cases/content-platform.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
