> ## Documentation Index
> Fetch the complete documentation index at: https://docs.initia.xyz/llms.txt
> Use this file to discover all available pages before exploring further.

# MemoBoard - Wasm

This tutorial will guide you through building an on-chain
guestbook called MemoBoard. Users can post public messages, and the
application prioritizes human-readable identity by resolving `.init` usernames.

By the end of this tutorial, you will have:

* Generated and verified a Rust smart contract for the guestbook.
* Deployed the contract to your live appchain.
* Scaffolded and connected a React frontend.
* Verified the on-chain functionality.

## Your Project Structure

The following steps will instruct your AI agent to create these directories
inside your `my-initia-project` folder:

```text wrap Project Structure theme={null}
my-initia-project/
├── memoboard/           # Rust smart contract project
└── memoboard-frontend/  # React frontend application
```

<Note>
  **Prerequisite:** Ensure you have a Wasm-compatible appchain running
  locally. If you haven't launched one yet, complete the
  [Set Up Your Appchain](../get-started) first.
</Note>

### Readiness Check

Before you start, verify that your local infrastructure is healthy.

```terminal title="Prompt: Check local infrastructure health" wrap theme={null}
Using the `initia-appchain-dev` skill, please verify that my appchain, executor bot, and relayer are running and that my Gas Station account has a balance.
```

## Step 1: Create and Unit Test the Smart Contract

Instruct your AI agent to create the Rust (Wasm) contract using the
`initia-appchain-dev` skill. Your AI agent will generate the contract and
automatically run unit tests to ensure the logic is sound.

```terminal title="Prompt: Create and test the MemoBoard contract" wrap theme={null}
Using the `initia-appchain-dev` skill, please create a Rust smart contract project for our MemoBoard in a new directory named `memoboard`. The contract should:
- Allow users to post a message (string).
- Store a list of all messages with the sender's address.
- Include a function to query all messages on the board.
Please also create and run unit tests to verify these features.
```

Your AI agent will generate the `memoboard` project and confirm that the Rust
tests pass.

<Accordion title="Manual Approach: The Rust Contract">
  If you prefer to see the Rust logic, here is a simplified but schema-accurate version of what your AI agent would generate. If doing this manually, save the following code in `src/contract.rs` inside your `memoboard` directory (ensure `src/lib.rs` exports the `contract` module).

  First create and enter the project directory:

  ```bash wrap theme={null}
  cargo new --lib --edition 2021 memoboard
  cd memoboard
  ```

  **Important:** Ensure your `Cargo.toml` includes the following `[lib]` section
  to correctly generate the Wasm binary:

  ```toml wrap Cargo.toml theme={null}
  [lib]
  crate-type = ["cdylib", "rlib"]
  ```

  Also ensure `Cargo.toml` includes these dependencies and features required by
  the example contract:

  ```toml wrap Cargo.toml theme={null}
  [features]
  library = []

  [dependencies]
  cosmwasm-schema = "2.0.4"
  cosmwasm-std = "2.0.4"
  cw-storage-plus = "2.0.0"
  thiserror = "1.0.31"
  ```

  ```rust wrap src/contract.rs theme={null}
  use cosmwasm_schema::{cw_serde, QueryResponses};
  use cosmwasm_std::{entry_point, to_json_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult, StdError};
  use cw_storage_plus::Item;
  use thiserror::Error;

  #[derive(Error, Debug)]
  pub enum ContractError {
      #[error("{0}")]
      Std(#[from] StdError),
  }

  #[cw_serde]
  pub struct Memo {
      pub sender: String,
      pub message: String,
  }

  pub const MESSAGES: Item<Vec<Memo>> = Item::new("messages");

  #[cw_serde]
  pub struct InstantiateMsg {}

  #[cw_serde]
  pub enum ExecuteMsg {
      PostMessage { message: String },
  }

  #[cw_serde]
  pub struct MessagesResponse {
      pub messages: Vec<Memo>,
  }

  #[cw_serde]
  #[derive(QueryResponses)]
  pub enum QueryMsg {
      #[returns(MessagesResponse)]
      GetMessages {},
  }

  #[cfg_attr(not(feature = "library"), entry_point)]
  pub fn instantiate(deps: DepsMut, _env: Env, _info: MessageInfo, _msg: InstantiateMsg) -> Result<Response, ContractError> {
      MESSAGES.save(deps.storage, &vec![])?;
      Ok(Response::new().add_attribute("action", "instantiate"))
  }

  #[cfg_attr(not(feature = "library"), entry_point)]
  pub fn execute(deps: DepsMut, _env: Env, info: MessageInfo, msg: ExecuteMsg) -> Result<Response, ContractError> {
      match msg {
          ExecuteMsg::PostMessage { message } => {
              let mut messages = MESSAGES.load(deps.storage)?;
              messages.push(Memo {
                  sender: info.sender.to_string(),
                  message: message.clone(),
              });
              MESSAGES.save(deps.storage, &messages)?;
              Ok(Response::new()
                  .add_attribute("action", "post_message")
                  .add_attribute("sender", info.sender)
                  .add_attribute("message", message))
          }
      }
  }

  #[cfg_attr(not(feature = "library"), entry_point)]
  pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult<Binary> {
      match msg {
          QueryMsg::GetMessages {} => {
              let messages = MESSAGES.load(deps.storage)?;
              to_json_binary(&MessagesResponse { messages })
          }
      }
  }

  #[cfg(test)]
  mod tests {
      use super::*;
      use cosmwasm_std::testing::{message_info, mock_dependencies, mock_env};
      use cosmwasm_std::Addr;
      use cosmwasm_std::from_json;

      #[test]
      fn test_post_and_query() {
          let mut deps = mock_dependencies();
          let env = mock_env();

          // Instantiate
          let info = message_info(&Addr::unchecked("creator"), &[]);
          instantiate(deps.as_mut(), env.clone(), info, InstantiateMsg {}).unwrap();

          // Post Message
          let info = message_info(&Addr::unchecked("user1"), &[]);
          let msg = ExecuteMsg::PostMessage { message: "Hello!".to_string() };
          execute(deps.as_mut(), env.clone(), info, msg).unwrap();

          // Query
          let res = query(deps.as_ref(), env, QueryMsg::GetMessages {}).unwrap();
          let val: MessagesResponse = from_json(&res).unwrap();
          assert_eq!(val.messages.len(), 1);
          assert_eq!(val.messages[0].message, "Hello!");
          assert_eq!(val.messages[0].sender, "user1");
      }
  }
  ```
