Build a TODO App using the Collection-Document Storage Smart Contract
To reduce the need for custom contract development and accelerate Web2 to Web3 migrations, we've created a contract that provides a collection/document based storage model, inspired by Firebase’s Firestore. It supports structured JSON data storage using a flexible key-value format and an advanced permission model.
This solution is a major upgrade from the original user_map
contract by enabling:
Namespaced storage: Data is organized into logical collections and documents.
Permissioned access: Fine-grained control over who can
create
,update
,delete
, andread
documents.Role-based security: Support for roles like "creator", "admin", and any custom roles.
Batch operations: Write multiple documents in a single transaction.
Why This Pattern Works for Developers
This architecture is designed to simplify App development by offering a ready-made, flexible backend for storing user-specific and structured application data. Instead of writing custom storage logic for every project, developers can leverage a familiar model that handles collections, documents, and permissions out of the box. By adopting this approach, developers can:
Accelerates development: Skip the need to write, test, or audit custom storage contracts from scratch.
Speeds up time-to-launch: Teams can prototype quickly and launch with just a frontend and this reusable backend.
Reduces smart contract complexity: Consolidates repetitive storage logic into a single, modular contract.
Eases the Web2-to-Web3 transition: Adopts a Firebase-like model using collections and documents, making it intuitive for Web2 developers.
Possible Use Cases
This pattern is ideal for applications that need to store dynamic, user-specific, and structured content on-chain. Whether you're building tools for creators, learners, shoppers, or communities, this storage model provides a reliable and scalable foundation.
Use cases include:
Social platforms – Posts, messages, comments, and user timelines
Educational apps – Quizzes, answers, grades, and course submissions
eCommerce platforms – Product listings, shopping carts, user reviews, and order history
Creative tools – Galleries, stories, media libraries, and asset management
Because of its modular design and flexible permissions, the same backend contract can support multiple use cases across a single app or ecosystem.
Storage Model
This model is inspired by Firebase’s document-based NoSQL structure, allowing apps to store structured, hierarchical data in a flexible and scalable way.
At its core, the storage is based on a two-level key-value mapping:
Each entry is organized by:
Collection name (e.g., "users", "quizzes", "settings")
Document ID (e.g., "xion1abc...", "quiz_001", "user_quiz1")
Each document includes metadata:
This structure supports two main types of storage:
1. User-Owned Documents
These are records that individual users can create and manage themselves, such as profile data or quiz submissions. Each user’s access is limited to their own documents, while admins retain the ability to override or update any record if needed.
Examples:
user_profiles/xion1abc...
quiz_submissions/user1_quiz1
2. Admin-Controlled Collections
Reserved for system-level or shared data, only authorized addresses (like the contract admin or a configured role) can write to these collections. They are ideal for managing global or shared state across the app.
Examples:
quizzes/quiz_001
settings/platform_config
This model provides a clean and extensible foundation for building multi-user, multi-role apps with fine-grained control over data access, storage structure, and updates.
Permission System
The permission system provides fine-grained access control over data stored in the contract, allowing developers to define who can perform specific actions on each collection. Permissions are defined at the collection level, ensuring consistent rules for all documents within that namespace.
Collection-Level Permissions
Each collection can specify separate rules for the following actions:
This means you can independently control who can create, update, delete, or read documents in any given collection.
Permission Levels
The following permission levels are supported:
Anyone: Open access to all users.
AdminOnly: Only the admin can perform the action.
AllowList(Vec): Only specific addresses are allowed.
DenyList(Vec): Everyone except the listed addresses are allowed.
RequireRole(String): Only users with a specific role (e.g., "moderator") can perform the action.
These flexible permission levels allow each collection to enforce tailored access rules based on user roles or specific address whitelisting/blacklisting.
Role System
The role system complements permissions by allowing role-based access control (RBAC). Roles are stored in a simple mapping:
This allows each address to have one or more assigned roles.
Admin role: Granted full access across all collections and operations.
Custom roles: Developers can define and assign roles such as "verified_tutor", "moderator", or any application-specific designation.
By combining the permission and role systems, dapps can implement sophisticated access models to manage both user-generated and system-controlled content in a secure and scalable way.
Create an Instance of the DocuStore Contract
There's no need to deploy your own version of the DocuStore contract on-chain as we've already done so for Testnet.
The Code ID for the latest deplosyment is 1,228
Each contract instance requires a unique initialization message based on its expected parameters. In the case of the DocuStore contract, an admin parameter which stores a Xion address is required.
Set the contract's initialization message by executing the following in your terminal, swapping the address to a Xion address you own and would like to be the admin of the contract:
Execute the following command updating the value for --from
to your wallet address that will be executing the transaction:
Example output:
Copy the txhash
value for the next step.
Retrieve the Contract Address
Once a contract instance is created, it is assigned a unique contract address.
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:
Deploy 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 app to execute certain transactions on their behalf.
Steps to Deploy a Treasury Contract
Click the "New Treasury" button to create a new treasury contract instance.
Select the appropriate configurations. The following "Fee Grant (Allowance)" and "Grant Config (Permission)" sections gives a recommended configuration that works for the DocuStore app:
Allowance
Enter a "Description" in the respective field. This will reflect the intended purpose of the request.
For the "Allowance Type" select "Basic Allowance".
In the "Spend Limit" field, enter
0.1
and selectXION
as the token.Click the "Save" button to apply the configuration.
Permission
Enter a description in the "Description" field. This will reflect the intended purpose of the permission request. This description will be displayed to users when they click "Allow" after connecting their account.
For the "Permission Type" field, select
Execute on a smart contract
.In the "Authorization Type" field, select
Contract Execution Authorization
.Enter the contract address in the "Contract Address" field — this should be the DocuStore 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"
Treasury Params
You will need to enter a Redirect URL
along with an ICON URL
. After those fields are set click the "Create" button to finally create the treasury contract instance. You will be taken to the dahsboard where you will see the newly created Treasury Contract instance. You can click on an instance to get the treasury contract address which will be required for setting up the frontend.
Building the Frontend
Manual Installation
You will first need to clone to repository:
After doing so you will have to change into the newly created directory and then execute the steps below.
Install dependencies:
Copy the
.env.example
file and name it.env.local
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:
Last updated
Was this helpful?