multiversx-project-architecture

Production-grade project structure patterns for MultiversX smart contracts. Use when starting a new contract project, refactoring an existing one, or building multi-contract systems with shared code.

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "multiversx-project-architecture" with this command: npx skills add multiversx/mx-ai-skills/multiversx-mx-ai-skills-multiversx-project-architecture

MultiversX Project Architecture

Production-tested folder structures and module composition patterns for MultiversX contracts.

Single Contract Structure

For contracts up to ~1000 lines of business logic:

my-contract/
├── Cargo.toml
├── src/
│   ├── lib.rs              # #[contract] trait — ONLY trait composition + init/upgrade
│   ├── storage.rs           # All #[storage_mapper] definitions
│   ├── views.rs             # All #[view] endpoints
│   ├── config.rs            # Admin configuration endpoints
│   ├── events.rs            # #[event] definitions
│   ├── validation.rs        # Input validation helpers
│   ├── errors.rs            # Static error constants
│   └── helpers.rs           # Pure business logic functions
├── meta/
│   ├── Cargo.toml
│   └── src/main.rs
├── wasm/
│   ├── Cargo.toml
│   └── src/lib.rs
└── tests/
    └── integration_test.rs

Multi-Contract Workspace Structure

For protocols with multiple interacting contracts:

my-protocol/
├── Cargo.toml               # [workspace] members
├── common/                   # Shared crate across all contracts
│   ├── Cargo.toml
│   ├── constants/
│   │   ├── Cargo.toml
│   │   └── src/lib.rs       # Protocol-wide constants
│   ├── errors/
│   │   ├── Cargo.toml
│   │   └── src/lib.rs       # Shared error constants
│   ├── structs/
│   │   ├── Cargo.toml
│   │   └── src/lib.rs       # Shared data types
│   ├── math/
│   │   ├── Cargo.toml
│   │   └── src/math.rs      # Shared math module trait
│   └── events/
│       ├── Cargo.toml
│       └── src/lib.rs       # Shared event definitions
├── contract-a/               # First contract
│   ├── Cargo.toml
│   ├── src/
│   │   ├── lib.rs
│   │   ├── storage/
│   │   │   └── mod.rs        # Local + proxy storage
│   │   ├── cache/
│   │   │   └── mod.rs        # Drop-based cache
│   │   ├── views.rs
│   │   ├── config.rs
│   │   ├── events.rs
│   │   ├── validation.rs
│   │   └── helpers/
│   │       └── mod.rs
│   ├── meta/
│   ├── wasm/
│   └── tests/
├── contract-b/
│   └── ...                   # Same structure
└── proxy-definitions/        # Optional: shared proxy traits
    ├── Cargo.toml
    └── src/lib.rs

lib.rs Pattern: Trait Composition Only

The main contract file should ONLY compose modules — no business logic:

#![no_std]

multiversx_sc::imports!();

pub mod cache;
pub mod config;
pub mod events;
pub mod helpers;
pub mod storage;
pub mod validation;
pub mod views;

#[multiversx_sc::contract]
pub trait MyContract:
    storage::StorageModule
    + config::ConfigModule
    + views::ViewsModule
    + events::EventsModule
    + validation::ValidationModule
    + helpers::HelpersModule
    + common_math::SharedMathModule
{
    #[init]
    fn init(&self, /* params */) {
        // Only initialization logic
    }

    #[upgrade]
    fn upgrade(&self) {
        // Only upgrade migration logic
    }

    #[endpoint]
    fn main_operation(&self) {
        // Delegate to helpers/validation
        self.validate_payment(&payment);
        let mut cache = cache::StorageCache::new(self);
        self.process_operation(&mut cache, &payment);
        // cache drops and commits
    }
}

errors.rs Pattern

Use pub static byte string references for gas-efficient error messages:

pub static ERROR_NOT_ACTIVE: &[u8] = b"Contract is not active";
pub static ERROR_INVALID_AMOUNT: &[u8] = b"Invalid amount";
pub static ERROR_UNAUTHORIZED: &[u8] = b"Unauthorized";
pub static ERROR_NOT_SUPPORTED: &[u8] = b"Not supported";
pub static ERROR_INSUFFICIENT_BALANCE: &[u8] = b"Insufficient balance";
pub static ERROR_ZERO_AMOUNT: &[u8] = b"Amount must be greater than zero";

Usage:

require!(amount > 0u64, ERROR_ZERO_AMOUNT);

This is more gas-efficient than inline string literals because the compiler deduplicates static references.

events.rs Pattern

Define events as a separate module trait:

