Getting Started With Solidity and Binance Smart Chain
Blockchain networks that support smart contracts functionality are on the rise. Smart contracts are programs that live in a blockchain and are executed on demand. <!--more--> In a platform like Binance, we can use the Smart chain protocol to build decentralized apps in a cross-chain network.
Table of contents
- Goal
- Prerequisites
- Why Binance Smart Chain
- Set up Remix
- Tokens in Binance Smart Chain
- Defining contracts
- Add mapping and events
- Create a smart contract constructor
- Retrieve balances in smart contracts
- Transfering tokens ownership
- Approve and transfer
- A final demo in MetaMask
- Conclusion
- Further reading
Goal
Tokens are the building blocks of any smart contract-based blockchain. We rely on them to build exchange platforms, Dapps, and Defi platforms. This tutorial provides context on how we can get started with developing a binance smart chain token using Ethereum compatible tools such as Remix and Metamask. In the process, we will learn the basics of smart contracts, solidity programming, tokens supply, management, and allocation.
Prerequisites
- Basics of Blockchain technology.
- An IDE for smart contracts such as the in-browser Remix IDE.
- A Metamask wallet extension. Use this link to install on your Chrome browser.
Why Binance Smart Chain
Over time the Ethereum platform has expanded causing network congestion to its infrastructure. This impacted gas fees and slowed transactions. As the plan to migrate to Proof-of-Stake has taken a longer time (than expected), and Ethereum currently uses the Proof-of-Work consensus. On the other hand, Binance uses the Proof-of-Authority mechanism.
Block producers are known as validators in the Proof-of-Authority consensus in the Binance Smart Chain. These validators are limited to 21 and need the approval of identities from Binance. Since we need pre-approval from Binance to become a validator, Binance gains complete control making it more of a private blockchain. The Binance Smart Chain is fully compatible with the Ethereum Virtual Machine.
Set up Remix
Remix is an online tool for Ethereum and solidity development. It comes bundled with plugins for testing, debugging, and deploying smart contracts. Launch Remix IDE on your browser by navigating to https://remix.ethereum.org/.
The editor looks like this:
In your environment, select the solidity version to use. In our case, version 0.8.2
. Under the workspaces
, inside our contracts
folder, add a new file and name it Token.sol
. Every Solidity file requires that we add the license type and the solidity version compatible with the code and configurations. Inside your Token.sol
, add the two lines of code as:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.2; // compiler version
The above code is very straightforward:
- Solidity will use SPDX that has been specified on the first line as
SPDX-License-Identifier: MIT
to show that our code is compliant with the MIT license. - The line statement
pragma solidity ^0.8.2;
tells solidity that our code is compatible with version0.8.2
or later.
Note: A pragma directive remains local to a source file ensuring that any modules we import on a file do not cause ambiguity.
Tokens in Binance Smart Chain
In blockchains that support smart contracts, developers need to create crypto tokens based on a set of standards. These standards include building DApps, exchanges, wallets, etc. While Ethereum's popular standard is the ERC-20 token, BSC uses the BEP-20 standard and in many ways, it is similar to the ERC-20 in Ethereum. Examples of popular BEP-20 tokens include:
- BUSD Token: BUSD stands for Binance USD. It is a stable coin created by Paxos, a tether to USD.
- CAKE Token: This is a BEP-20 token that controls the PancakeSwap platform.
- BUX Token was created as a utility token for the BUX exchange ecosystem.
- SAFEMOON Token: A Defi token for safemoon on the Binance Smart Chain.
Defining contracts
By convention, each solidity file contains one smart contract. A contract in Solidity is similar to classes in OOP languages, in that it wraps a collection of our variables, functions, events, state of a blockchain, and modifiers. Start by defining a contract named Token
with the following state variables:
contract Token{
uint public total_supply = 1500;
string public token_name = "Tutorial Token"; // token name - human - readable
string public symbol = "TTK";
uint public decimals = 18; // will set the divisibility of your token
}
- State variables are defined outside the function(s) of the contract. These values govern the state of the smart contract and persist on the blockchain.
- To define non-negative integers, we use the
uint
keyword (unsigned integer). - Any publicly defined variable is accessible internally and externally by any smart contract.
- The line
uint public total_supply = 1500;
is a state variable that defines our tokens' total supply. We will have 1500 fungible tokens in our smart contract. - Since addresses are hard to read, we use a string data type
token_name
as a human-readable name for our token. - A BEP-20 token includes a
symbol
string data type that is useful in exchange wallets when trading. - Now, we have
decimals
to represent the smallest fraction of a transferrable token. By default, we use 18 decimals. That means when transferring 2.5 tokens of BEP-20 standard, a calculation to perform is2.5 * decimals
giving us2.5 * 10**18 = 2500000000000000000
. For more on this, check here.
Add mapping and events
A mapping is a key-value
store in Solidity.
The syntax looks like this:
mapping(key => value) mappingName
Mapping is a Solidity keyword. Our key is mostly an address that references each record map to the balance (in integers). Our arrow simply points to the direction of the mapping. For example, 0x0000 => 500
is a mapping that shows the address 0x000
has a balance of 500 tokens.
Define a mapping for the balances:
// mapping
mapping(address => uint) public _balance; // bal will constantly update
Our final mapping named allowance
keeps track of the unspent portion of tokens that a delegate third part has spent on our balance. More on this later.
mapping(address => mapping(address => uint)) public allowance; //[allowance - how much spender is allowed to spend]
The Ethereum Virtual Machine supports events as a way to allow loggings and external software like wallets to listen to and perform actions on behalf of the user. These events inform the calling client about the state of the smart contract. Add an Approval
and Transfer
events as.
// event transfer
event Transfer(address indexed from, address indexed to, uint value);
// event emitted during an approval
event Approval(address indexed owner, address indexed spender, uint value);
Create a smart contract constructor
Next, add a constructor that runs when the contract is executed. Unlike in object-oriented programming, where the constructor gets initialized every time, Solidity invokes it only once when the contract is deployed to init
the smart contract state.
contract Token{
uint public total_supply = 1500;
string public token_name = "My Token";
string public symbol = "MTK";
uint public decimals = 18;
constructor(){
// send supply of tokens to the address that deployed smart contract
balances[msg.sender] = total_supply;
}
}
Here, we will add a constructor to assign all tokens in supply to the address that deployed the contract. From this address, msg.sender
, the lifecycle of supplies will begin. The distribution of tokens can happen via ICOs, Airdrops, and more.
Retrieve balances in smart contracts
To check the number of tokens within a user’s address, we will create a function that accepts an address argument and returns the balance in that address. Functions in smart contracts are invoked to perform actions on demand such as checking balances or updating the state of the blockchain.
function balanceOf(address owner) public view returns(uint) {
// return mapping balance of the owner
return balances[owner];
}
Using the view
keyword in a function indicates that it is read-only and will not change the state of any variable. All functions we mark as view
do not utilize gas fees when executing.
Transfering tokens ownership
Our BSC smart contract needs a function to support transfer
transactions. Within our function, we need to ensure that the calling party performing the transfer is the owner of the token.
function transfer(address to, uint value) public returns(bool){
// ensure sender has enough
require(balanceOf(msg.sender)>= value, 'balance not enough');
balances[to] += value;
balances[msg.sender] -= value;
// smart contracts emit event which external s/w e.g wallet
emit Transfer(msg.sender, to , value);
return true;
}
- The
require()
function checks for the role of actions in a smart contract. Ourrequire
functions receives the balance of themsg.sender
address and checks if the caller has enough value to transfer the requested tokens. If the balance is too low, we abort the transaction and throw an error. balances[msg.sender]
references our mapping to perform a deductvalue
of tokens from the sender and instead increment thebalances[to] += value;
for the recipient. These actions will trigger an update to the mappings during transactions.- Then our function emits a
Transfer
event to support transfer.
Approve and transfer
To support delegate transactions, we need to approve the actions of the spender so that the owner of the account allows the delegate to transfer the requested amount of tokens. The approve
function will limit the value of tokens that are deducted from the sender's balance.
The code snippet for the approve
function looks like this:
// Delegate a Transfer Functionn
function approve(address spender, uint value) public returns(bool){
allowance[msg.sender][spender] = value; // spender can spend *value* amount belonging to sender
emit Approval(msg.sender, spender, value); // emit approval event to allow spending
return true;
}
To transfer tokens from an account, we need to call Approve
on the address of the spender
within the smart contract itself.
- The line
allowance[msg.sender][spender] = value;
is a mapping that we can translate asallowance[owner][recipient] = value
. This triggers an exchange (asvalue
in tokens) on behalf of the owner. emit Approval(msg.sender, spender, value);
will emit an event frommsg.sender
to approve thespender
to spendvalue
amount of tokens to its address within the smart contract.
Now we can add a function to automate transfers of approved delegates. The transferFrom
function will perform a deduction from an account wallet of the owner and emit a Transfer
event.
Check the code below:
//
function transferFrom(address from, address to, uint value) public returns(bool){
// check allowance mapping if spender is approved
require(allowance[from][msg.sender] >=value, "allowance too low");
// check balance
require(balanceOf(from) >= value, "balance is too low");
// update mappings balances of sender & recipient
balances[to] += value;
balances[from] -= value;
// emit event
emit Transfer(from, to, value);
return true;
}
- The first line
require(allowance[from][msg.sender] >=value, "allowance too low");
retrieves a mapping of how much (amountvalue
in tokens) the owner allows another account (the delegate) to transfer on his behalf. - The
balanceOf
is a function that returns the amount in tokens the address has to confirm spending. Since we need to ensure they exist before allowing them to spend to restrict double spending. - Now we update our mappings and emit the
Transfer
event to complete the transaction.
A final demo in MetaMask
Using a metamask plugin, we can inject the web3.js namespace within our browser. If we configure and deploy our smart contract in a sandbox test environment, the final app should look like this:
The executable functions that we deploy to the smart contract to run transactions.
.
Interacting with transfer and balance functions.
.
Conclusion
Blockchain technology has given rise to a new disruptive wave of technological solutions in nearly every industry. It takes things to the next level by rethinking how untrusting parties agree, without relying on third parties to lower risks and manipulations. Ultimately, Web3 and blockchain are still in the early stages of adoption. Time will tell if this technology can become a solid industry standard.
Happy coding!
Further reading
Peer Review Contributions by: Willies Ogola