Code in Move [4] — Sui Move Basics

Thouny
Building on Sui
Published in
8 min readJan 2, 2024

One year after the last “Code in Move” article about Aptos, here is finally the 4th episode featuring one of the most innovative and promising blockchain: Sui!

Shortly after exploring Aptos in Spring 2022, I discovered Sui, built by Mysten Labs, a team coming from Facebook, just like Aptos Labs. If I thought that Move and Aptos were interesting innovations compared to their competitors, I was utterly blown away by Sui, which is truly a 0 to 1 innovation and is now in a league of its own.

Just like with Aptos in that article, we’re going to create a simple smart contract to issue and manage a Coin, then deploy it and interact with it. I’ve been eager for months to continue this series with Sui, which excites me immensely, and at last, we’re here! You’ll note, however, that I’ve been very active on Twitter, sharing technical and educational content about Sui, and more!

Prerequisites

Let’s start by installing our development environment. Install the Sui binaries following this tutorial. Then if you’re using vscode or a fork (like the very good Cursor), install the move-analyzer (following these instructions) or sui-move-analyzer (following the instructions on the extension page).

Fundamentals

Sui is built around a fundamental concept: objects. An object is a basic building block that takes the form of a specific struct. Structs have the same abilities as those on Aptos, but their effects are not all the same. The abilities copy and drop allow the struct to be copied or dropped, while store enables the structure to be wrapped in another and transferred freely if it is an object. A struct with the key ability and the first field being id: UID is an object.

Everything is a Sui object, except user accounts which are just public addresses. Assets (NFTs, Coins, LP Tokens, SFTs, etc.) are objects, and so are smart contracts, which are immutable because they don’t maintain a state. Data related to smart contracts is stored in shared objects.

There are two types of objects:

  • Owned Objects: accessible for reads and writes by transactions signed by their owner. Transactions that use only owned objects benefit from very low latency to finality, because they do not need to go through consensus.
  • Shared Objects: accessible for reads and writes by any transaction. Transactions that access one or more shared objects require consensus to sequence reads and written to those objects. This comes at a slightly higher gas cost and increases latency.
    There is also a special type of shared object which is immutable (or frozen) objects. Those can’t be modified, transferred or deleted and thus can bypass the consensus, just like owned objects (that’s why smart contracts are immutable).
yes, Sui is fast

There are two ways to use an object:

  • by value: taking ownership of the object means you have full control over it. You can destroy it, wrap it, modify it and transfer it depending on the permissions set in the module defining it.
  • by reference: borrowing certain parts of the object, mutably or immutably, means the user keeps full-ownership over it. Smart contracts define setters and getters giving permissioned accesses to specific fields in read-only or write mode.

Writing a Package

On Sui, smart contracts are called packages and are published (deployed) on-chain. They are divided in Move modules similarly to Aptos.

sui move new first-coin will create a new Sui project with a Move.toml in which the sui-framework dependency is already set. You can add any on-chain dependency by referring to a github repository, let’s add the Move Standard Library:

[package]
name = "first-coin"
version = "0.0.1"

[dependencies]
Sui = { git = "https://github.com/MystenLabs/sui.git", subdir = "crates/sui-framework/packages/sui-framework", rev = "framework/testnet" }
MoveStdlib = { git = "https://github.com/MystenLabs/sui.git", subdir = "crates/sui-framework/packages/move-stdlib", rev = "framework/testnet" }

[addresses]
first_coin = "0x0"

Create a coin.move file under ./src and add the following imports.

module first_coin::managed {
use std::option;
use sui::coin::{Self, Coin, TreasuryCap};
use sui::transfer;
use sui::tx_context::{Self, TxContext};

}

For this use case, we need a special Sui Move type called a One-Time Witness (OTW). This is a type that is guaranteed to have at most one instance. It is useful for limiting certain actions to only happen once (creating a coin, issuing a NFT collection, etc). In Move, a type is considered a OTW if:

  • Its name is the same as its module’s names, all uppercased.
  • It has the drop ability and only the drop ability.
  • It has no fields, or a single bool field.

It is the first argument to be passed in the init function (function ran once, during deployment) and it will be the symbol of our coin.

struct COIN has drop {}

fun init(otw: COIN, ctx: &mut TxContext) {
let (treasury_cap, metadata) = coin::create_currency<COIN>(witness, 9, b"COIN", b"First Coin", b"This is my first coin", option::none(), ctx);
transfer::public_freeze_object(metadata);
transfer::public_transfer(treasury_cap, tx_context::sender(ctx))
}