</Accordion>

## Step 2: Deploy to Your Appchain

Now that the logic is verified, build and publish the contract to your appchain
using the Gas Station account.

```terminal title="Prompt: Deploy the MemoBoard contract" wrap theme={null}
Using the `initia-appchain-dev` skill, please build, publish, and instantiate the MemoBoard Rust contract located in the `memoboard` directory to my appchain using my Gas Station account, then return the deployed contract address.
```

<Accordion title="Manual Approach: Deploy via CLI">
  **1. Build and Store the Code:**

  Standard `cargo build` binaries often fail validation on-chain. For WasmVM
  deployment, it is strongly recommended to use the CosmWasm Optimizer.

  **Note for Apple Silicon (M1/M2/M3):** Use
  `cosmwasm/optimizer-arm64:0.17.0`.

  ```bash wrap Optimizer Build Command theme={null}
  # Run from the root of your project
  # Use 'optimizer' for x86_64 or 'optimizer-arm64' for Apple Silicon
  OPTIMIZER_IMAGE="cosmwasm/optimizer-arm64:0.17.0"

  docker run --rm -v "$(pwd)/memoboard":/code \
    --mount type=volume,source="$(basename "$(pwd)")_cache",target=/code/target \
    --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \
    $OPTIMIZER_IMAGE
  ```

  The optimized binary will be located in `./memoboard/artifacts/memoboard.wasm`.
  Now, store the code:

  ```bash wrap Store Wasm Code theme={null}
  # If the chain requires fees, add the --fees flag (e.g., --fees 1000000umin)
  minitiad tx wasm store ./memoboard/artifacts/memoboard.wasm \
    --from gas-station \
    --keyring-backend test \
    --chain-id <YOUR_APPCHAIN_ID> \
    --gas auto --gas-adjustment 1.4 --yes
  ```

  **2. Retrieve the Code ID and Instantiate:**

  If the `code_id` is not returned directly, wait for 5 seconds for indexing and
  then query the transaction hash:

  ```bash wrap Get Code ID theme={null}
  # Retrieve Code ID
  minitiad q tx <YOUR_TX_HASH> --output json | jq -r '.events[] | select(.type=="store_code") | .attributes[] | select(.key=="code_id") | .value'
  ```

  Then, instantiate the contract:

  ```bash wrap Instantiate Contract theme={null}
  minitiad tx wasm instantiate <YOUR_CODE_ID> '{}' \
    --label "memoboard" \
    --from gas-station \
    --keyring-backend test \
    --chain-id <YOUR_APPCHAIN_ID> \
    --gas auto --gas-adjustment 1.4 \
    --no-admin --yes
  ```

  **3. Retrieve the Contract Address:**

  Wait for 5 seconds for indexing and then query the transaction hash:

  ```bash wrap Get Contract Address theme={null}
  # Retrieve Contract Address
  minitiad q tx <YOUR_TX_HASH> --output json | jq -r '.events[] | select(.type=="instantiate") | .attributes[] | select(.key=="_contract_address") | .value'
  ```
