This article is part of an extended content series we’re working on to help bring developers into the Starknet ecosystem.
By the end of this article, you’ll understand what an ERC20 token is and how to create one from scratch.
WTF is an ERC20 token?
ERC20 tokens are fungible, meaning that they are replaceable by another of their token. This is just how money works. A $20 bill in one wallet holds the same value and can be used in the same way as a different $20 bill.
Fabian Vogelsteller and Vitalik Buterin proposed the ERC20 standard to create a set of universal standards for fungible tokens. Before the ERC20 standard, every token on Ethereum was different, meaning wallets had to write custom code to support them.
An ERC20 comprises the following major methods:
- name() -> (name: felt) — which returns the name of the token
- symbol() -> (symbol: felt) — which returns the token’s symbol
- decimals() -> (decimals: felt) — which returns the token’s decimals
- totalSupply() -> (totalSupply: Uint256) — which returns the token’s total supply.
- balanceOf(account: felt) -> (balance: Uint256) — which queries and returns the balance of a particular account.
- allowance(owner: felt, spender: felt) -> (remaining: Uint256) — which queries and returns the allowance assigned to a spender by an owner.
- transfer(recipient: felt, amount: Uint256) -> (success: felt) — which transfers a certain amount of tokens from a sender to the specified recipient.
- transferFrom(sender: felt, recipient: felt, amount: Uint256) -> (success: felt) — which allows a spender to transfer a certain amount of tokens allowed to be spent by the owner/sender to the specified recipient.
- approve(spender: felt, amount: Uint256) -> (success: felt) — which approves a spender to spend a certain amount of tokens from the owner’s wallet.
Setting up Scarb
As always, we will be using the Scarb package manager for development. If you still don’t have Scarb installed at this point, refer to the documentation here. To get started, we will need to initialize a new project. Let’s call our project “argentERC20”.
To do this, run:
Creating a new file
We will create a new file called ERC20.cairo in our src folder. This is where we’ll be writing our contract codes.
ERC20 Interface
Inside our new file, we’ll begin with the #[starknet::interface] attribute, which we'll use to specify the ERC20 interface implemented by our contract.
Imports
Next up, we’ll introduce the #[starknet::contract] attribute, which specifies that our file contains code for a Starknet contract.
After we’ve done that, we’ll create a new module “ERC20” and begin by importing all the necessary library functions we’ll need for our contract.
Storage variables
Going forward, we'll need to define our storage variables. With the new Cairo syntax, storage structs must be specified using the `#[storage]` attribute:
Events
Our contracts will definitely need to emit certain events such as `Approval`, and `Transfer`. To do this we’ll need first to specify all the events to be emitted in an enum called `Event`, with custom data types.
Finally, we’ll create these structs, with the members being the variables to be emitted:
Writing the contract
Constructor
For our ERC20 token, we need to initialize certain variables on deployment, such as the name, symbol, decimals, total_supply and balance of the recipient. To do this, our contract must implement a constructor:
As you can see from the snippet above, the constructor function takes in a reference to a `self` parameter which points to the contract’s storage, a `_name` parameter representing the name of our token, a `_symbol` parameter representing the token symbol, the `_decimals` parameter representing the token decimals, and the `_initial_supply` representing the initial supply of the token. These are initialized within the constructor body.
Contract Implementation
With the new Cairo syntax, all public functions are defined within an implementation block, and required to subscribe to a certain Interface which we specified at the top of the contract.
We are also going to be specifying the `external[v0]` attribute to inform the compiler that the functions contained within this implementation block are public/external functions.
Finally, if you take a good look at the code above, you’ll notice we delegated the logic for certain functions to a helper function. We’ll also need to write out these helper functions, within an implementation block, but since they are not public functions we are going to exclude the `external[v0]` attribute.
We are also going to let the compiler automatically generate the interface trait by using the`generate_trait`attribute.
Conclusion
Having gotten to this point, congratulations! You just completed your first ERC20 Starknet contract. You can find the full source code in the repo here.
If you have any questions as regards this, reach out to me @0xdarlington, I’d love to help you build on StarkNet with Argent X.
For more developer resources, follow us across our socials:
Twitter — @argentHq
Engineering Twitter — @argentDeveloper
LinkedIn — @argentHq
Youtube — @argentHQ