Path: Learning Path > 02 Mental Model Shift
This repo assumes you already think in Solidity. The goal here is not to re-teach smart contracts, but to rewire a few instincts so Sui feels natural.
- Understand what changes (objects, ownership, capabilities, packages).
- Know how this repo is laid out for a linear learning path.
- Run a quick environment sanity check before touching code.
- Contract storage -> objects + tables: your state lives in owned/shared objects and typed table entries, not in a single contract storage map. See
packages/dapp/contracts/oracle-market/sources/shop.move(Shop,Discount,Shop.listings,Shop.accepted_currencies). - onlyOwner -> capability: authority is proved by holding a capability object. See
ShopOwnerCapinpackages/dapp/contracts/oracle-market/sources/shop.move. - Deployment -> publish + instantiate: publishing creates a package object; stateful instances are created later as shared objects. See publish flow in
packages/dapp/src/scripts/contracts/publish.tsand shop creation inpackages/dapp/src/scripts/owner/shop-create.ts. - Inheritance -> modules + generics: Move has no inheritance or dynamic dispatch; reuse is done through modules, functions, and type parameters. This repo uses
ShopItem<phantom T>andCoin<C>to keep types safe without polymorphism. - Composability -> PTBs: you compose calls at runtime in a programmable transaction block (PTB), rather than writing a single on-chain "router" contract for every workflow.
- Upgrades -> new package + UpgradeCap: upgrades publish a new package ID gated by an
UpgradeCap. Callers opt into new package IDs explicitly.
- Abilities (
key,store,copy,drop): abilities declare how values can be stored and moved.keyturns a struct into an object with identity.storelets it live on-chain.copyanddropopt into value semantics. In this repo, objects likeShopandShopOwnerCaparehas key, store, while events arehas copy, drop. Code:packages/dapp/contracts/oracle-market/sources/shop.move(struct definitions) - Resources and ownership: Move resources must be moved, not copied. Owned objects (like
ShopOwnerCap) are authority tokens. Passing a resource by value is a one-time action, which is why capability and coin flows remain explicit. Code:packages/dapp/contracts/oracle-market/sources/shop.move(ShopOwnerCap) - Object ownership types: Sui supports address-owned, shared, immutable, and object-owned
objects. This repo uses address-owned capabilities/receipts, shared objects for
Shop/discounts, and object-owned dynamic-field children under tables. Code:packages/dapp/contracts/oracle-market/sources/shop.move(ShopOwnerCap, Shop) - Strings (
String): Sui Move’s String type is designed for user-facing, human-readable text and is always UTF-8 encoded. This is different from Solidity, where string is a dynamic byte array and encoding is not enforced. Using String ensures your data is valid UTF-8, which is important for interoperability and user interfaces. Many Sui and Move standard library functions expect or return String, making it the idiomatic choice for names, descriptions, and other text fields. Use vector only when you need to store arbitrary bytes (e.g., binary data, hashes, or non-UTF-8 content). Code:packages/dapp/contracts/oracle-market/sources/shop.move(Shop.name, ItemListing.name) - Options instead of sentinels: optional values use
Optioninstead of magic constants. This is used for optional listing links, discount expiry, and max-redemption caps. Code:packages/dapp/contracts/oracle-market/sources/shop.move(Option fields)
packages/dapp/contracts/oracle-market/sources/shop.move(Shop, ShopOwnerCap, entry functions)packages/dapp/src/scripts/contracts/publish.ts(publish flow + artifacts)packages/dapp/src/scripts/owner/shop-create.ts(shop instantiation)
Code spotlight: object-first state + capability auth
packages/dapp/contracts/oracle-market/sources/shop.move
public struct ShopOwnerCap has key, store {
id: UID,
shop_id: ID,
}
public struct Shop has key, store {
id: UID,
owner: address,
name: String,
disabled: bool,
accepted_currencies: Table<TypeName, AcceptedCurrency>,
listings: Table<ID, ItemListing>,
}Code spotlight: publish flow resolves package + artifacts
packages/dapp/src/scripts/contracts/publish.ts
const fullPackagePath = resolveFullPackagePath(
path.resolve(tooling.suiConfig.paths.move),
cliArguments.packagePath
)
const deploymentArtifacts = await loadDeploymentArtifacts(
tooling.suiConfig.network.networkName
)
if (
await shouldSkipPublish(
tooling,
cliArguments.rePublish,
deploymentArtifacts,
fullPackagePath
)
) {
logSkippedPublish(tooling.suiConfig.network.networkName, fullPackagePath)
return
}
await publishPackageToNetwork(
tooling,
fullPackagePath,
derivePublishOptions(tooling.suiConfig.network.networkName, cliArguments)
)Code spotlight: instantiate and share a Shop after publish
packages/dapp/contracts/oracle-market/sources/shop.move
public fun create_shop_and_share(name: String, ctx: &mut TxContext): (ID, ShopOwnerCap) {
let (shop, owner_cap) = create_shop(name, ctx);
let shop_id = shop.id();
transfer::public_share_object(shop);
(shop_id, owner_cap)
}- Open
packages/dapp/contracts/oracle-market/sources/shop.moveand findShopOwnerCap. Expected outcome: you can explain why it replacesonlyOwner. - Skim
packages/dapp/src/scripts/contracts/publish.tsand list the artifacts it writes. Expected outcome: you can point topackages/dapp/deployments/deployment.<network>.json.
EVM: contract storage
Contract
mapping(listingId => Listing)
Sui: shared objects + typed tables
Shop (shared)
table listings: listing_id (ID) -> ItemListing
table accepted_currencies: coin_type (TypeName) -> AcceptedCurrency
Discount (shared)
- https://docs.sui.io/concepts/sui-move-concepts
- https://docs.sui.io/guides/developer/objects/object-model
- https://docs.sui.io/references/sui-move
- https://docs.sui.io/concepts/sui-for-ethereum
- Previous: 01 Repo Layout + How to Navigate
- Next: 03 EVM → Sui Cheatsheet
- Back to map: Learning Path Map