How to develop smart contract programs on Sui

Running Web2 applications on Amphitheatre differs from running Web3 applications, therefore, before deploying Sui smart contracts, you need to first build the contracts, then deploy them to the DevNet within the cluster to observe the execution of the contracts.

The Example Application

You can get the code for the example from the GitHub repository. Just git clone https://github.com/amphitheatre-app/amp-example-sui to get a local copy.

The amp-example-sui application is, as you'd expect for an example, small. It's a Sui smart contract.

Here's all the code from sources/example.move:

// Copyright (c) Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

module first_package::example {
    use sui::object::{Self, UID};
    use sui::transfer;
    use sui::tx_context::{Self, TxContext};

    struct Sword has key, store {
        id: UID,
        magic: u64,
        strength: u64,
    }

    struct Forge has key {
        id: UID,
        swords_created: u64,
    }

    /// Module initializer to be executed when this module is published
    fun init(ctx: &mut TxContext) {
        let admin = Forge {
            id: object::new(ctx),
            swords_created: 0,
        };

        // transfer the forge object to the module/package publisher
        transfer::transfer(admin, tx_context::sender(ctx));
    }

    // === Accessors ===

    public fun magic(self: &Sword): u64 {
        self.magic
    }

    public fun strength(self: &Sword): u64 {
        self.strength
    }

    public fun swords_created(self: &Forge): u64 {
        self.swords_created
    }

    /// Constructor for creating swords
    public fun new_sword(
        forge: &mut Forge,
        magic: u64,
        strength: u64,
        ctx: &mut TxContext,
    ): Sword {
        forge.swords_created = forge.swords_created + 1;
        Sword {
            id: object::new(ctx),
            magic: magic,
            strength: strength,
        }
    }

    // === Tests ===
    #[test_only] use sui::test_scenario as ts;

    #[test_only] const ADMIN: address = @0xAD;
    #[test_only] const ALICE: address = @0xA;
    #[test_only] const BOB: address = @0xB;

    #[test]
    public fun test_module_init() {
        let ts = ts::begin(@0x0);

        // first transaction to emulate module initialization.
        {
            ts::next_tx(&mut ts, ADMIN);
            init(ts::ctx(&mut ts));
        };

        // second transaction to check if the forge has been created
        // and has initial value of zero swords created
        {
            ts::next_tx(&mut ts, ADMIN);

            // extract the Forge object
            let forge: Forge = ts::take_from_sender(&ts);

            // verify number of created swords
            assert!(swords_created(&forge) == 0, 1);

            // return the Forge object to the object pool
            ts::return_to_sender(&ts, forge);
        };

        ts::end(ts);
    }

    #[test]
    fun test_sword_transactions() {
        let ts = ts::begin(@0x0);

        // first transaction to emulate module initialization
        {
            ts::next_tx(&mut ts, ADMIN);
            init(ts::ctx(&mut ts));
        };

        // second transaction executed by admin to create the sword
        {
            ts::next_tx(&mut ts, ADMIN);
            let forge: Forge = ts::take_from_sender(&ts);
            // create the sword and transfer it to the initial owner
            let sword = new_sword(&mut forge, 42, 7, ts::ctx(&mut ts));
            transfer::public_transfer(sword, ALICE);
            ts::return_to_sender(&ts, forge);
        };

        // third transaction executed by the initial sword owner
        {
            ts::next_tx(&mut ts, ALICE);
            // extract the sword owned by the initial owner
            let sword: Sword = ts::take_from_sender(&ts);
            // transfer the sword to the final owner
            transfer::public_transfer(sword, BOB);
        };

        // fourth transaction executed by the final sword owner
        {
            ts::next_tx(&mut ts, BOB);
            // extract the sword owned by the final owner
            let sword: Sword = ts::take_from_sender(&ts);
            // verify that the sword has expected properties
            assert!(magic(&sword) == 42 && strength(&sword) == 7, 1);
            // return the sword to the object pool (it cannot be dropped)
            ts::return_to_sender(&ts, sword)
        };

        ts::end(ts);
    }
}

Building the Application

As with most Sui applications, a simple sui move build will create a binary which we can run. So, the raw application works. Now to package it up for Amphitheatre.

Install Amphitheatre

We are ready to start working with Amphitheatre and that means we need amp, our CLI app for managing apps on Amphitheatre. If you've already installed it, carry on. If not, hop over to our installation guide.

Inside .amp.toml

The .amp.toml file now contains a default configuration for deploying your Character. If we look at the .amp.toml file we can see it in there:

name = "amp-example-sui"
version = "0.1.0"
edition = "v1"
description = "A simple Sui example app"
readme = "README.md"
homepage = "https://github.com/amphitheatre-app/amp-example-sui"
repository = "https://github.com/amphitheatre-app/amp-example-sui"
license = "Apache-2.0"
license-file = "LICENSE"
keywords = ["example", "sui", "getting-started"]
categories = ["example"]

[build]
builder = "ghcr.io/amp-buildpacks/move-builder"

[partners]
sui = { version = "stable", registry = "catalog" }

[build.env]
BP_ENABLE_SUI_DEPLOY = "false"
# BP_SUI_DEPLOY_PRIVATE_KEY = "<Your-Deploy-Private-Key>"

The amp command will always refer to this file in the current directory if it exists, specifically for the Character name value at the start. That name will be used to identify the Character to the Amphitheatre platform. The rest of the file contains settings to be applied to the Character when it deploys.

See the move-builder documentation for more options.

Deploying to Amphitheatre

To deploy your Character, just run:

amp run

This will lookup our .amp.toml file, and get the Character name amp-example-sui from there. Then amp will start the process of deploying our Character to the Amphitheatre platform. amp will return you to the command line when it's done.

Arrived at Destination

You have successfully built, deployed your first Sui application on Amphitheatre.