Skip links

Table of Contents

How to Write Solidity Smart Contracts

How-to-Write-Solidity-Smart-Contracts

Smart contracts are like digital wizards—self-executing agreements with predefined rules, all meticulously encoded. They’re the enchanting core of blockchain technology, promising a world of transparency and security. Among the enchanters, Ethereum stands tall as one of the foremost blockchain platforms, and it wields a magical language called Solidity to craft these digital spells.

In this guide, we’ll be your trusty guides on this mystical journey, unraveling the arcane secrets of Solidity, and together, we’ll conjure your very first one of solidity smart contracts in just 10 enchanting steps. So, prepare to embark on an adventure where code meets conjuration!

What are the Prerequisites?

Before we delve into the enchanting world of Solidity Smart Contracts, there are a few things to consider:

  1. Basic Programming Knowledge: To navigate this magical realm, it’s advisable to have a foundation in programming, preferably in a language like JavaScript or something similar to it.
  2. Development Environment: You’ll need a well-prepared development environment equipped with the Solidity compiler. For a swift initiation, we recommend the Remix IDE, which is a powerful environment developed by the Ethereum team.
  3. Blockchain Understanding (Optional): While not an absolute requirement, a basic comprehension of blockchain and Ethereum can be your guiding star.

With these prerequisites in place, you’re poised to embark on your journey into the realm of Solidity and smart contracts. Prepare to weave your digital spells!

How to Write Solidity Smart Contracts?

In this enchanting journey, we’ll embark on a quest to create a delightful contract—a “Message of the Day” oracle. Our contract will evolve in complexity as we proceed, allowing us to unearth a myriad of Solidity’s secrets and capabilities.

So, fasten your seatbelts as we set out to explore the fascinating world of Solidity, one spell at a time!

And remember, while this journey may stretch on a bit, fear not! We’re committed to covering every fundamental aspect of Solidity, ensuring you’re well-prepared for your magical coding endeavors.

1. Setting Up Solidity

Our initial step is to configure the Remix environment, ensuring it’s primed for our journey into Solidity. Here’s how you can do it:

1.Click on the “Workspaces” drop-down located at the top left corner.

remix idesolidity smart contractssmart contracts with solidity

2. Select “Create a New Workspace.”

3. Opt for the “Blank” template; you have the liberty to assign it any name that appeals to you, such as “Intro-to-Solidity.”

4. Below the workspace, locate the file icon and click on it to generate a new file. This file can be named as you prefer, but it must end in “.sol”. For our purposes, we shall name it “message.sol.”

With this configuration in place, we stand ready to embark on our coding expedition!

2. The Basics

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;

contract MessageOfTheDay {
    string public message = "Be kind!";
}

Every Solidity smart contract starts with two crucial elements at the top: its license and compiler version. While the license isn’t always mandatory, it’s a good practice to include it as it clarifies how your contract can be used for commercial purposes. As for the compiler version, it’s necessary because it provides essential information to the Ethereum Virtual Machine (EVM) on how to process your code. Newer compiler versions often bring performance enhancements and security updates, making it wise to specify the most up-to-date version when initiating a new project.

Following this, we declare our contract which is named “MessageOfTheDay.” Within it, we define our message, which currently reads as “Be kind!” Take a look at how we structure this message declaration with the following syntax: <type> <visibility> <name>. This represents the template for defining any variable in Solidity. The name can be customized to your preference, as long as it doesn’t clash with Solidity’s reserved keywords. As for the type and visibility themselves, we’ll delve deeper into them in a bit.


Before we proceed, let’s ensure you know how to compile a contract within Remix. To compile your contract, you have two options:

  • Click on the play button located above the contract code.
  • Press “Ctrl + S” as a shortcut.

A successful compilation will be indicated by a green check-mark next to the compiler icon on the left-hand side of the screen.

Once your contract is compiled, you can deploy it. Click on the deploy icon on the left, which will open a tab displaying your contract’s name and a deploy button beneath it.