</Accordion>

## Step 3: Smoke Test the Deployed Contract On-Chain

Before frontend integration, smoke test your deployed contract directly on
chain.

```terminal title="Prompt: Smoke test the MemoBoard contract" wrap theme={null}
Using the `initia-appchain-dev` skill, I want to smoke test our live MemoBoard contract. Using my Gas Station account on my appchain, please:
1. Post a message: "Hello from Initia!"
2. Query the board to see all messages.
```

<Accordion title="Manual Approach: On-Chain Interaction">
  Submit one message transaction, then query contract state to confirm it was
  persisted on chain.

  ```bash wrap Post Message theme={null}
  # If the chain requires fees, add the --fees flag (e.g., --fees 1000000umin)
  minitiad tx wasm execute <YOUR_CONTRACT_ADDRESS> '{"post_message":{"message":"Hello!"}}' \
    --from gas-station \
    --keyring-backend test \
    --chain-id <YOUR_APPCHAIN_ID> \
    --gas auto --gas-adjustment 1.4 --yes
  ```

  ```bash wrap Query Messages theme={null}
  sleep 5
  minitiad query wasm contract-state smart <YOUR_CONTRACT_ADDRESS> '{"get_messages":{}}'
  ```
</Accordion>

## Step 4: Create a Frontend

Let's create a UI to display and post messages.

**1. Scaffold the Frontend:**

```terminal title="Prompt: Scaffold the MemoBoard frontend" wrap theme={null}
Using the `initia-appchain-dev` skill, please scaffold a new Vite + React application named `memoboard-frontend` in my current directory using the `scaffold-frontend` script. Create a component named Board.jsx that displays a list of messages and a text input to post a new one.
```

**2. Connect to Appchain:**

```terminal title="Prompt: Connect the frontend to the MemoBoard contract" wrap theme={null}
Using the `initia-appchain-dev` skill, modify the Board.jsx component in the `memoboard-frontend` directory to connect to our MemoBoard contract on my appchain. Use the `@initia/interwovenkit-react` package for wallet connection and transaction signing.
```

