XION
DiscordGithub
  • Welcome to XION
  • XION'S Core
    • Concepts
      • Generalized Chain Abstraction
      • Intro to Account Abstraction
      • XION's Meta Accounts
      • Meta Accounts Design
      • Architecture & Tech Glossary
      • Use Cases
  • developers
    • Xion Quick Start
      • Zero to Dapp in 5 Minutes
        • Launch a User Map Dapp on XION in 5 Minutes
        • React Native Mobile Dapp on XION in 5 Minutes
    • Mobile dapp Development on XION
      • Set up your XION Mobile Development Environment
      • Create Mobile Dapp and Integrate Meta Account Authentication
    • Getting Started (Advanced)
      • Set Up Local Environment
        • Setting up your Local Smart Contract Development Environment for XION
          • Setting up your XION Smart Contract Development Environment on Windows (WSL2 + Ubuntu)
        • Set Up an Integrated Development Environment (IDE)
        • Interacting with Xion Chain using Xion Daemon
      • Your First Contract
        • Deploying Your First Smart Contract on XION
      • Gasless UX & Permission Grants
        • Enabling Gasless Transactions with Treasury Contracts
      • Dapp Development
        • Account Abstraction with Gasless Transactions
        • Interact with XION via your Backend Service
    • Re-using Existing Contracts
      • Deployed Contracts on XION
      • Non-Fungible Tokens (NFTs)
      • Fungible Tokens
      • Marketplace
      • Multisig
      • Proxy Contracts
      • Membership Management
    • Web3 for Web2 Developers
      • Web2 vs Web3 App Architecture: A Comparison
      • Misconceptions and Misapplied Web2 Patterns
      • Recommended Architecture for Dapps on XION
    • Building for Mainnet
      • Xion Testnet: Your Development Playground
      • Building with Audited & Battle-Tested Contracts
      • Community Engagement: Building Support for Your dApp
      • Deploying to Xion Mainnet
        • Smart Contract Audits: Process, Costs & Support
        • Governance Process to Deploying Smart Contracts to Mainnet
    • Learn & Build
      • Token Factory
        • Creating, Minting, and Interacting with a Token Factory Token
        • Building a React dApp to Interact with Token Factory Tokens
        • Integrating a Token Factory Token in a Smart Contract
      • Websockets
        • WebSockets with Xion: Real-Time Communication
      • Mobile Development
        • Building a React Native Mobile App with Abstraxion (Xion.js)
      • Oracles
        • Creating a Smart Contract with Pyth Oracle Integration
      • Indexers: Optimized Data Retrieval
        • SubQuery
      • Use Cases
        • Building a Per-User Data Storage Dapp
    • Reference and Resources
      • Requesting XION Testnet Tokens
      • Public Endpoints & Resources
      • Block Explorers
      • Governance
        • Deploying Smart Contracts to Mainnet
      • Developer Tools: Abstract
      • IBC Denoms on XION Networks
      • Frequently Asked Questions
      • XION Token Contract Addresses on EVM Chains
  • Nodes & Validators
    • Run a Node
      • System Specifications
      • Build the Xion Daemon
      • Download the Xion Daemon
      • Configure the Xion Daemon
        • app.toml
        • client.toml
        • config.toml
      • Join the XION Network
        • xion-testnet-1
      • Confirm node is running
    • Become a Validator
      • Initial Setup
      • Obtain a XION Wallet Address
      • Obtain Funds
        • Testnet
      • Create Validator
    • IBC Relayers and Tokens
  • Others
    • Resources
Powered by GitBook
On this page
  • User Map Contract
  • Key Components of the User Map Contract
  • Common Use Cases
  • Deploying User Map Contract On-Chain
  • Clone the Repository
  • Optimize Contract
  • Upload Optimized Contract On-chain
  • Retrieve the Code ID
  • Instantiate the Contract
  • Retrieve the Contract Address
  • Deploying a Treasury Contract
  • Steps to Deploy a Treasury Contract
  • Building the Frontend
  • Manual Installation
  • Frontend: Main Page Breakdown

Was this helpful?

Edit on GitHub
  1. developers
  2. Learn & Build
  3. Use Cases

