Deploying and interacting with a Starknet smart contract with Remix

This topic previously appeared in the Starknet Book.

To compile, deploy, and interact with our smart contract, use the Starknet Remix plugin. This tool enables you to start to develop for Starknet without the need for any local installations on your machine.

Setting up your environment in Remix

  1. Go to the Remix IDE website with the Starknet plugin activated.

  2. Click the File Explorer File Explorer tab to review the details of the example project.

  3. Click the Starknet Starknet tab, and click the Settings. Select the latest version of Cairo available in Remix.

  4. In the File explorer, open the Scarb.toml file to verify the version of your project. Ensure it matches the version specified originally, and correct any discrepancies, if necessary.

Customizing your environment for the Ownable contract

For this tutorial, a default example project is provided. Modify or remove certain files and directories as needed.

  1. Rename the root directory to ownable. In your Scarb.toml, under the [package] section, set the name to ownable.

  2. Under src/, delete the balance.cairo and forty_two.cairo files, if they exist.

  3. Open lib.cairo and delete all its contents.

Exploring the ownable Starknet smart contract

Explore the Example Ownable contract, crafted in Cairo for Starknet. It includes:

  • An ownership system.

  • A method to transfer ownership.

  • A method to check the current owner.

  • An event notification for ownership changes.

Example: ownable Cairo contract

use starknet::ContractAddress; (1)

#[starknet::interface]
trait OwnableTrait<T> { (1)
    fn transfer_ownership(ref self: T, new_owner: ContractAddress); (2)
    fn get_owner(self: @T) -> ContractAddress; (2)
}

#[starknet::contract]
mod Ownable {
    use super::ContractAddress;
    use starknet::get_caller_address;

    #[event]
    #[derive(Drop, starknet::Event)]
    enum Event {
      OwnershipTransferred: OwnershipTransferred, (3)
    }

    #[derive(Drop, starknet::Event)]
    struct OwnershipTransferred { (3)
        #[key]
        prev_owner: ContractAddress,
        #[key]
        new_owner: ContractAddress,
    }

    #[storage]
    struct Storage { (4)
        owner: ContractAddress,
    }

    #[constructor]
    fn constructor(ref self: ContractState, init_owner: ContractAddress) { (5)
        self.owner.write(init_owner);
    }

    #[abi(embed_v0)]
    impl OwnableImpl of super::OwnableTrait<ContractState> {
        fn transfer_ownership(ref self: ContractState, new_owner: ContractAddress) {
            self.only_owner();
            let prev_owner = self.owner.read();
            self.owner.write(new_owner);
            self.emit(Event::OwnershipTransferred(OwnershipTransferred {
                prev_owner: prev_owner,
                new_owner: new_owner,
            }));
        }

        fn get_owner(self: @ContractState) -> ContractAddress {
            self.owner.read()
        }
    }

    #[generate_trait]
    impl PrivateMethods of PrivateMethodsTrait {
        fn only_owner(self: @ContractState) { (6)
            let caller = get_caller_address();
            assert(caller == self.owner.read(), 'Caller is not the owner');
        }
    }
}

Contract component breakdown

1 Dependencies and Interface:
starknet::ContractAddress: Represents a Starknet contract address. OwnableTrait: Defines essential functions for transferring and retrieving ownership.
2 External Functions: Contains functions for transferring ownership and fetching information about the current owner.
3 Events: OwnershipTransferred indicates changes in ownership, providing details about the previous and new owners.
4 Storage: Stores the contract’s state, including the address of the current owner.
5 Constructor: Sets up the contract by assigning an initial owner.
6 Private Methods: only_owner validates if the caller is the current owner.

Compiling the contract

To compile using Remix:

  1. Click the File Explorer File Explorer tab, open the lib.cairo file and insert the code from Example: ownable Cairo contract.

  2. Click the Starknet Starknet tab, then click Home.

  3. Under (1) Compile, click Compile lib.cairo.

  4. Grant the necessary permissions when prompted. Select Remember my choice for a smoother compilation process in the future.

The compilation process creates an artifacts directory containing the compiled contract in two formats: a Sierra file, in JSON format, and a Casm file. For Starknet deployment, Remix uses the Sierra file.

Deploying your contract on the development network (devnet)

Deploying a smart contract in Starknet requires two high-level steps:

  1. Declare the class of your contract, that is, send your contract’s code to the network.

    When you declare the contract class, you establish an initial owner by calling the class’s constructor function.

  2. Deploy an instance of the contract class.

This tutorial uses a development network (devnet) to deploy your smart contract. A devnet is a Starknet instance that you run as a local node. A devnet enables much quicker development than is possible using testnet, as well as providing privacy prior to launching on testnet.

Declaring the contract class
  1. Select the network by clicking the Starknet tab, and then clicking the Remote Devnet menu.

  2. Under Devnet account selection, open the menu to view a list of accounts specific to the designated devnet.

  3. Select a devnet account from the list and note its address for later use.

  4. Click the Declare lib.cairo button.

    Remix’s terminal provides various logs with important details such as:

    • transaction_hash: This unique hash identifies the transaction and can be used to track its status.

    • class_hash: Similar to an identifier, the class hash contains the definition of the smart contract.

    Remix terminal output after declaring the ownable contract
    ------------------------ Declaring contract: lib.cairo ------------------------
    {
      "transaction_hash": "0x36dabf43f4962c97cf67ba132fb520091f268e7e33477d77d01747eeb0d7b43",
      "class_hash": "0x540779cd109ad20f46cb36d8de1ce30c75469862b4dc75f2f29d1b4d1454f60"
    }
    ---------------------- End Declaring contract: lib.cairo ----------------------