<Accordion title="Manual Approach: Scaffold and Connect">
  If you prefer to set up the frontend manually, follow these steps:

  **1. Create the Project and Install Dependencies:**

  ```bash wrap theme={null}
  npm create vite@latest memoboard-frontend -- --template react
  cd memoboard-frontend
  npm install
  npm install @initia/interwovenkit-react wagmi viem @tanstack/react-query @initia/initia.js
  npm install --save-dev vite-plugin-node-polyfills
  npm install buffer util
  ```

  **2. Configure Vite Polyfills:** Update `vite.config.js` to include the Node
  polyfills:

  ```javascript wrap vite.config.js theme={null}
  import { defineConfig } from 'vite'
  import react from '@vitejs/plugin-react'
  import { nodePolyfills } from 'vite-plugin-node-polyfills'

  export default defineConfig({
    plugins: [
      react(),
      nodePolyfills({
        globals: {
          Buffer: true,
          process: true,
        },
      }),
    ],
  })
  ```

  Create the frontend files in this order so the imports line up cleanly:

  1. `vite.config.js`
  2. `.env`
  3. `src/main.jsx`
  4. `src/Board.css`
  5. `src/Board.jsx`
  6. `src/App.jsx`

  **3. Gather Runtime Values for Frontend Config:** Right before creating `.env`,
  collect the values you will use:

  ```bash wrap Gather Frontend Values theme={null}
  APPCHAIN_ID=$(curl -s http://localhost:26657/status | jq -r '.result.node_info.network')
  NATIVE_DENOM=$(minitiad q bank denoms-metadata --output json | jq -r '.metadatas[0].base // empty')
  [ -z "$NATIVE_DENOM" ] && NATIVE_DENOM=$(minitiad q bank total --output json | jq -r '.supply[0].denom')
  NATIVE_SYMBOL=$(minitiad q bank denoms-metadata --output json | jq -r '.metadatas[0].symbol // empty')
  [ -z "$NATIVE_SYMBOL" ] && NATIVE_SYMBOL="$NATIVE_DENOM"

  # Use the contract address you retrieved in Step 2.
  MEMOBOARD_CONTRACT_ADDRESS=<YOUR_CONTRACT_ADDRESS>

  echo "APPCHAIN_ID=$APPCHAIN_ID"
  echo "NATIVE_DENOM=$NATIVE_DENOM"
  echo "NATIVE_SYMBOL=$NATIVE_SYMBOL"
  echo "MEMOBOARD_CONTRACT_ADDRESS=$MEMOBOARD_CONTRACT_ADDRESS"
  ```

  **4. Add Runtime Configuration:** Create a `.env` file in `memoboard-frontend`
  so the chain metadata, endpoints, and live contract address are explicit:

  ```bash wrap .env theme={null}
  VITE_APPCHAIN_ID=$APPCHAIN_ID
  VITE_CHAIN_NAME=social
  VITE_CHAIN_PRETTY_NAME=Social 1
  VITE_INITIA_RPC_URL=http://localhost:26657
  VITE_INITIA_REST_URL=http://localhost:1317
  VITE_INITIA_INDEXER_URL=http://localhost:8080
  VITE_INITIA_JSON_RPC_URL=http://localhost:8545
  VITE_NATIVE_DENOM=$NATIVE_DENOM
  VITE_NATIVE_SYMBOL=$NATIVE_SYMBOL
  VITE_NATIVE_DECIMALS=6
  VITE_MEMOBOARD_CONTRACT_ADDRESS=$MEMOBOARD_CONTRACT_ADDRESS
  ```

  **5. Set up Providers in `main.jsx`:**

  ```javascript wrap src/main.jsx theme={null}
  import { Buffer } from 'buffer'
  window.Buffer = Buffer
  window.process = { env: { NODE_ENV: 'development' } }

  import React from 'react'
  import ReactDOM from 'react-dom/client'
  import '@initia/interwovenkit-react/styles.css'
  import {
    injectStyles,
    InterwovenKitProvider,
    TESTNET,
  } from '@initia/interwovenkit-react'
  import InterwovenKitStyles from '@initia/interwovenkit-react/styles.js'
  import { WagmiProvider, createConfig, http } from 'wagmi'
  import { mainnet } from 'wagmi/chains'
  import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
  import App from './App.jsx'

  // Inject styles for the InterwovenKit drawer
  injectStyles(InterwovenKitStyles)

  const queryClient = new QueryClient()
  const wagmiConfig = createConfig({
    chains: [mainnet],
    transports: { [mainnet.id]: http() },
  })

  const customChain = {
    chain_id: import.meta.env.VITE_APPCHAIN_ID,
    chain_name: import.meta.env.VITE_CHAIN_NAME,
    pretty_name: import.meta.env.VITE_CHAIN_PRETTY_NAME,
    network_type: 'testnet',
    bech32_prefix: 'init',
    logo_URIs: {
      png: 'https://raw.githubusercontent.com/initia-labs/initia-registry/main/testnets/initia/images/initia.png',
      svg: 'https://raw.githubusercontent.com/initia-labs/initia-registry/main/testnets/initia/images/initia.svg',
    },
    apis: {
      rpc: [{ address: import.meta.env.VITE_INITIA_RPC_URL }],
      rest: [{ address: import.meta.env.VITE_INITIA_REST_URL }],
      indexer: [{ address: import.meta.env.VITE_INITIA_INDEXER_URL }], // Placeholder REQUIRED for stability
      'json-rpc': [{ address: import.meta.env.VITE_INITIA_JSON_RPC_URL }],
    },
    fees: {
      fee_tokens: [
        {
          denom: import.meta.env.VITE_NATIVE_DENOM,
          fixed_min_gas_price: 0,
          low_gas_price: 0,
          average_gas_price: 0,
          high_gas_price: 0,
        },
      ],
    },
    staking: {
      staking_tokens: [{ denom: import.meta.env.VITE_NATIVE_DENOM }],
    },
    metadata: {
      minitia: { type: 'miniwasm' },
      is_l1: false, // REQUIRED for local appchains
    },
    native_assets: [
      {
        denom: import.meta.env.VITE_NATIVE_DENOM,
        name: 'Minitia',
        symbol: import.meta.env.VITE_NATIVE_SYMBOL,
        decimals: Number(import.meta.env.VITE_NATIVE_DECIMALS ?? 6),
      },
    ],
  }

  ReactDOM.createRoot(document.getElementById('root')).render(
    <React.StrictMode>
      <WagmiProvider config={wagmiConfig}>
        <QueryClientProvider client={queryClient}>
          <InterwovenKitProvider
            {...TESTNET}
            defaultChainId={customChain.chain_id}
            customChain={customChain}
            customChains={[customChain]}
          >
            <App />
          </InterwovenKitProvider>
        </QueryClientProvider>
      </WagmiProvider>
    </React.StrictMode>,
  )
  ```

  **6. Create the `Board.jsx` Component:** Create `src/Board.jsx` and
  `src/Board.css` with the following content:

  **src/Board.css:**

  ```css wrap src/Board.css theme={null}
  .board-container {
    max-width: 600px;
    margin: 40px auto;
    padding: 32px;
    font-family: sans-serif;
  }
  .board-title {
    font-size: 2.5rem;
    font-weight: 800;
    margin-bottom: 24px;
    text-align: center;
  }
  .section-header {
    font-size: 0.85rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.1em;
    color: #888;
    margin-bottom: 16px;
    display: block;
  }
  .auth-section {
    display: flex;
    justify-content: center;
    margin-bottom: 24px;
  }
  .wallet-info {
    background: #f0f0f0;
    padding: 12px 16px;
    border-radius: 12px;
    display: flex;
    justify-content: space-between;
    align-items: center;
    width: 100%;
  }
  .message-card {
    background: white;
    padding: 16px;
    border-radius: 12px;
    border: 1px solid #eee;
    margin-bottom: 16px;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.02);
  }
  .message-sender {
    font-family: monospace;
    font-size: 0.75rem;
    color: #999;
  }
  .message-content {
    margin-top: 8px;
    font-size: 1.1rem;
  }
  .input-group {
    display: flex;
    gap: 12px;
    margin-top: 16px;
  }
  .memo-input {
    flex: 1;
    padding: 12px 16px;
    border-radius: 8px;
    border: 1px solid #ddd;
    outline: none;
  }
  .btn-primary {
    background: #000;
    color: #fff;
    border: none;
    padding: 12px 24px;
    border-radius: 8px;
    font-weight: 600;
    cursor: pointer;
  }
  ```

  **src/Board.jsx:**

  ```javascript wrap src/Board.jsx theme={null}
  import React, { useState, useEffect } from 'react'
  import { useInterwovenKit } from '@initia/interwovenkit-react'
  import { RESTClient } from '@initia/initia.js'
  import './Board.css'

  const CHAIN_ID = import.meta.env.VITE_APPCHAIN_ID
  const MEMO_BOARD_ADDRESS = import.meta.env.VITE_MEMOBOARD_CONTRACT_ADDRESS
  const rest = new RESTClient(import.meta.env.VITE_INITIA_REST_URL)

  const Board = () => {
    const [messages, setMessages] = useState([])
    const [content, setContent] = useState('')
    const { initiaAddress, requestTxSync } = useInterwovenKit()

    const truncate = (addr) => `${addr.slice(0, 10)}...${addr.slice(-6)}`

    const fetchMessages = async () => {
      try {
        // Wasm queries via REST MUST be base64 encoded
        const queryData = Buffer.from(
          JSON.stringify({ get_messages: {} }),
        ).toString('base64')
        const res = await rest.wasm.smartContractState(
          MEMO_BOARD_ADDRESS,
          queryData,
        )
        setMessages([...(res?.messages ?? [])].reverse())
      } catch (e) {
        console.error('Failed to fetch messages', e)
      }
    }

    useEffect(() => {
      fetchMessages()
      // Poll for new messages every 5 seconds
      const interval = setInterval(fetchMessages, 5000)
      return () => clearInterval(interval)
    }, [])

    const handlePostMessage = async () => {
      if (!content || !initiaAddress || !MEMO_BOARD_ADDRESS) return

      // Encode execute msg to Uint8Array (bytes)
      const msg = new TextEncoder().encode(
        JSON.stringify({ post_message: { message: content } }),
      )

      try {
        // Use requestTxSync for local development transactions
        // ALWAYS include CHAIN_ID to avoid RPC routing errors
        await requestTxSync({
          chainId: CHAIN_ID,
          messages: [
            {
              typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract',
              value: {
                sender: initiaAddress,
                contract: MEMO_BOARD_ADDRESS,
                msg,
                funds: [],
              },
            },
          ],
        })
        setContent('')
        // Small delay to allow block inclusion
        setTimeout(fetchMessages, 2000)
      } catch (e) {
        console.error('Transaction failed', e)
      }
    }

    return (
      <div className="board-container">
        <h1 className="board-title">MemoBoard</h1>
        {!initiaAddress && (
          <div
            className="message-card"
            style={{ marginBottom: 24, textAlign: 'center', color: '#666' }}
          >
            Connect your wallet from the app header to post a memo.
          </div>
        )}
        {initiaAddress && (
          <div style={{ marginBottom: '32px' }}>
            <h3 className="section-header">Post a Memo</h3>
            <div className="input-group">
              <input
                className="memo-input"
                value={content}
                onChange={(e) => setContent(e.target.value)}
                placeholder="Write a memo..."
              />
              <button onClick={handlePostMessage} className="btn-primary">
                Post Message
              </button>
            </div>
          </div>
        )}
        <div className="messages-list">
          <h3 className="section-header">Board Feed</h3>
          {messages.map((m, i) => (
            <div key={i} className="message-card">
              <div className="message-sender">{truncate(m.sender)}</div>
              <div className="message-content">{m.message}</div>
            </div>
          ))}
        </div>
      </div>
    )
  }

  export default Board
  ```

  **7. Create `App.jsx`:** Create the app shell that renders the board and owns the wallet button:

  ```javascript wrap src/App.jsx theme={null}
  import React from 'react'
  import { useInterwovenKit } from '@initia/interwovenkit-react'
  import Board from './Board.jsx'

  function shortenAddress(addr) {
    if (!addr) return ''
    return `${addr.slice(0, 8)}...${addr.slice(-4)}`
  }

  function App() {
    const { initiaAddress, openConnect, openWallet } = useInterwovenKit()

    return (
      <div>
        <header
          style={{
            maxWidth: 600,
            margin: '40px auto 0',
            display: 'flex',
            justifyContent: 'space-between',
            alignItems: 'center',
            gap: 16,
          }}
        >
          <div>
            <p style={{ margin: 0, color: '#666', fontSize: 12, fontWeight: 700 }}>
              Initia Wasm Appchain
            </p>
            <h1 style={{ margin: '8px 0 0' }}>MemoBoard</h1>
          </div>

          {!initiaAddress ? (
            <button onClick={openConnect} className="btn-primary">
              Connect Wallet
            </button>
          ) : (
            <button onClick={openWallet} className="btn-primary">
              {shortenAddress(initiaAddress)}
            </button>
          )}
        </header>

        <Board />
      </div>
    )
  }

  export default App
  ```