Building a Per-User Data Storage Dapp

PreviousUse CasesNextReference and Resources

Last updated 17 days ago

Was this helpful?

Let's build a simple yet powerful decentralized application (dApp) that allows users to store and retrieve their JSON data. For developers coming from Web2, this serves as an excellent introduction to Web3 concepts by providing familiar functionality (user-specific data storage) implemented in a decentralized way.

User Map Contract

The User Map contract is a lightweight and beginner friendly smart contract designed to help developers, especially those coming from Web2, understand how to manage per-user data in a decentralized environment. You can find the source code here: .

This contract enables users to store their own JSON encoded data using their wallet address as a unique key. It functions similarly to a key-value store backend in Web2 applications, with each user's address acting as the key and their JSON object as the value.

Key capabilities:

  • Allows any user to store structured data under their address (e.g., profile, settings, preferences).

  • Validates that only proper JSON strings are stored.

  • Makes all data publicly queryable (anyone can fetch stored data for any address).

Key Components of the User Map Contract

Below is a breakdown of the key components of the contract.

State Definition (state.rs)

pub const USER_MAP: Map<Addr, String> = Map::new("user_map");

This defines a Map named USER_MAP, which stores stringified JSON values for each user, keyed by their wallet address (Addr). It acts like a key-value store:

  • Key: Addr (user wallet address)

  • Value: String (the JSON data)

Messages (msg.rs)

This file defines the messages that the contract can handle:

InstantiateMsg

pub struct InstantiateMsg {}

No parameters are required when initializing this contract.

ExecuteMsg

pub enum ExecuteMsg {
    Update { value: String }
}
  • Update: Allows a user to store a JSON string under their own address.

QueryMsg

pub enum QueryMsg {
    GetUsers {},
    GetValueByUser { address: Addr },
    GetMap {}
}

Provides three ways to query stored data:

  • GetUsers: Returns a list of all user addresses that have stored data.

  • GetValueByUser: Retrieves the stored value for a specific address.

  • GetMap: Returns the full map of all user addressed data pairs.

Contract Logic (contract.rs)

This file defines the behavior of the contract using various entry points.

instantiate

pub fn instantiate(...)

No configigurations done during this particular instantiation.

execute

pub fn execute(...)

Handles the Update message:

  • Uses serde_json::from_str to ensure the input string is valid JSON.

  • Stores the validated JSON string under the sender's address in USER_MAP.

query

pub fn query(...)

Implements all the QueryMsg variants:

  • GetValueByUser: Loads and returns the stored string for a given user.

  • GetUsers: Iterates over the keys in USER_MAP and returns all addresses.

  • GetMap: Iterates over the full map and returns all (address, value) pairs.

Common Use Cases

The following are some use cases for that this contract could cover.

  1. User Profiles

    {
      "username": "alice123",
      "bio": "Web3 developer",
      "avatar": "ipfs://Qm..."
    }
  2. Application Preferences

    {
      "theme": "dark",
      "notifications": true,
      "language": "en"
    }
  3. Form Submissions

    {
      "survey": {
        "question1": "answer",
        "question2": 5
      }
    }

Deploying User Map Contract On-Chain

Clone the Repository

First, you need to download the contract code that will be compiled. Use the following commands to clone the repository and navigate into the project directory:

git clone https://github.com/burnt-labs/contracts
cd contracts

Optimize Contract

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

The compiled contract will be located at artifacts/user_map.wasm.

Upload Optimized Contract On-chain

WALLET="your-wallet-address-or-key-name-here"

Now, upload the contract to the blockchain:

RES=$(xiond tx wasm store ./artifacts/user_map.wasm \
      --chain-id xion-testnet-2 \
      --gas-adjustment 1.3 \
      --gas-prices 0.001uxion \
      --gas auto \
      -y --output json \
      --node https://rpc.xion-testnet-2.burnt.com:443 \
      --from $WALLET)

After running the command, extract the transaction hash by executing:

echo $RES

Example output:

{
  "height": "0",
  "txhash": "B557242F3BBF2E68D228EBF6A792C3C617C8C8C984440405A578FBBB8A385035",
  ...
}

Copy the txhash value for the next step.

Retrieve the Code ID

