Module Structure
Every SUI Move smart contract starts with a module. Modules are the fundamental compilation unit -- they group related types, functions, and constants into a namespace that lives on-chain inside a published package.
Declaring a Module
SUI Move modules use the module keyword followed by package_name::module_name:
module tribe_storage_access::config;This declares a module named config inside the tribe_storage_access package. The package name must match the name field in your Move.toml:
[package]
name = "tribe_storage_access"
edition = "2024.beta"
[dependencies]
world = { local = "../../.world-contracts/contracts/world" }Every .move file in the sources/ directory contains exactly one module.
Use Statements
Import types and functions from other modules with use:
use sui::dynamic_field as df;
use world::character::Character;
use world::storage_unit::{Self, StorageUnit};use sui::dynamic_field as df-- imports the module and gives it a shorter alias.use world::character::Character-- imports a single type.use world::storage_unit::{Self, StorageUnit}--Selfimports the module itself (so you can callstorage_unit::some_function()), andStorageUnitimports the type directly.
Struct Definitions and Abilities
Structs are how you define data in Move. Each struct can declare abilities that control what operations are allowed on instances of that type:
| Ability | Meaning |
key | Can be stored as a top-level object in global storage (requires an id: UID field) |
store | Can be stored inside another object's fields |
copy | Can be duplicated with implicit copy |
drop | Can be silently discarded when it goes out of scope |
Here is the ExtensionConfig struct from the tribe storage contract:
/// Shared configuration object. Rules are stored as dynamic fields.
public struct ExtensionConfig has key {
id: UID,
}This struct has key ability, which means it is a SUI object. The id: UID field is mandatory for any struct with key -- it gives the object a globally unique address on-chain.
The AdminCap adds store so it can be transferred and wrapped:
/// Capability proving ownership of this extension deployment.
public struct AdminCap has key, store {
id: UID,
}And the XAuth witness is a zero-sized struct with only drop:
/// Zero-sized typed witness for SSU extension authorization.
public struct XAuth has drop {}We will cover the witness pattern in detail in the next chapter.
Function Visibility
SUI Move has three visibility levels for functions:
public -- Anyone Can Call
public fun has_rule<K: copy + drop + store>(config: &ExtensionConfig, key: K): bool {
df::exists_(&config.id, key)
}Any module in any package can call config::has_rule(...).
public(package) -- Same Package Only
/// Create an XAuth witness value. Only callable within this package.
public(package) fun x_auth(): XAuth {
XAuth {}
}Only modules inside tribe_storage_access can call config::x_auth(). External packages cannot. This is critical for security -- it means only your own modules can mint authorization witnesses.
Private (No Modifier) -- Same Module Only
fun init(ctx: &mut TxContext) {
let admin_cap = AdminCap { id: object::new(ctx) };
transfer::transfer(admin_cap, ctx.sender());
let config = ExtensionConfig { id: object::new(ctx) };
transfer::share_object(config);
}The init function has no visibility modifier, making it private. In SUI Move, init is a special function called exactly once when the package is published. No one can call it again.
entry -- Transaction Entry Point
There is a fourth modifier, entry, for functions that should only be callable directly from a transaction (not from other Move code). It is useful for restricting composability when needed:
entry fun dangerous_admin_action(cap: &AdminCap) {
// Only callable via a PTB, not from another module
}Putting It All Together
Here is the complete config.move module that demonstrates all these patterns in a real EVE Frontier extension:
module tribe_storage_access::config;
use sui::dynamic_field as df;
// --- Structs with different abilities ---
public struct ExtensionConfig has key {
id: UID,
}
public struct AdminCap has key, store {
id: UID,
}
public struct XAuth has drop {}
// --- Package-private function ---
public(package) fun x_auth(): XAuth {
XAuth {}
}
// --- Private init (runs once on publish) ---
fun init(ctx: &mut TxContext) {
let admin_cap = AdminCap { id: object::new(ctx) };
transfer::transfer(admin_cap, ctx.sender());
let config = ExtensionConfig { id: object::new(ctx) };
transfer::share_object(config);
}
// --- Public functions anyone can call ---
public fun has_rule<K: copy + drop + store>(
config: &ExtensionConfig, key: K,
): bool {
df::exists_(&config.id, key)
}Key Takeaways
- A module is declared with
module package::name;and lives in a single.movefile undersources/. - Structs with
keyare SUI objects and must have anid: UIDfield. publicfunctions are callable by anyone;public(package)restricts to the same package; private functions (no modifier) are module-only.- The
initfunction runs once at publish time and is the place to create initial objects like config and capability tokens. usestatements import types and modules, with optional aliases viaas.
Sign in to track your progress.