Building a Per-User Data Storage Dapp
Last updated
Was this helpful?
Last updated
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.
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).
Below is a breakdown of the key components of the contract.
state.rs
)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)
msg.rs
)This file defines the messages that the contract can handle:
InstantiateMsg
No parameters are required when initializing this contract.
ExecuteMsg
Update: Allows a user to store a JSON string under their own address.
QueryMsg
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.rs
)This file defines the behavior of the contract using various entry points.
instantiate
No configigurations done during this particular instantiation.
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
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.
The following are some use cases for that this contract could cover.
User Profiles
Application Preferences
Form Submissions
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:
The compiled contract will be located at artifacts/user_map.wasm
.
Now, upload the contract to the blockchain:
After running the command, extract the transaction hash by executing:
Example output:
Copy the txhash value for the next step.
The Code ID is required for creating an instance of your contract. Set your transaction hash you retrieved above by executing:
Query the blockchain to get the Code ID:
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:
Instantiate the contract with the Code ID from the previous step:
Example output:
Copy the new transaction hash for the next step.
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:
Query the blockchain to get the contract address:
Display the contract address:
Example output:
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.
Click othen "New Treasury" button to create a new treasury contract.
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:
Enter a "Description" in the respective field. This will reflect the intended purpose of the request.
In the "Allowance Type" field, enter "/cosmwasm.feegrant.v1beta1.BasicAllowance"
.
In the "Spend Limit" field, enter 1000uxion
.
Click the "Save" button to apply the configuration.
For the "Type URL" field, select "/cosmwasm.wasm.v1.MsgExecuteContract"
.
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.
In the "Authorization Type" field, select "/cosmwasm.wasm.v1.ContractExecutionAuthorization"
.
Enter the contract address in the "Contract Address" field — this should be the User Map smart contract created above.
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.
Click the "Add Contract Grant" button to apply the configuration.
Then click the "Save" button which generates the "Treasury Instance Preview"
Once the preview is to your liking click the "Create" button to create the Treasury contract.
Install dependencies:
Copy the .env.example
file and name it .env
and set the values with the correct information:
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
)
Build and start the application:
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.
Loads required information from .env
.
Needed to know which contract to interact with and which blockchain node to connect to.
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).
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.
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:
This mirrors a REST API POST
request in Web2.
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:
This mirrors a REST API GET
request in Web2.
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.