After Remix declares the contract class, the Declare button says Declared lib.cairo.

Now you’re ready to deploy an instance of the contract class.

Deploying a contract instance
  1. Paste the Devnet account address you used into the init_owner variable.

    init owner field

  2. Click Deploy.

After deployment, Remix’s terminal displays various logs, including a transaction receipt, containing important details, such as:

  • transaction_hash: This unique hash identifies the transaction and can be used to track its status.

  • contract_address: The address of the deployed contract. You can use this address to interact with your contract.

  • data: Contains the init_owner address provided to the constructor.

Remix terminal output after deploying an instance of the Ownable class in lib.cairo
------------------------ Deploying contract: lib.cairo ------------------------

{
  "transaction_hash": "0x624f5b9f57e53f6b5b62e588f0f949442172b3ad5d04f0827928b4d12c2fa58",
  "contract_address": [
    "0x699952dc736661d0ed573cd2b0956c80a1602169e034fdaa3515bfbc36d6410"
  ]
    ...
  "data": [
        "0x6b0ee6f418e47408cf56c6f98261c1c5693276943be12db9597b933d363df",
         ...
      ]
    ...
}
---------------------- End Deploying contract: lib.cairo ----------------------

Interacting with the contract

Now that the contract is operational on the development network, you can start interacting with it on the Starknet tab, under (3) Interact.

Identifying the owner of the contract instance

  • Under Read you should see the get_owner() function. Click the Call button. The function doesn’t require any arguments so the calldata field remains empty. This function reads data, so its invocation is referred to as a call.

The terminal displays the output, showing the owner’s address, which you provided during the contract’s deployment within the calldata for the constructor:

------------------- Calling get_owner ------------------------
{
  "resp": {
    "result": [
      "0x6b0ee6f418e47408cf56c6f98261c1c5693276943be12db9597b933d363df"
    ]
  },
  "contract": "lib.cairo",
  "function": "get_owner"
}
------------------- End calling get_owner --------------------

This call doesn’t consume gas because the function doesn’t modify the contract’s state.

Transferring ownership of the contract instance

  1. Under (3) Interact, select Write, where functions that alter the contract’s state are listed.

  2. Select the transfer_ownership() function, which requires providing the new owner address as input.

  3. Fill in the new_owner field with any Devnet address other than the one you used to deploy the contract.

    Under Devnet account selection, open the menu, select a Devnet account from the list, and copy its address.

  4. Click the Call button. The terminal displays the transaction hash indicating the change in the contract’s state. Because this interaction is an INVOKE transaction, and it modifies the contract’s state. An INVOKE transaction requires the signature of the account executing the function.

    For INVOKE transactions, the terminal logs include a finality_status parameter indicating the outcome. A status of ACCEPTED_ON_L2 indicates approval by the Sequencer, the entity responsible for receiving and processing transactions, indicating inclusion in an upcoming block. Conversely, a REJECTED status indicates that the Sequencer did not approve the transaction, preventing its inclusion in the next block. Typically, transactions of this nature are approved, resulting in a modification of the contract’s state.

---------- Invoke transfer_ownership transaction receipt ----------------
{
  "resp": {
    "transaction_hash": "0x5495d56633745aa3b97bdb89c255d522e98fd2cb481974efe898560839aa472"
  },
  "contract": "lib.cairo",
  "function": "get_owner"
}
----------End Invoke transfer_ownership transaction receipt -------------

Deployment on Starknet testnet

After testing your smart contract on a development network, the next step is deploying it onto the Starknet testnet. The Starknet testnet is a public platform accessible to all, providing an excellent environment for testing smart contracts and encouraging collaboration among developers.

Setting up a smart wallet and a Starknet account on testnet

Before deploying your smart contract on Starknet, it’s crucial to address transaction costs. While deploying on the Starknet testnet is free, having an operational smart wallet account is essential. You can set up a smart wallet and a Starknet account using either of the following platforms:

Both options offer robust Starknet wallets with advanced security measures and enhanced accessibility features enabled by the capabilities of the Cairo VM.

Here’s how to set up your smart wallet:
  1. Install the recommended browser extension corresponding to your chosen wallet.

  2. Follow the instructions provided by your wallet provider to deploy your account on testnet.

  3. Use the Starknet Faucet to fund your account.

  4. Execute the deployment of your account onto the network, typically completed within approximately 10 seconds.

Once the setup is complete, you are primed to deploy your smart contracts onto the Starknet testnet.

Deployment and Interaction

  1. Proceed as per the aforementioned deployment steps.

  2. Within the Environment selection tab, Select Wallet.

  3. Select your Starknet account and proceed with the deployment and interaction processes for your contract.

You can monitor transaction hashes and addresses by using various Starknet block explorers such as:

These block explorers offer a graphical depiction of transactions and modifications to the contract state. Noteworthy is the visibility provided when altering contract ownership through the transfer_ownership() function, as the emitted event by the contract becomes observable within the block explorer. This mechanism serves as a potent means to monitor contractual events.