#[multiversx_sc::module]
pub trait EventsModule {
    #[event("deposit")]
    fn deposit_event(
        &self,
        #[indexed] caller: &ManagedAddress,
        #[indexed] token: &TokenId,
        amount: &BigUint,
    );

    #[event("withdraw")]
    fn withdraw_event(
        &self,
        #[indexed] caller: &ManagedAddress,
        #[indexed] token: &TokenId,
        amount: &BigUint,
    );
}

validation.rs Pattern

Centralize all input validation:

#[multiversx_sc::module]
pub trait ValidationModule: crate::storage::StorageModule {
    fn validate_payment(&self, payment: &Payment<Self::Api>) {
        self.require_token_supported(&payment.token_identifier);
        self.require_amount_positive(payment.amount.as_big_uint());
    }

    fn require_token_supported(&self, token: &TokenId<Self::Api>) {
        require!(self.supported_tokens().contains(token), ERROR_NOT_SUPPORTED);
    }

    fn require_amount_positive(&self, amount: &BigUint) {
        require!(amount > &BigUint::zero(), ERROR_ZERO_AMOUNT);
    }
}

When to Create a Common Workspace Crate

SignalAction
Same struct used in 2+ contractsMove to common/structs/
Same math function in 2+ contractsMove to common/math/
Same error messages across contractsMove to common/errors/
Same event definitionsMove to common/events/
Protocol constants (precision, limits)Move to common/constants/

Workspace Cargo.toml

[workspace]
members = [
    "contract-a",
    "contract-a/meta",
    "contract-b",
    "contract-b/meta",
    "common/structs",
    "common/math",
    "common/errors",
    "common/constants",
    "common/events",
]

Common crate Cargo.toml:

[package]
name = "common-structs"
version = "0.0.0"
edition = "2024"

[dependencies.multiversx-sc]
version = "0.64.0"

SDK Standard Modules (multiversx-sc-modules)

Reusable modules provided by the SDK. Import in Cargo.toml and inherit in your contract trait.

[dependencies.multiversx-sc-modules]
version = "0.64.0"
ModulePurposeImport Path
only_adminAdmin-only access control (owner can add/remove admins)multiversx_sc_modules::only_admin
pausePausable contract pattern (#[endpoint] pause / unpause)multiversx_sc_modules::pause
default_issue_callbacksStandard ESDT token issue/set-role callbacksmultiversx_sc_modules::default_issue_callbacks
esdtToken issuance, minting, burning via unified issue_tokenmultiversx_sc_modules::esdt
governanceDAO proposals, voting, and executionmultiversx_sc_modules::governance
bonding_curveToken pricing with bonding curve formulasmultiversx_sc_modules::bonding_curve
token_mergeNFT/SFT merging and splittingmultiversx_sc_modules::token_merge
subscriptionRecurring payment subscriptionsmultiversx_sc_modules::subscription
stakingBasic staking logic with rewardsmultiversx_sc_modules::staking
featuresFeature flags for contract capabilitiesmultiversx_sc_modules::features
usersUser ID mapping (address to numeric ID)multiversx_sc_modules::users
ongoing_operationLong-running operation checkpointingmultiversx_sc_modules::ongoing_operation
claim_developer_rewardsClaim accumulated developer rewardsmultiversx_sc_modules::claim_developer_rewards
dnsMultiversX DNS herotag registrationmultiversx_sc_modules::dns

Usage example:

#[multiversx_sc::contract]
pub trait MyContract:
    multiversx_sc_modules::only_admin::OnlyAdminModule
    + multiversx_sc_modules::pause::PauseModule
    + multiversx_sc_modules::default_issue_callbacks::DefaultIssueCallbacksModule
{
    #[endpoint]
    fn admin_action(&self) {
        self.require_caller_is_admin();
        self.require_not_paused();
        // ...
    }
}

Naming Conventions

ItemConventionExample
Contract cratekebab-caseliquidity-pool
Module filessnake_casestorage.rs, helpers.rs
Storage keyscamelCase"totalSupply", "feeRate"
Error constantsSCREAMING_SNAKEERROR_INVALID_AMOUNT
Module traitsPascalCaseStorageModule, ValidationModule
Endpoint namessnake_casefn deposit_tokens(&self)
View namescamelCase (ABI)#[view(getBalance)]

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Coding

multiversx-code-analysis

No summary provided by upstream source.

Repository SourceNeeds Review
General

multiversx-clarification-expert

No summary provided by upstream source.

Repository SourceNeeds Review
General

multiversx-protocol-experts

No summary provided by upstream source.

Repository SourceNeeds Review