The Code ID is required for creating an instance of your contract. Set your transaction hash you retrieved above by executing:

TXHASH="your-txhash-here"

Query the blockchain to get the Code ID:

CODE_ID=$(xiond query tx $TXHASH \
  --node https://rpc.xion-testnet-2.burnt.com:443 \
  --output json | jq -r '.events[-1].attributes[1].value')

Instantiate the Contract

When you uploaded the WASM bytecode, you stored the contract code on the blockchain, but no contract instance has been created. The uploaded code can be used to create multiple contract instances, each with its own state.

Set the contract's initialization message by executing:

MSG='{ }'

Instantiate the contract with the Code ID from the previous step:

xiond tx wasm instantiate $CODE_ID "$MSG" \
  --from $WALLET \
  --label "user-map" \
  --gas-prices 0.025uxion \
  --gas auto \
  --gas-adjustment 1.3 \
  -y --no-admin \
  --chain-id xion-testnet-2 \
  --node https://rpc.xion-testnet-2.burnt.com:443

Example output:

gas estimate: 217976
code: 0
txhash: 09D48FE11BE8D8BD4FCE11D236D80D180E7ED7707186B1659F5BADC4EC116F30

Copy the new transaction hash for the next step.

Retrieve the Contract Address

Once a contract instance is created, it is assigned a unique contract address, which can be queried for state changes.

Set the new transaction hash from the previous contract instantiation step:

TXHASH="your-txhash-here"

Query the blockchain to get the contract address:

CONTRACT=$(xiond query tx $TXHASH \
  --node https://rpc.xion-testnet-2.burnt.com:443 \
  --output json | jq -r '.events[] | select(.type == "instantiate") | .attributes[] | select(.key == "_contract_address") | .value')

Display the contract address:

echo $CONTRACT

Example output:

xion1v6476wrjmw8fhsh20rl4h6jadeh5sdvlhrt8jyk2szrl3pdj4musyxj6gl

Deploying a Treasury Contract

Before integrating the Abstraxion SDK into the application, we first need to deploy a Treasury Contract. This contract facilitates gasless transactions for your smart contract by handling fee grants on behalf of users as well as allowing users to grant authorization(s) to your dapp to execute certain account transactions on their behalf.

Steps to Deploy a Treasury Contract

  1. Click othen "New Treasury" button to create a new treasury contract.

  2. Select the appropriate configuration based on your use case. The following "Fee Grant" and "Grant Config" sections gives a recommended configuration that works for most scenarios:

Fee Grant

  1. Enter a "Description" in the respective field. This will reflect the intended purpose of the request.

  2. In the "Allowance Type" field, enter "/cosmwasm.feegrant.v1beta1.BasicAllowance".

  3. In the "Spend Limit" field, enter 1000uxion.

  4. Click the "Save" button to apply the configuration.

Grant Config

  1. For the "Type URL" field, select "/cosmwasm.wasm.v1.MsgExecuteContract".

  2. Enter a "Description" in the respective field. This will reflect the intended purpose of the request. This description will be displayed to users when they click "Allow" after connecting their account.

  3. In the "Authorization Type" field, select "/cosmwasm.wasm.v1.ContractExecutionAuthorization".

  4. Enter the contract address in the "Contract Address" field — this should be the User Map smart contract created above.

  5. You must select at least one of the following::

    • "Max Call" – Limits the number of times a user can execute a transaction under this fee grant.

    • "Max Funds" – Specifies the maximum amount of funds allocated for covering transaction fees.

    • "Both" – Allows you to set both options.

  6. Click the "Add Contract Grant" button to apply the configuration.

  7. Then click the "Save" button which generates the "Treasury Instance Preview"

Treasury Instance Preview

Once the preview is to your liking click the "Create" button to create the Treasury contract.

Building the Frontend

Manual Installation

  1. Install dependencies:

npm install
  1. Copy the .env.example file and name it .env and set the values with the correct information:

NEXT_PUBLIC_TREASURY_ADDRESS=your_treasury_address
NEXT_PUBLIC_CONTRACT_ADDRESS=your_contract_address
NEXT_PUBLIC_RPC_URL="https://rpc.xion-testnet-2.burnt.com:443"
NEXT_PUBLIC_REST_URL="https://api.xion-testnet-2.burnt.com"
Variable
Description