After clicking “Deploy,” your contract will appear under the “Deployed Contracts” section. Click on the “Message” button within it and take note of what appears beneath the button—it’s our message!

As it stands, our message is hard-coded into the contract. Let’s now evolve it to introduce dynamic behavior!

3. Initializing The Contract

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;

contract MessageOfTheDay {
    string public message;

    constructor(string memory initialMessage) {
        message = initialMessage;
    }
}

In this section, we introduce two critical concepts: the constructor and the memory keyword. Let’s explore their significance:

The Constructor: Think of the constructor as the grand conductor that orchestrates the contract’s initial symphony. It’s the function that runs when you deploy a contract, responsible for executing code right at the contract’s creation. Typically, constructors are used to initialize contract variables.

The Memory Keyword: Now, the memory keyword is a unique gem in the realm of Solidity. It comes into play with data types that are not primitive and serves as a directive to the program regarding where these non-primitive variables reside. In this case, “initialMessage” finds its home in the program’s memory and will cease to exist once the constructor function completes its enchantment.

After this, we perform a simple task—assigning the message variable to our initial message. To see the magic in action, compile and deploy the program once more. You’ll notice an input that allows you to provide your initial message.

But our message is not done evolving; we need a way to change it beyond the confines of the constructor, and that’s where functions take center stage.

4. Change is Good

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;

contract MessageOfTheDay {
    string public message;

    constructor(string memory initialMessage) {
        message = initialMessage;
    }

    function setMessage(string memory newMessage) external {
        message = newMessage;
    }
}

As you can see, there’s nothing special about what our function does, it just takes a bunch of input and processes it like any other functions would do. However, you may have noticed a new keyword “external” after our function name. “external” means our function can only be called outside of the contract, and not within the contract itself. This is another type of visibility other than “public”, however this one is only relative in the context of functions.

Now I know what you’re thinking: can’t we just change the message to whatever at any time? Doesn’t that defeat the whole purpose of “daily” in daily message? And you’re correct, so let’s start implementing some conditions to make sure our contract runs as expected.

At first glance, our function may seem rather ordinary—it accepts input and processes it, much like any other function. However, there’s a subtle addition you might have noticed: a new keyword, “external,” just after the function name. This “external” keyword bestows our function with a unique power—it can only be invoked from outside the contract, and not from within it. This introduces a new dimension of visibility, distinct from the familiar “public,” however this one is only relative in the context of functions.

Now, you might be pondering a curious thought: Can’t we simply change the message whenever we please? Wouldn’t that render the concept of a “daily” message somewhat pointless? Your intuition is spot on, and we’re about to ensure our contract adheres to its daily mission.

5. Setting Some Healthy Boundaries

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;

contract MessageOfTheDay {
    uint256 public constant MIN_TIME_ELAPSED = 1 days;
    string public message;
    uint256 public messageSetAt;

    constructor(string memory initialMessage) {
        message = initialMessage;
        messageSetAt = block.timestamp;
    }

    function setMessage(string memory newMessage) external {
        if (messageSetAt <= block.timestamp - MIN_TIME_ELAPSED) {
            message = newMessage;
        }
    }
}

Wait a minute… What’s all of this?!

Hold your horses, let’s unravel this magical tapestry one thread at a time!

First and foremost, we’ve introduced the “MIN_TIME_ELAPSED” constant, a beacon that guides our contract. This constant, defined with the “uint256” type, signifies a positive integer capable of reaching up to a value of 2256-1. The “constant” keyword, as it implies, means that our constant shall remain unchanging, engraving it into the contract which increases efficiency.


Now, feast your eyes upon “1 days,” a testament to Solidity’s knack for time-based wizardry. Since blockchain contracts often dance to the rhythm of time, Solidity offers a suite of predefined time-related values, simplifying time-based calculations. Clicking on “MIN_TIME_ELAPSED” reveals its secret: the value 86400, representing the number of seconds in a day.

