In this part of the series, we will dive into writing our first Sui Move smart contract. Before we tackle the complexity of a Decentralized Exchange (DEX), we'll start with a simpler example to understand the basics of Sui objects, ownership, and module structure. We'll build a simple "Note" contract that allows users to create and delete notes.
1. Understanding the Note Contract
Below is the code for the note contract we will be using for this tutorial. The note contract is a very simple proof of concept that allows users to create and delete notes.
We've added detailed comments to help you understand every part of the code.
1.1 The Note Module
Create a new file sources/notes.move and add the following code:
#[lint_allow(self_transfer)]
module dacade_zklogin::notes {
use sui::tx_context::{TxContext, Self};
use sui::transfer::Self;
use sui::object::{Self, UID};
use std::string::String;
/// Struct to store the ID of the Notes (Shared Object)
struct Notes has key {
id: UID
}
/// Struct to represent a Note
struct Note has key, store {
id: UID,
title: String,
body: String
}
/// Module initializer to create the Notes shared object
#[allow(unused_function)]
fun init(ctx: &mut TxContext) {
let notes = Notes{
id: sui::object::new(ctx),
};
// Share the Notes object so it can be accessed by everyone
transfer::share_object(notes)
}
/// Function to create a new note
/// @param title: The title of the note
/// @param body: The body of the note
/// @param ctx: The transaction context
public fun create_note(title: String, body: String, ctx: &mut TxContext) {
let note = Note {
id: object::new(ctx),
title,
body
};
// Transfer the note to the sender of the transaction
transfer::transfer(note, tx_context::sender(ctx))
}
/// Function to delete a note
/// @param note: The note object to be deleted
/// @param _ctx: The transaction context
public fun delete_note(note: Note, _ctx: &mut TxContext) {
// Unpack the Note struct to access its fields
let Note {id, title: _, body: _} = note;
// Delete the object ID, effectively deleting the note
sui::object::delete(id)
}
}
1.2 Breaking Down the Code
Let's break down the code above. The contract is pretty straightforward.
-
Structs: It has two structs:
NotesandNote.- The
Notesstruct is a shared object (indicated bytransfer::share_objectininit). It could be used to track global state, though in this simple version it just holds a UID. - The
Notestruct represents an individual note. It haskeyandstoreabilities, meaning it can be owned by an address and transferred. It stores theid,title, andbodyof the note.
- The
initFunction: This function runs once when the package is published. It initializes theNotesstruct and shares it, making it accessible to everyone.create_noteFunction: This function allows any user to create a new note. It takes a title and body, creates aNoteobject, and transfers it to the sender (tx_context::sender(ctx)). This means the caller becomes the owner of the note.delete_noteFunction: This function allows the owner of a note to delete it. It takes theNoteobject by value (consuming it), unpacks it, and deletes itsUID.
1.3 Writing Comprehensive Tests
Testing is crucial for smart contract development. We will write a test module to verify that we can create and delete notes correctly.
Create a new file sources/notes_tests.move (or add this to your existing test file):
#[test_only]
module dacade_zklogin::notes_tests {
use dacade_zklogin::notes::{Self, Note};
use sui::test_scenario;
use std::string::{Self};
#[test]
fun test_create_and_delete_note() {
let user = @0xA;
// Start a test scenario with 'user' as the sender
let scenario = test_scenario::begin(user);
// 1. Create a note
{
let ctx = test_scenario::ctx(&mut scenario);
let title = string::utf8(b"My First Note");
let body = string::utf8(b"This is the body of my note.");
notes::create_note(title, body, ctx);
};
// 2. Verify note creation and ownership
test_scenario::next_tx(&mut scenario, user);
{
// Try to take the Note object from the sender's inventory
let note = test_scenario::take_from_sender<Note>(&scenario);
// If we successfully took it, it means the note was created and transferred to the user.
// Now, let's delete it.
// 3. Delete the note
let ctx = test_scenario::ctx(&mut scenario);
notes::delete_note(note, ctx);
};
// 4. Verify note deletion
test_scenario::next_tx(&mut scenario, user);
{
// To verify deletion, we check that the user no longer has the note.
// has_most_recent_for_sender returns false if the object is no longer accessible/owned.
assert!(!test_scenario::has_most_recent_for_sender<Note>(&scenario), 0);
};
test_scenario::end(scenario);
}
}
1.4 Running the Tests
To run the tests, open your terminal in the contract directory and run:
sui move test
You should see output indicating that the test passed:
Running Move unit tests
[ PASS ] dacade_zklogin::notes_tests::test_create_and_delete_note
Test result: OK. Total tests: 1; passed: 1; failed: 0
3.5 Publishing the Contract
Now that we have tested our contract, it's time to publish it to the Sui Devnet.
-
Switch to Devnet: Ensure your Sui client is connected to the devnet environment.
sui client switch --env devnet -
Publish: Run the publish command. We set a gas budget to ensure the transaction has enough gas to execute.
sui client publish --gas-budget 100000000Note: If you haven't funded your address yet, run
sui client faucetto get some test SUI.
3.6 Saving Important Information
When you publish a package, the Sui CLI outputs a transaction summary. This output contains critical information that you must save for connecting your frontend application to the smart contract later.
Look for the Object Changes section in the output. You need to identify and save the following IDs:
-
Package ID: This is the ID of the published code. It is listed under
Published Objects. -
Shared Object ID: In our
initfunction, we created aNotesobject and shared it. You need to find the ID of this object. It will be listed underCreated ObjectswithOwner: Shared.
Example Output Analysis:
╭──────────────────────────────────────────────────────────────────────────────────────────────────╮
│ Object Changes │
├──────────────────────────────────────────────────────────────────────────────────────────────────┤
│ Created Objects: │
│ ┌── │
│ │ ObjectID: 0x81833f94d4019d03fdc0d63abdf179950740f513bf434ae182e1031e0568a2b7 │
│ │ Owner: Shared( 5 ) │
│ │ ObjectType: 0xfea5f1839d675fa03d5b02208adf2e6167336b4646399cb2248b9a7347a48998::notes::Notes │
│ └── │
...
│ Published Objects: │
│ ┌── │
│ │ PackageID: 0xfea5f1839d675fa03d5b02208adf2e6167336b4646399cb2248b9a7347a48998 │
│ │ Version: 1 │
│ │ Digest: 93isZkd3bCKDqeMUL1wUhWnxYvJT4KLc72U6buFAPS4F │
│ │ Modules: notes │
│ └── │
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
What to save:
-
Package ID:
0xfea5...8998(from thePublished Objectssection) -
Notes Shared Object ID:
0x8183...a2b7(from theCreated Objectssection where Owner isShared)
Tip: Create a
.envfile or aconstants.tsfile in your frontend project now and save these values. You will need them to interact with the contract from the UI.
Congratulations! If you’ve made it this far, give yourself a well-deserved pat on the back, you’re officially wearing the blockchain pro badge now. 😄
You’ve successfully written, tested, and published your first Sui Move smart contract. Not bad at all.
In the next part, we’ll put these concepts to work by building the core components of the DeepBook DEX frontend, where theory meets practice and things start getting seriously interesting.
Top comments (0)