NEXT_PUBLIC_TREASURY_ADDRESS

Treasury address used for gasless transactions and grantz authorization

NEXT_PUBLIC_CONTRACT_ADDRESS

Address of your deployed User Map smart contract

NEXT_PUBLIC_RPC_URL

RPC endpoint for Xion (default: https://rpc.xion-testnet-2.burnt.com:443)

NEXT_PUBLIC_REST_URL

REST endpoint for Xion (default: https://api.xion-testnet-2.burnt.com)

  1. Build and start the application:

npm run dev

Frontend: Main Page Breakdown

The src/app/page.tsx file is the core of the frontend. It connects the wallet, submits JSON data to the User Map smart contract, and queries stored data, all using Xion’s Abstraxion toolkit.

Environment Setup

const contractAddress = process.env.NEXT_PUBLIC_CONTRACT_ADDRESS!;
const rpc = process.env.NEXT_PUBLIC_RPC_URL!;
const rest = process.env.NEXT_PUBLIC_REST_URL!;
  • Loads required information from .env.

  • Needed to know which contract to interact with and which blockchain node to connect to.

Wallet and Blockchain Client Setup

const { data: account } = useAbstraxionAccount();
const { client, signArb, logout } = useAbstraxionSigningClient();
const { client: queryClient } = useAbstraxionClient();
  • account: Information about the connected wallet (if connected).

  • signArb: Signs and sends transactions (e.g., sending a message to store data).

  • queryClient: Queries blockchain data without needing the user's signature.

This sets up the page to handle both signed actions (store data) and unsigned actions (read data).

Local State Management

const [jsonInput, setJsonInput] = useState("");
const [userAddressQuery, setUserAddressQuery] = useState("");
const [queryResult, setQueryResult] = useState("");
  • jsonInput: What the user wants to save (stringified JSON).

  • userAddressQuery: What user address to query.

  • queryResult: Where to show the retrieved data after a query.

Keeps track of user inputs and outputs without page reloads.

Handling Submitting JSON Data (handleSubmit)

async function handleSubmit() { ... }
  • Takes the jsonInput the user typed in.

  • Wraps it into the update smart contract message structure.

  • Sends the transaction using client.

If successful, the blockchain stores the user's JSON string under their address.

Example message sent to blockchain:

{
  "update": {
    "value": "{\"username\":\"alice\",\"theme\":\"dark\"}"
  }
}

This mirrors a REST API POST request in Web2.

Handling Querying User Data (handleQuery)

async function handleQuery() { ... }
  • Takes an entered wallet address (userAddressQuery).

  • Sends a smart query to the blockchain to fetch the stored data.

  • Displays the result in queryResult.

Example smart query sent:

{
  "get_value": {
    "address": "xion1abc..."
  }
}

This mirrors a REST API GET request in Web2.

Quick Frontend Walkthrough

If you're new to compiling, deploying, and instantiating smart contracts, we recommend following the to learn the process. Once deployed, the contract address will be referenced in your Treasury contract configuration and in the upcoming code updates. We'll also walk through the major steps below.

Next, compile and optimize the smart contract using the . You need to have Docker running to execute the command below:

To learn how to set up a wallet see the following section on using xiond. Next, set your wallet address or key name by executing the following in your terminal:

Each contract instance requires a unique initialization message based on its expected parameters. In the case of the , there are no required fields.

Login to the .

Learn more about Treasury Contracts .

We've created a frontend which is a application built to interact with the User Map smart contract that can be downloaded at . It showcases how users can connect their wallets, submit JSON data to update their record, and query the stored data.

You will first need to clone to repository using git clone . After doing so you will have to change into the newly created directory and then execute the steps below.

github.com/burnt-labs/contracts/tree/main/contracts/user_map
guide
CosmWasm Optimizer
generating an account
User Map contract
XION Developer Portal
here
Next.js
https://github.com/burnt-labs/xion-user-map-json-store-frontend
https://github.com/burnt-labs/xion-user-map-json-store-frontend
Example of a general Fee Grant configuration
Example of additional Grant configuration