The “messageSetAt” variable is our timekeeper, marking the moment a message is set.

Now, let’s talk about “block.timestamp.” It’s like a cosmic clock that ticks in every blockchain transaction. The “block” is a global entity accessible by any contract, revealing attributes of the current block in which the contract transaction resides. In simpler terms, when a contract function is called, it operates within a block, and that same block provides essential data.


Here, we’re checking the timestamp of the block—a number expressing the current time in seconds. If the time when the message was last set is earlier than the current time minus a day, it signals that a full day has elapsed since the previous message. This allows us to usher in a new message.

We’ve woven the threads of basic checking into our contract, but now it’s time to provide our users with a little more guidance and feedback on the success or failure of their message-setting.

6. Spreading The News

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;

contract MessageOfTheDay {
    error NotEnoughTimePassedSinceLastMessage();
    event NewMessageSet(string message);

    uint256 public constant MIN_TIME_ELAPSED = 1 days;
    string public message;
    uint256 public messageSetAt;

    constructor(string memory initialMessage) {
        message = initialMessage;
        messageSetAt = block.timestamp;
    }

    function setMessage(string memory newMessage) external {
        if (block.timestamp - MIN_TIME_ELAPSED < messageSetAt) {
            revert NotEnoughTimePassedSinceLastMessage();
        }

        message = newMessage;
        emit NewMessageSet(newMessage);
    }
}

At the top of our mystical contract, behold the additions we’ve conjured: an “Error” and an “Event.”

Errors are companions to the “revert” keyword, which halts a function’s execution and undoes any previous changes. We’ve bestowed upon our contract the error “NotEnoughTimePassedSinceLastMessage,” pretty clear and self-explanatory. You could choose to name it differently, perhaps as “FailedToSetMessage,” with an explanatory argument if you prefer.

Notice that we’ve tweaked our check condition. It now seeks to catch attempts to set a message less than 24 hours after the previous one. This adjustment allows us to emit an error early in the function, minimizing potential side effects and enhancing performance.

In addition to an error, we’ve introduced an “event.” Solidity employs events to broadcast changes across the blockchain. Events can carry arguments, and in our case, it carries the new message. Importantly, events serve as a public announcement, eliminating the need for others to manually inspect contract changes.

As a reminder, the blockchain inherently tracks all changes, and anyone can verify whether the contract’s message has been altered. Events are not record-keepers but rather messengers, efficiently broadcasting updates to all interested parties. Here’s something that can help you learn more about them.

Now, let’s dive into the heart of the matter—the enchanting world of Ether and the fees for setting our daily message. Get ready for some magical finance!

7. Nothing Comes For Free

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;

contract MessageOfTheDay {
    error NotEnoughTimePassedSinceLastMessage();
    error NotEnoughEther();
    event NewMessageSet(string message);

    uint256 public constant MIN_TIME_ELAPSED = 1 days;
    uint256 public constant MIN_ETHER_SENT = 0.01 ether;
    string public message;
    uint256 public messageSetAt;

    constructor(string memory initialMessage) {
        message = initialMessage;
        messageSetAt = block.timestamp;
    }

    function setMessage(string memory newMessage) external payable {
        if (block.timestamp - MIN_TIME_ELAPSED < messageSetAt) {
            revert NotEnoughTimePassedSinceLastMessage();
        }
        if (msg.value < MIN_ETHER_SENT) {
            revert NotEnoughEther();
        }

        message = newMessage;
        emit NewMessageSet(newMessage);
    }
}

Let’s break down this enchanted transformation, step by step:

First, we’ve summoned two new constants: “MIN_ETHER_SENT” and the accompanying error “NotEnoughEther.” These define the minimum amount of Ether required for a successful function call and inform users if their offering falls short.

Next, there’s the “payable” spell. This tags our function as capable of receiving Ether—a necessity when dealing with money in Solidity. It’s essential to note that for a function to accept Ether, it must also be marked as “external” or “public.” Lucky for us, this is already the case.