Here we create a Coin type with 9 decimals within the Sui system and get back a TreasuryCap and CoinMetadata objects. “Cap” objects are often used to give specific permissions to its bearer (here to mint and burn). We transfer it to the publisher address to restrict minting and burning, but we could also share the object to authorize anyone to mint and burn an amount of this coin. The other object is used by frontends and other smart contracts to get information about the coin. We freeze it to prevent further modifications.

We now need functions to mint and optionally burn an amount of this Coin.

// returns a Coin object
public fun mint(
treasury_cap: &mut TreasuryCap<COIN>, amount: u64, ctx: &mut TxContext
): Coin<COIN> {
coin::mint(treasury_cap, amount, ctx)
}

// sends the Coin object to a recipient
public entry fun mint_and_transfer(
treasury_cap: &mut TreasuryCap<COIN>, amount: u64, recipient: address, ctx: &mut TxContext
) {
coin::mint_and_transfer(treasury_cap, amount, recipient, ctx);
}

public entry fun burn(treasury_cap: &mut TreasuryCap<COIN>, coin: Coin<COIN>) {
coin::burn(treasury_cap, coin);
}

Functions are private by default and can be annotated entry (callable from frontends), public (callable from other packages, and now programmable transaction blocks), public(friend) (callable from friend modules).

All these functions borrow mutably the TreasuryCap, meaning that technically the object is passed to the smart contract with write access but the sender always keeps the ownership. Because one needs to own the Cap to call these functions, only the Coin manager can mint and burn coins. It is passed mutably because the package needs to modify its total_supply field.

When used, the ctx: &mut TxContext needs to be the last parameters of a function, it is automatically passed from the frontend. This parameter allows to access important information (like on-chain time) or to create objects (like Coins).

burn and mint_and_transfer are straightforward because that’s how it works on other blockchains. But Sui has many cool features, and one of them is programmable transaction blocks (PTBs).

I will dedicate an article to PTBs but here is the tl;dr: Sui has transactions like other chains, and up to 1024 transactions can be combined into one PTB. This means that many operations can be executed in a single execution, reducing latency, costs and failure rates drastically, and increasing composability. Here is an example and another one.

It is truly a fantastic tool that opens many new patterns and opportunities for developers, but it is a new programming paradigm to grasp. For the connoisseurs, Radix has a similar concept called Transaction Manifest.

Here, the idea is to return the Coin object. On the front-end or with a back-end, we will then be able to use this object, either send it or pass it to another function. Maybe we’d like to use the mint function to directly send some Coin to a list of address and add the rest into a liquidity pool. We don’t need to create a send function or complex cross-contract calls within our package. This will be abstracted away thanks to PTBs!

You can find the source code here.

Publishing and interacting with the Package

Our package is now ready to be published. Run sui client to init a new config for the Sui CLI. You can find it in ~/.sui/sui_config. Build the package with sui move build. Publish it with sui client publish --gas-budget 100000000. If you get an error that’s normal, you need SUI Coins to pay for gas. Copy the address indicated in the error and go to the Sui discord to claim some SUI on the devnet.

After publishing, you will see data related to the transaction, including the object changes.

Here we can find the IDs of the objects newly created that we’ll need for calling the mint and burn functions. You can also get them from one of the Sui explorers: Sui Explorer, Sui Vision or Sui Scan.

sui client call --package <publishedPackageID> --module coin --function mint_and_transfer --args <createdTreasuryCapID> 10 <recipientAddress>--gas-budget 10000000

This command will call the function mint_and_transfer in the coin module in your newly published package, passing the TreasuryCap id, amount and recipient address as the arguments (TxContext is automatically passed).

Now if you check the recipient address on an explorer you will see a Coin with a balance of amount/10ᵈᵉᶜⁱᵐᵃˡˢ. If it is your address you can get the Coin object id to burn an amount of your choice in the same way.

Summing up

This is a very basic Sui smart contract but it showcases many specificities characteristics of Sui. This is just the beginning!

I have personally played with several blockchain programming models but this is on Sui that I love to build more or less complex dapps the most. Once you grasp the philosophy behind this new programming model, it feels so intuitive and natural. I strongly recommend you to go through the docs which detail very well the vision of Mysten Labs.

This talented team developed exciting building primitives such as PTBs, sponsored transactions, zkLogin, zkSend, dynamic fields, closed loop Tokens, Kiosk and more. We will see those concepts in future articles, feel free to tell me what parts interest you the most on Twitter or in the comments.

If you have any questions, feel free to drop a dm on Twitter. And check my website for much more educational and technical content!

--

--

Thouny
Building on Sui

Blockchain developer crafting educational and technical content on Web3 techs and philosophies. Digressions on life trying to make sense of it.