</Accordion>

## Step 5: Wallet Funding and UI Verification

Ask your AI agent to fund your browser wallet, then verify frontend behavior
manually in the browser:

1. Start the frontend:

```bash wrap Start Vite Dev Server theme={null}
cd memoboard-frontend
npm run dev
```

Check the **browser console** if you encounter issues.

2. Open your browser wallet and copy your address (`init1...`).
3. Give this prompt to your AI agent, replacing `<YOUR_WALLET_ADDRESS>` with the address you just copied:

```terminal wrap theme={null}
Using the `initia-appchain-dev` skill, please fund my wallet address <YOUR_WALLET_ADDRESS> with 1 INIT on L1 and 100 of my appchain's native tokens on L2.
```

4. Connect your wallet in `memoboard-frontend`.
5. Post a message from the UI.
6. Confirm it appears in the message list after refresh.
7. If your UI supports it, verify sender identity rendering matches expectations.

If you get stuck, see the [Debugging Workflow guide](../builder-guide#debugging-workflow).

# 🪢 Native Feature: Initia Usernames

To make your MemoBoard natively integrated with the Initia stack, you can
replace long, complex addresses with human-readable Initia Usernames (e.g.,
`vitalik.init`).

## Step 6: Register Your .init Name

Before updating your code, you should register a `primary username` for your
wallet on the Initia testnet.

1. Navigate to
   [app.testnet.initia.xyz/usernames](https://app.testnet.initia.xyz/usernames)
   and connect your browser wallet.
2. In the `Find a username` search box, enter your desired name. If it is
   available, you will see a green `Available` checkmark.
3. Crucial: Ensure the `Set as primary name` checkbox is selected.
4. Click `Register` and approve the transaction. Once resolved, your new
   `.init` name will appear in the top-right corner of the Initia App.

## Step 7: Update the Frontend

Your AI agent knows how to integrate Initia Usernames. Simply ask it to
update your board.

```terminal title="Prompt: Add Initia username support" wrap theme={null}
Using the `initia-appchain-dev` skill, please add Initia username support to my MemoBoard.
```

<Accordion title="Manual Approach: Username Resolution">
  The `useInterwovenKit()` hook provides the `username` for the currently connected wallet, and `useUsernameQuery(address)` resolves usernames for other sender addresses. This requires `@initia/interwovenkit-react` `2.4.6` or newer. To implement this, update your wallet button component (for example `src/App.jsx`) and your board message list like this:

  ```tsx wrap theme={null}
  import { useInterwovenKit, useUsernameQuery } from '@initia/interwovenkit-react'

  function MessageRow({ message }) {
    const { initiaAddress, username } = useInterwovenKit()
    const { data: senderUsername } = useUsernameQuery(message.sender)
    const senderLabel =
      message.sender === initiaAddress
        ? username
          ? username
          : truncate(message.sender)
        : senderUsername
          ? senderUsername
          : truncate(message.sender)

    return (
      <div className="message-sender">
        {senderLabel}
      </div>
    )
  }

  // 1. Extract username from the hook
  const { initiaAddress, username, openConnect, openWallet } = useInterwovenKit()

  // 2. Update the connected wallet button to show the username
  <div className="wallet-info">
    <button
      onClick={openWallet}
      style={{ background: 'none', border: 'none', cursor: 'pointer' }}
    >
      {username ? username : truncate(initiaAddress)}
    </button>
  </div>

  // 3. Update the feed rows to resolve usernames for sender addresses
  messages.map((message, index) => (
    <MessageRow key={`${message.sender}-${index}`} message={message} />
  ))
  ```

  <Warning>
    **Hook Placement:** Keep `useUsernameQuery(address)` inside a child row component like `MessageRow`. Do not call it directly inside a parent component's `.map()` callback.
  </Warning>
</Accordion>

If you want the complete finished frontend after applying the manual steps
above, use the consolidated reference below.

<Accordion title="Manual Approach: Final Frontend Reference">
  If you want a single copyable end-state after completing the manual steps above, use this consolidated reference. It combines:

  * live Wasm contract query and execute flow,
  * wallet connect/display via `App.jsx`,
  * runtime config from `.env`, and
  * Initia username support for the connected wallet and sender rows.

  **src/Board.jsx**

  ```jsx wrap src/Board.jsx theme={null}
  import React, { useEffect, useMemo, useState } from 'react'
  import { RESTClient } from '@initia/initia.js'
  import { useInterwovenKit, useUsernameQuery } from '@initia/interwovenkit-react'
  import './Board.css'

  const CHAIN_ID = import.meta.env.VITE_APPCHAIN_ID
  const REST_URL = import.meta.env.VITE_INITIA_REST_URL
  const CONTRACT_ADDRESS = import.meta.env.VITE_MEMOBOARD_CONTRACT_ADDRESS

  function truncate(value) {
    if (!value) return ''
    if (value.length < 18) return value
    return `${value.slice(0, 10)}...${value.slice(-6)}`
  }

  function MessageRow({ message, index, initiaAddress, username }) {
    const { data: senderUsername } = useUsernameQuery(message.sender)

    const senderLabel =
      message.sender === initiaAddress
        ? username
          ? username
          : truncate(message.sender)
        : senderUsername
          ? senderUsername
          : truncate(message.sender)

    return (
      <div className="message-card">
        <div className="message-sender">{senderLabel}</div>
        <div className="message-content">{message.message}</div>
        <div
          style={{
            marginTop: 8,
            fontSize: 12,
            color: '#999',
          }}
        >
          Message #{index + 1}
        </div>
      </div>
    )
  }

  export default function Board() {
    const { initiaAddress, username, requestTxSync } = useInterwovenKit()
    const [messages, setMessages] = useState([])
    const [draft, setDraft] = useState('')
    const [loading, setLoading] = useState(true)
    const [posting, setPosting] = useState(false)
    const [error, setError] = useState('')

    const rest = useMemo(() => new RESTClient(REST_URL), [])

    const fetchMessages = async () => {
      if (!CONTRACT_ADDRESS) {
        setError('Missing VITE_MEMOBOARD_CONTRACT_ADDRESS in .env')
        setLoading(false)
        return
      }

      setLoading(true)
      try {
        const queryData = Buffer.from(
          JSON.stringify({ get_messages: {} }),
        ).toString('base64')
        const res = await rest.wasm.smartContractState(CONTRACT_ADDRESS, queryData)
        setMessages(Array.isArray(res.messages) ? res.messages : [])
        setError('')
      } catch (e) {
        console.error('Failed to fetch messages', e)
        setError('Failed to load board messages')
      } finally {
        setLoading(false)
      }
    }

    useEffect(() => {
      fetchMessages()
    }, [])

    const handlePostMessage = async (event) => {
      event.preventDefault()

      const trimmed = draft.trim()
      if (!trimmed || !initiaAddress || !CONTRACT_ADDRESS) return

      setPosting(true)
      setError('')

      try {
        const msg = new TextEncoder().encode(
          JSON.stringify({ post_message: { message: trimmed } }),
        )

        await requestTxSync({
          chainId: CHAIN_ID,
          messages: [
            {
              typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract',
              value: {
                sender: initiaAddress,
                contract: CONTRACT_ADDRESS,
                msg,
                funds: [],
              },
            },
          ],
        })

        setDraft('')
        await new Promise((resolve) => setTimeout(resolve, 1200))
        await fetchMessages()
      } catch (e) {
        console.error('Transaction failed', e)
        setError('Posting the message failed')
      } finally {
        setPosting(false)
      }
    }

    return (
      <div className="board-container">
        <h1 className="board-title">MemoBoard</h1>

        {!initiaAddress && (
          <div
            className="message-card"
            style={{ marginBottom: 24, textAlign: 'center', color: '#666' }}
          >
            Connect your wallet from the app header to post a memo.
          </div>
        )}

        {initiaAddress && (
          <div style={{ marginBottom: 32 }}>
            <h3 className="section-header">Post a Memo</h3>
            <p style={{ color: '#777', marginBottom: 12 }}>
              Posting as {username ? username : truncate(initiaAddress)}
            </p>
            <form className="input-group" onSubmit={handlePostMessage}>
              <input
                className="memo-input"
                value={draft}
                onChange={(e) => setDraft(e.target.value)}
                placeholder="Write a memo..."
              />
              <button
                type="submit"
                className="btn-primary"
                disabled={posting || !draft.trim()}
              >
                {posting ? 'Posting...' : 'Post Message'}
              </button>
            </form>
          </div>
        )}

        <div className="messages-list">
          <h3 className="section-header">Board Feed</h3>

          {error && (
            <div className="message-card" style={{ color: '#b42318' }}>
              {error}
            </div>
          )}

          {loading ? (
            <div className="message-card">Loading board state...</div>
          ) : messages.length === 0 ? (
            <div className="message-card">No messages yet.</div>
          ) : (
            messages.map((message, index) => (
              <MessageRow
                key={`${message.sender}-${index}`}
                message={message}
                index={index}
                initiaAddress={initiaAddress}
                username={username}
              />
            ))
          )}
        </div>
      </div>
    )
  }
  ```

  **src/App.jsx**

  ```jsx wrap src/App.jsx theme={null}
  import React from 'react'
  import { useInterwovenKit } from '@initia/interwovenkit-react'
  import Board from './Board.jsx'

  function shortenAddress(addr) {
    if (!addr) return ''
    return `${addr.slice(0, 8)}...${addr.slice(-4)}`
  }

  export default function App() {
    const { initiaAddress, username, openConnect, openWallet } = useInterwovenKit()

    return (
      <div>
        <header
          style={{
            maxWidth: 600,
            margin: '40px auto 0',
            display: 'flex',
            justifyContent: 'space-between',
            alignItems: 'center',
            gap: 16,
          }}
        >
          <div>
            <p style={{ margin: 0, color: '#666', fontSize: 12, fontWeight: 700 }}>
              Initia Wasm Appchain
            </p>
            <h1 style={{ margin: '8px 0 0' }}>MemoBoard</h1>
          </div>

          {!initiaAddress ? (
            <button onClick={openConnect} className="btn-primary">
              Connect Wallet
            </button>
          ) : (
            <button onClick={openWallet} className="btn-primary">
              {username ? username : shortenAddress(initiaAddress)}
            </button>
          )}
        </header>

        <Board />
      </div>
    )
  }
  ```
</Accordion>

### Native Feature Verification

1. Connect your wallet in `memoboard-frontend`.
2. Confirm the header button shows your `.init` username (not only a truncated address).
3. Post a message and confirm the sender label for your message uses your username.

## Next Steps

Now that you've mastered a Wasm application, you're ready to build your own
idea! Ensure your project meets all the
[Submission Requirements](../submission-requirements) before submitting.