Now, the “msg” keyword enters the stage. Like the “block” we’ve introduced earlier, when a user calls a function, the “msg” emerges as a global variable, bearing vital information about the call to this function. Among its attributes, the most used are “value” (the amount of Wei sent) and “sender” (the sender’s address).

To clarify, Wei is a smaller denomination of Ether, ideal for microtransactions. You can convert between Wei and Ether by dividing or multiplying by 1018 , or using an online conversion tool.

Phew—everything is set. Or is it…

Given the public nature of blockchain contracts, anyone can tamper with the message. And right now our Ether is locked behind the bars of the contract’s chamber. We desire instant message-changing abilities and the power to withdraw Ether, but these privileges must be held only by a trusted user.

The answer? We introduce a privileged user. This user, upon deploying the contract, becomes the gatekeeper of these extraordinary powers.

8. All Hail The King

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;

contract MessageOfTheDay {
    error NotEnoughTimePassedSinceLastMessage();
    error NotEnoughEther();
    error NotAuthorized();
    event NewMessageSet(string message);

    uint256 public constant MIN_TIME_ELAPSED = 1 days;
    uint256 public constant MIN_ETHER_SENT = 0.01 ether;
    address public immutable owner;
    string public message;
    uint256 public messageSetAt;

    constructor(string memory initialMessage) {
        message = initialMessage;
        messageSetAt = block.timestamp;
        owner = msg.sender;
    }

    modifier onlyOwner() {
        if (msg.sender != owner) {
            revert NotAuthorized();
        }
        _;
    }

    function setMessage(string memory newMessage) external payable {
        if (block.timestamp - MIN_TIME_ELAPSED < messageSetAt) {
            revert NotEnoughTimePassedSinceLastMessage();
        }
        if (msg.value < MIN_ETHER_SENT) {
            revert NotEnoughEther();
        }

        message = newMessage;
        emit NewMessageSet(newMessage);
    }

    function forceSetMessage(string memory newMessage) external onlyOwner {
        message = newMessage;
        emit NewMessageSet(newMessage);
    }
}

As you might have guessed, it’s time to crown an owner for the contract, a guardian with administrative powers. And who could that be? Well, it’s none other than you! It’s a customary practice for the person deploying the contract to also become its rightful owner.

In this enchanted scroll, behold the following transformations:

We’ve introduced a key character: the “owner” of the contract. This noble entity is assigned an “address” type, which is instrumental in tracking accounts or wallets on the blockchain. The “immutable” attribute ensures that this variable must be set during the contract’s creation (the constructor) and remains unaltered thereafter.

The constructor ritual bestows ownership upon the deployer. The “owner” is set to “msg.sender.” The “msg.sender” represents the address calling the function. In this case, in the context of the constructor, it’s the address of the person deploying the contract (you!), making you the esteemed owner.

Enter the “modifier,” a mystical cloak that wraps around other functions. We’ve applied the “onlyOwner” modifier to the “forceSetMessage” function, ensuring that only the owner possesses the privilege to invoke it.

Modifiers, just like functions, can accept arguments, which weren’t a necessity for us. The underscore “_” is also a special symbol in modifiers. It represents a placeholder where the actual function code will be inserted. When “forceSetMessage” is called, the code in “onlyOwner” is executed first, and then control is passed to the function. The _; allows this transition.

The modifier also has some interesting properties of its own. Firstly just like a function, you can give it arguments that can be passed when it’s being added to a function, but we didn’t need to here. Secondly, you can see that it ends with an underscore, which represents the code where the wrapped function should run. In this case, the “forceSetMessage” runs after the modifier does, because the underscore is placed after the modifier code.

The placement of the underscore within a modifier can vary based on your specific requirements. You have the flexibility to position it before the modifier’s code or even within the middle of the modifier’s code, creating what we might whimsically call a “modifier sandwich.”

Now, moving on to the final piece of the puzzle, we shall exile all those who have written malicious messages.

9. You’re Not Welcome Here

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;

contract MessageOfTheDay {
    error NotEnoughTimePassedSinceLastMessage();
    error NotEnoughEther();
    error NotAuthorized();
    event NewMessageSet(string message);

    uint256 public constant MIN_TIME_ELAPSED = 1 days;
    uint256 public constant MIN_ETHER_SENT = 0.01 ether;
    address public immutable owner;
    string public message;
    uint256 public messageSetAt;
    mapping(address => bool) private _bannedAddresses;

    constructor(string memory initialMessage) {
        message = initialMessage;
        messageSetAt = block.timestamp;
        owner = msg.sender;
    }

    modifier onlyOwner() {
        if (msg.sender != owner) {
            revert NotAuthorized();
        }
        _;
    }

    function setMessage(string memory newMessage) external payable {
        if (block.timestamp - MIN_TIME_ELAPSED < messageSetAt) {
            revert NotEnoughTimePassedSinceLastMessage();
        }
        if (msg.value < MIN_ETHER_SENT) {
            revert NotEnoughEther();
        }
        if (_bannedAddresses[msg.sender]) {
            revert NotAuthorized();
        }

        message = newMessage;
        emit NewMessageSet(newMessage);
    }

    function forceSetMessage(string memory newMessage) external onlyOwner {
        message = newMessage;
        emit NewMessageSet(newMessage);
    }

    function banAddress(address addressToBan) external onlyOwner {
        _bannedAddresses[addressToBan] = true;
    }
}

In the mystical world of smart contracts, we introduce a powerful spellbook known as a “mapping.” This dictionary of spells allows us to associate addresses with secret boolean spells, where “true” signifies a mystical banishment, and “false” represents freedom.

At the start, this spellbook is blank, and all addresses wander freely. However, the wizardly “banAddress” function bestows the owner with the ability to impose a ban upon any address. By invoking this function, the owner marks an address as bewitched, setting its corresponding spell in the mapping to “true.”

Now, as mere mortals interact with the “setMessage” spell, a subtle check transpires. If an address has fallen under the spell of banishment, an error  materializes, preventing any further magic.

A magical note to ponder: While we’ve concealed the “_bannedAddresses” mapping from prying eyes by marking it as “private” within the contract’s enchanted realm, remember that the blockchain unveils all secrets to those who seek them. Thus, external observers can discern the banishment status of addresses.

However, contracts wield their own spells and can communicate with one another. This is something we shall explore in another journey. For the purpose of this one, all the knowledge you need to know it that “private” variables are invisible between contracts, and only between contracts.

We’ve arrived at the grand finale of our mystical journey through the enchanted realm of Solidity and smart contracts.

Hold! We overlooked a step you say?

Well, not at all! The enchanting truth about this last step is that it’s a blank canvas awaiting your magical touch.

The Master of the Chain

In the enchanted realm of Ethereum, you have the power to add more features to your contract. Consider these possibilities:

  1. Admin Council: Create a group of trusted individuals with special privileges to manage the contract when you’re not available. They can be like a council of wizards who help oversee the contract’s affairs.
  2. Bidding War: Introduce a system where users compete by bidding with their Ether to earn the right to set the daily message. The user with the highest bid will be able to decide the message of the day.
  3. Vote for the Message: Implement a voting system where users can cast their votes for their favorite messages. The message with the most votes will be chosen as the daily message, giving the community a voice in deciding the message.

Remember, in the world of Ethereum, you have the ability to add new features and engage the community. Use your programming skills to bring these ideas to life and shape the destiny of your contract.

May your journey in the world of smart contracts be filled with wonder and endless possibilities. Good luck, brave enchanter!

  1. How do you write a Solidity smart contract?

Writing a Solidity smart contract involves several steps. First, you need to install the solidity compiler (solc). Once you’ve done that, you can start writing the contract. Here’s a basic sample of a Solidity contract:

pragma solidity ^0.5.16;

contract SimpleStorage {

    uint data;

    function set(uint x) public {

        data = x;

    }

    function get() public view returns (uint) {

        return data;

    }

}

//This simple contract has a single variable, `data`, and two functions, `set` and `get`, that set and retrieve the value of `data` respectively.
  1. Are smart contracts written in Solidity?

Yes, many smart contracts, especially those on the Ethereum blockchain, are written in Solidity. However, Solidity isn’t the only language for writing smart contracts. Other languages, such as Vyper for Ethereum, Chaincode for Hyperledger Fabric, and Rust and C++ for EOS, are also used.

  1. How do you write a smart contract on Ethereum in Solidity?

Writing a smart contract for Ethereum in Solidity involves writing the contract in the Solidity language and then deploying it to the Ethereum blockchain. The deployment process typically involves compiling the contract to bytecode using the Solidity compiler and then sending a transaction to the Ethereum network that includes the compiled contract.

  1. How do you write a smart contract?

Writing a smart contract involves defining the rules and protocols for performing transactions in a programmable format. This programming is done using specific languages like Solidity, Vyper, and others. Once written, the contract is deployed to a blockchain network where it can be interacted with.

  1. Is it easy to write smart contracts?

The difficulty of writing smart contracts largely depends on your knowledge of programming and the specific language used. If you are familiar with the programming concepts and the language, it can be relatively straightforward. However, due to the immutable and transparent nature of the blockchain, writing safe and secure smart contracts can be challenging.

  1. How do I write my first smart contract?

To write your first smart contract, start by learning a smart contract language such as Solidity. Then, write a simple contract and deploy it to a test Ethereum network using tools like Truffle or Hardhat. 

  1. Are smart contracts written in code?

Yes, smart contracts are written in code. They are essentially programs that execute on a blockchain.

  1. How do I write my own contract?

To write your own contract, you need to learn a smart contract language like Solidity, define the rules of the contract, and then write those rules in code. Afterward, compile the contract and deploy it to a blockchain.

  1. What language are smart contracts written in?

Smart contracts are typically written in languages such as Solidity and Vyper for Ethereum, Chaincode for Hyperledger Fabric, and others like Rust and C++ for EOS.

  1. Should I learn Solidity in 2023?

The decision to learn Solidity in 2023 depends on your goals. If you aim to develop on the Ethereum blockchain, then learning Solidity is highly recommended, as it is the main language for Ethereum smart contracts. However, it would be beneficial also to evaluate other blockchain platforms and their languages.

Metana Guarantees a Job 💼

Plus Risk Free 2-Week Refund Policy ✨

You’re guaranteed a new job in web3—or you’ll get a full tuition refund. We also offer a hassle-free two-week refund policy. If you’re not satisfied with your purchase for any reason, you can request a refund, no questions asked.

Web3 Solidity Bootcamp

The most advanced Solidity curriculum on the internet!

Full Stack Web3 Beginner Bootcamp

Learn foundational principles while gaining hands-on experience with Ethereum, DeFi, and Solidity.

You may also like

Metana Guarantees a Job 💼

Plus Risk Free 2-Week Refund Policy

You’re guaranteed a new job in web3—or you’ll get a full tuition refund. We also offer a hassle-free two-week refund policy. If you’re not satisfied with your purchase for any reason, you can request a refund, no questions asked.

Web3 Solidity Bootcamp

The most advanced Solidity curriculum on the internet

Full Stack Web3 Beginner Bootcamp

Learn foundational principles while gaining hands-on experience with Ethereum, DeFi, and Solidity.

Start Your Application

Secure your spot now. Spots are limited, and we accept qualified applicants on a first come, first served basis..

Career Track(Required)

The application is free and takes just 3 minutes to complete.

What is included in the course?

Expert-curated curriculum

Weekly 1:1 video calls with your mentor

Weekly group mentoring calls

On-demand mentor support

Portfolio reviews by Design hiring managers

Resume & LinkedIn profile reviews

Active online student community

1:1 and group career coaching calls

Access to our employer network

Job Guarantee