Chapter 5. Tokenize Everything

The advent of Bitcoin gave developers an opportunity to explore different types of cryptocurrencies. Yet it was Ethereum, a totally new technology, that gave coders the ability to easily create new cryptocurrencies on top of its blockchain, known as tokens. Today, there are tens of thousands of cryptocurrencies, mostly thanks to Ethereum. The Ethereum network sparked the proliferation of the concept of “tokenize everything” via initial coin offerings (ICOs), which allow a project to raise cryptocurrency funds and give investors tokens in exchange. This chapter looks at how that happened. We’ll begin by introducing a few notable examples:

Mastercoin
Developer J.R. Willett began working on the Mastercoin whitepaper in 2011. His aim was not “to bootstrap an entirely new blockchain, as every other cryptocurrency does,” but rather “to create an entirely new network of currencies, commodities, and securities on top of Bitcoin itself.” Willett eventually realized that community backing in the form of investment via bitcoin might help foster adoption. So, he held the first “token sale,” or ICO, in 2013. This enabled Mastercoin to raise 3,700 BTC, or about $2.3 million at the time.
Ethereum

As described in the previous chapter, the beginnings of Ethereum trace back to November 2013, when Vitalik Buterin began emailing around a whitepaper proposing a new protocol based on elements of Bitcoin, Mastercoin, and other projects. This document was disseminated throughout the cryptocurrency community, and developers and backers began to accumulate. Buterin made a public announcement of the Ethereum project in February 2014.

A Swiss-based nonprofit foundation was created to initiate the ICO, and starting in July 2014, for 42 days Ethereum conducted a crowdsale. Approximately 60 million ether tokens were sold, raising some 31,000 BTC (around $18 million at the time). This became the template for many other ICOs in the future.

Gnosis

A decentralized prediction market platform, Gnosis shares some concepts and some personnel with the Augur project, an early Ethereum-based offering that had its ICO in 2015. The Gnosis multisignature wallet is still one of the most widely used in the Ethereum ecosystem, especially for applications such as cold storage of tokens.

The most interesting aspect of the Gnosis project’s ICO was the Dutch-style auctioning system it employed. This novel concept enabled tokens to decline in value over the time of the ICO, encouraging investors to wait until the end to get the best pricing. Most ICOs are conducted the reverse way: cheaper tokens are offered the earlier an investor gets in. However, this proved successful as Gnosis was able to raise over $300 million in 15 minutes while keeping 95% of the cryptocurrency attributed to the project and its founders.

EOS
EOS.IO, brainchild of Daniel Larimer, is a blockchain protocol that aims to solve the scalability issues of blockchains by distributing computing resources equally among EOS cryptocurrency holders. The project raised over $4 billion in a year, one of the biggest raises ever, by using an uncapped token sale on Ethereum. The offering was for an ERC-20 token called EOS, which was converted into the native token once their native blockchain was ready.

Tokens on the Ethereum Platform

Creating a token allows developers to create a cryptocurrency on the Ethereum network. This enables anyone to issue an asset on a blockchain using one of the most well-known cryptocurrency protocols. The ERC-20 standard on Ethereum is a reference implementation of blockchain assets on the network, paving the way for tokens to have properties that enable their use across many different exchanges, wallets, and other blockchain services. There are other blockchain platforms for issuing tokens. However, issuing an ERC-20 asset on Ethereum is one of the easiest and most secure ways to create a cryptocurrency today.

Outside of technical projects like mobile dapps, distributed computing, or payment mechanisms, tokens have the potential to disrupt existing financial services where bottlenecks still exist. Blockchain and cryptocurrency have the ability to represent something of value in the real world, as long as they can be properly pegged back to a real-world asset.

In complex real estate transactions, for example, tokens on a blockchain could enable better record keeping for owners—the state of Ohio is looking at using blockchain for this purpose. In the future, transfers of assets could be completed more quickly and easily using tokens. Other areas where tokens may prove useful to prove authenticity could include art, cars, and stocks and bonds.

Fungible and Nonfungible Tokens

Not all tokens are created equal. One of the most important differentiators when creating tokens is whether they are fungible or nonfungible. Fungible tokens all have the same value and are interchangeable with one another, whereas nonfungible tokens represent something that is unique.

Examples of fungible assets are currencies like the US dollar. One dollar is one dollar, whether it exists in a physical form as coins or a bill or digitally in a bank account or other financial service. Most cryptocurrencies, like bitcoin, ether, and ERC-20 assets, are also fungible.

Items such as cars or houses are nonfungible—each is unique and not interchangeable with any other random car or house. CryptoKitties—digital cats represented on Ethereum as ERC-721 tokens—are another example of a nonfungible asset (we discuss this example more later in this chapter).

Smart contract development is a nascent area of computer science. It is highly recommended that a third-party auditor (like OpenZeppelin, which contributed background for this chapter) take a look at your code before you bring a token into the wild. Other well-known companies providing such services include Trail of Bits and Chainsecurity.

Ethereum offers many different token types that can be issued. Some popular ones include ERC-20, ERC-721, ERC-223, ERC-777, and ERC-1400. The variety enables developers to create different types of functional cryptocurrencies on top of the Ethereum blockchain.

Note

An exhaustive list of Ethereum token standards is available on GitHub. Some of the tokens listed are currently functional, whereas others are merely proposed ideas.

Thanks to Ethereum’s nature, new types of cryptocurrencies on the Ethereum blockchain may provide real-world benefits. ERC-846, which provides for shared ownership of a token, is a good example of a real-world use case. 

Airdrops

As we’ve said, the main way to distribute tokens is via an ICO or similar type of offering. Another alternative is doing an airdrop. Intended to leverage network effects of already existing blockchains, airdrops are free or low-cost disbursements of cryptocurrencies to a large subset of users. The idea is to rapidly give a project a user base from its inception, baking in adoption of an already existing project/cryptocurrency. The largest case so far has been the Stellar Foundation’s $125 million airdrop of its XLM token via the Blockchain.info wallet.

Note

Airdrops may seem like a solution to nascent cryptocurrency adoption, but they’re not without their drawbacks. In particular, there likely are tax implications for users to obtain a cryptocurrency-based asset at zero cost. When sold, there could be a taxable event, depending on jurisdiction. Providing a token at nearly no cost also might not bode well for future value, due to dilution of the underlying cryptocurrency being airdropped. There’s no such thing as a free lunch in economics, so there could be compensating or even extra costs to airdrops.

Different Token Types

Different Ethereum tokens have different technical specifications, and they also use different nomenclature depending on how regulators around the world define them. It’s important for developers to understand the various terms being floated regarding how to define tokens:

Utility
Utility in the context of tokens means that a blockchain-based cryptocurrency must have some use outside of financial speculation. There are several longstanding projects attempting to do this in the blockchain world. One of the best-known is Filecoin, where tokens grant users access to space on a decentralized cloud storage platform.
Security
A security, as defined by the SEC, is an investment contract. Designed to provide a promise of a return, investment contracts are regulated devices used around the world for fundraising. For this reason, the tokens proposed in many ICOs could be considered securities. They are thus regulated in the jurisdiction of issuance. An example of a project that offers security tokens is bloXroute, a protocol that changes the way networking and routing work for blockchain. Owning a bloXroute token means entitlement to a share in the future payout that blockchains will be making to bloXroute to use its routing protocol.

A security token offering (STO) is an attempt to create an ICO that fits into a regulatory framework. As ICOs mimic some of the qualities of an equity IPO, regulators around the world are increasingly trying to understand how to protect investors from fraud, excessive risk, and theft. The SEC, for example, has published a framework for crypto-based security offerings. 

Understanding Ethereum Requests for Comment

With improving capabilities of the EVM, empowering developers to write better smart contracts, the Ethereum community began creating standards, formalized as Ethereum Requests for Comment (ERCs). These standards are important, as they ensure that apps wanting to interact with Ethereum smart contracts will know which functions and inputs to call. All proposed ERCs start as an Ethereum Improvement Proposal (EIP), which then goes through a vetting process. This is similar to the Bitcoin Improvement Process, discussed in Chapter 3.

ERC-20

The most common ERC standard for Ethereum tokens is ERC-20. Every smart contract that is compliant with the ERC-20 standard will implement the methods shown in Table 5-1.

Table 5-1. ERC-20 methods
Method Description
totalSupply() public view returns (uint256 totalSupply) Get the total token supply.
balanceOf(address _owner) public view returns (uint256 balance) Get the account balance of another account with address _owner.
transfer(address _to, uint256 _value) public returns (bool success) Send _value amount of tokens from address _from to address _to. Tokens are sent from the address that called the transaction.
transferFrom(address _from, address _to, uint256 _value) public returns (bool success) Send _value amount of tokens from address _from to address _to.
transferFrom(address _from, address _to, uint256 _value) public returns (bool success) Send _value amount of tokens from address _from to address _to.
approve(address _spender, uint256 _value) public returns (bool success) Allow _spender to withdraw from your account, multiple times, up to _value amount. If this function is called again, it overwrites the current allowance with the new _value.
allowance(address _owner, address _spender) public view returns (uint256 remaining) Return the amount which _spender is still allowed to withdraw from _owner.

Every smart contract that is compliant with the ERC-20 standard will implement the two events shown in Table 5-2. Developers can build applications that listen for these events to be triggered—for example, a cryptocurrency wallet checking to see if any of its Ethereum addresses have received tokens.

Table 5-2. Events supported by ERC-20-compliant smart contracts
Event Description
Transfer(address indexed _from, address indexed _to, uint256 _value) Event triggered when tokens are transferred.
Approval(address indexed _owner, address indexed _spender, uint256 _value) Event triggered whenever approve(address _spender, uint256 _value) is called.

The following is an example of a basic ERC-20 smart contract, Mastering_Blockchain_Token.sol:

pragma solidity ^0.4.21;

contract EIP20Interface {  
    /// total amount of tokens
    uint256 public totalSupply;
 
    /// @param _owner The address from which the balance will be retrieved
    /// @return The balance
    function balanceOf(address _owner) public view returns (uint256 balance);
 
    /// @notice send `_value` tokens to `_to` from `msg.sender`
    /// @param _to The address of the recipient
    /// @param _value The amount of tokens to be transferred
    /// @return Whether the transfer was successful or not
    function transfer(address _to, uint256 _value) public returns (bool success);
 
    /// @notice send `_value` tokens to `_to` from `_from` on the condition
    ///  it is approved by `_from`
    /// @param _from The address of the sender
    /// @param _to The address of the recipient
    /// @param _value The amount of tokens to be transferred
    /// @return Whether the transfer was successful or not
    function transferFrom(address _from, address _to, uint256 _value) public 
      returns (bool success);
 
    /// @notice `msg.sender` approves `_spender` to spend `_value` tokens
    /// @param _spender The address of the account able to transfer the tokens
    /// @param _value The amount of tokens to be approved for transfer
    /// @return Whether the approval was successful or not
    function approve(address _spender, uint256 _value) public 
      returns (bool success);
 
    /// @param _owner The address of the account owning tokens
    /// @param _spender The address of the account able to transfer the tokens
    /// @return Amount of remaining tokens allowed to be spent
    function allowance(address _owner, address _spender) public view 
      returns (uint256 remaining);
 
    // solhint-disable-next-line no-simple-event-func-name
    event Transfer(address indexed _from, address indexed _to, uint256 _value);
    event Approval(address indexed _owner, address indexed _spender, 
                   uint256 _value);
}
 
contract EIP20 is EIP20Interface {
 
    uint256 constant private MAX_UINT256 = 2**256 - 1;
    mapping (address => uint256) public balances;
    mapping (address => mapping (address => uint256)) public allowed;
    /*
    NOTE:
    The following variables are OPTIONAL vanities. One does not have to include
    them. They allow one to customize the token contract & in no way influence
    the core functionality. Some wallets/interfaces might not even bother to look
    at this information.
    */
    string public name;       // Token name: eg Mastering Blockchain Book
    uint8 public decimals;    // How many decimals to show. Standard is 18.
    string public symbol;     // An identifier: eg MBB
 
    function EIP20(
        uint256 _initialAmount,
        string _tokenName,
        uint8 _decimalUnits,
        string _tokenSymbol
    ) public {
        balances[msg.sender] = _initialAmount;  // Give creator initial tokens
        totalSupply = _initialAmount;           // Update total supply
        name = _tokenName;                      // Set name for display purposes
        decimals = _decimalUnits;   // Set number of decimals for display
        symbol = _tokenSymbol;      // Set symbol for display purposes
    }
 
    function transfer(address _to, uint256 _value) public returns (bool success) 
    {
        require(balances[msg.sender] >= _value);
        balances[msg.sender] -= _value;
        balances[_to] += _value;
        emit Transfer(msg.sender, _to, _value); 
        return true;
    }
 
    function transferFrom(address _from, address _to, uint256 _value) public 
      returns (bool success) {
        uint256 allowance = allowed[_from][msg.sender];
        require(balances[_from] >= _value && allowance >= _value);
        balances[_to] += _value;
        balances[_from] -= _value;
        if (allowance < MAX_UINT256) {
            allowed[_from][msg.sender] -= _value;
        }
        emit Transfer(_from, _to, _value); 
        return true;
    }
 
    function balanceOf(address _owner) public view returns (uint256 balance) {
        return balances[_owner];
    }
 
    function approve(address _spender, uint256 _value) public 
      returns (bool success) {
        allowed[msg.sender][_spender] = _value;
        emit Approval(msg.sender, _spender, _value); 
        return true;
    }
 
    function allowance(address _owner, address _spender) public view 
      returns (uint256 remaining) {
        return allowed[_owner][_spender];
    }
}

This token contract was published on the Ropsten testnet and has the following attributes:

  • Token name: Mastering Blockchain Book

  • Token symbol: MBB

  • Token supply: 100 MBB

  • Token decimal places: 18

To create your own custom token, you can simply copy and paste the preceding code and change these four values in the constructor function:

symbol
The symbol of your token.
name
The name of your token.
decimals
How many decimals your token can be divided into. The standard value for most tokens is 18.
totalSupply
How many tokens will be in existence. There is a lot of variation in supply among tokens; 1 billion is an easy round number that is common.

ERC-721

ERC-721 is a standard for nonfungible tokens. As mentioned earlier, with fungible tokens (like ERC-20 tokens), each token has the exact same attributes. With nonfungible tokens, each token can have different attributes and therefore is unique, which allows for extreme digital scarcity.

Before blockchain, most virtual items could easily be copied. Connecting a virtual good or a real-world item to an ERC-721 token is a way to create a digitally scarce item that cannot be copied or tampered with.

The most famous example of this in the blockchain world is CryptoKitties, virtual cats that are connected to ERC-721 tokens on the Ethereum blockchain. Figure 5-1 shows CryptoKitty #1270015.

Figure 5-1. The unique attributes of the CryptoKitty with the unique ID 1270015

Instead of reading the attribute information about CryptoKitty #1270015 from a centralized database, the information is pulled from the CryptoKitty ERC-721 smart contract. Go to function #32 and enter the CryptoKitty ID, which is 1270015. From there it is possible to see the unique attributes for this CryptoKitty stored on the Ethereum blockchain, as shown in Figure 5-2.

Figure 5-2. Calling the read function getKitty from the main CryptoKitties smart contract responds with data stored in Ethereum about the specific kitty ID 1270015

ERC-777

This proposed standard is for the next generation of fungible (ERC-20) tokens. It includes some improvements to the ERC-20 standard, the most important of which is in the way tokens are transferred.

There are two ways that users can move ERC-20 tokens from one address to another:

Push transaction
Calling the function transfer(address _to, uint256 _value) is a push transaction, where the sender initiates the transfer of tokens.
Pull transaction
The combination of the functions approve(address _spender, uint256 _value) and transferFrom(address _from, address _to, uint256 _value) is a pull transaction, where the sender gives permission to the receiver and then the receiver pulls the tokens out of the sender’s account.

If a person sends tokens to a smart contract using a push transaction, then the smart contract will receive the tokens. However, the smart contract will not receive a trigger telling it that it has received the tokens and instructing it to run some code.

This is the reason for a pull transaction—the smart contract receiving the tokens initiates the transaction, and it can recognize when the tokens are received and execute additional code to react to this event. The most common use case for a smart contract receiving and sending tokens is decentralized exchanges, IDEX being the most popular.

Pull transactions work for smart contracts receiving tokens, but their use has led to many tokens accidentally being lost. A common problem is if a user mistakenly sends tokens to a smart contract using a push transaction rather than via the correct pull method; those tokens will be burned and lost forever, because the smart contract will not recognize that it has received the tokens and therefore won’t know to send them to another address at a later time.

ERC-777 proposes to fix this problem by introducing the following improvements:

authorizeOperator(address operator) and revokeOperator(address operator)
Allow token holders to authorize smart contracts to transfer tokens on their behalf and revoke that permission, respectively. The contracts that are authorized are known as operators. This is a variation of the pull transaction combination in ERC-20, but instead of authorizing the operator each time you want to transfer tokens, you only need to authorize the operator once. Then, for each additional transfer, the operator can transfer the tokens on your behalf.
tokensReceived and tokensToSend hooks
The contract receiving the tokens can include a function called tokensReceived. In that function, the receiving contract can identify which ERC-777 tokens the contract would like to accept and which it would like to reject (using revert). If an ERC-777 token is received but identified as to be rejected, the token transfer will not complete. It’s like receiving a letter in the mail and sending it back. Similarly, a contract that is requesting a token transfer can receive the tokensToSend hook, and when that hook is called has the option to revert the transaction. It’s less likely that this will happen, because this is the contract that initiated the transfer of tokens—it’s like going to the post office to send a letter, but then changing your mind as you are about to hand it over.
send(address to, uint256 amount, bytes data)
The push transaction includes a data field that not only allows the sender to send tokens to a contract, but also can contain specialized logic that triggers a function in the receiving contract. This is similar to how Ethereum transactions are executed.

Although the ERC-777 standard is an improvement over ERC-20, it has not been adopted by the industry yet because there is a large switching cost for all stakeholders to move to the new standard. Many projects would have to create new token contracts, and then convince token holders to swap existing tokens for an equivalent amount using the new standard. Exchanges and some dapps would have to update their systems to support the new standard as well.

ERC-1155

This standard was designed to track virtual goods in games. For example, the default weapon in a shooting game might be a pistol, but it is possible to purchase the triple-barreled rocket-launching weapon that kills one hundred enemies in one shot.

An ideal token standard for these in-game items would have a mixture of ERC-20 and ERC-721 attributes:

  • ERC-20 (fungible) so that you can attach a price to the virtual good, and users can then purchase and trade the item

  • ERC-721 (nonfungible) so that the virtual good can have unique properties—for example, how many rockets it can hold, or how powerful the weapon is.

Like the ERC-777 token standard, this standard includes the concept of an operator, an address that has the authority to move your tokens on your behalf.

Another improvement in this standard is the ability to transfer multiple tokens in one transaction. When transferring ERC-721 tokens, you call the function safeTransferFrom and specify the token to be transferred by its _tokenId:

function safeTransferFrom(address _from, address _to, uint256 _tokenId, bytes data) 
      external payable;

With ERC-1155 tokens, you can call the function safeBatchTransferFrom and specify an array of _ids:

function safeBatchTransferFrom(address _from, address _to, uint256[] calldata _ids, 
      uint256[] calldata _values, bytes calldata _data) external;

The ability to do batch trades removes another layer of friction, for gamers and game publishers alike.

Enjin, the company that created this standard, is now providing a platform that makes it easy for game publishers to support virtual goods on the blockchain. One of the biggest challenges to gaining widespread adoption is that the Ethereum network is not yet fast enough to support hundreds of thousands of transactions a second, which is a common requirement in large-scale games.

At time of this writing, about 35 games have adopted this standard, and about 100,000 people hold ERC-1155 virtual goods.

Multisignature Contracts

Sending funds out of an externally owned account (EOA) wallet in Ethereum only requires one private key. This means if that key is compromised, there’s nothing stopping funds being stolen from the account—it’s a single point of failure. The purpose of a multisignature wallet is to lower the risk of unauthorized removal of funds by requiring multiple private keys to send the funds. This is a similar concept to a bank account that requires multiple signatures to authorize a payment. There isn’t an ERC standard for multisignature wallet contracts, although this type of contract is widely used in the industry.

Every multisignature contract requires M of N signatures to authorize a transaction, where:

  • N is the number of Ethereum addresses that can authorize a transaction.

  • M is the minimum number of signatures from among those N unique addresses required for a transaction to be authorized.

Note that M must be less than N. An example would be a 2-of-3 multisignature contract, which means there are three addresses that can authorize a transaction, and only two signatures are required to complete the transaction.

It’s common practice for entities that do ICOs to collect all the funds they raise into multisignature wallets. These ICOs also make their multisignature wallet code transparent and publicly share which addresses can sign a transaction. This transparency increases trust from investors, because at any time investors can audit the funds.

If you audit the multisignature wallet of a well-known company that raised $33 million in its ICO, you can see all the funds that have come in and been sent out. You can also audit the wallet’s M of N:

  1. Calling the function getOwners shows which addresses can authorize transactions. These addresses are called owners. In this case, there are five of them:

  2. Reading the current value of the variable required shows how many signatures are required to execute a transaction. This wallet requires three signatures to execute a transaction.

This wallet that is being audited here is thus a 3-of-5 multisignature wallet.

As you can see in Figure 5-3, you can also audit which addresses signed all the transactions that this wallet has executed.

Figure 5-3. Example of the series of events that took place to set up and execute a multisignature transaction

As this figure shows, the process for executing a multisignature transaction is as follows:

  1. One of the owner addresses (0x00c9…7f69) that is authorized to perform a multisignature transaction calls the submitTransaction function to submit the transaction details. The submitTransaction call performs two events:

    1. It stores the details of the requested transaction.

    2. It adds 0x00c9…7f69 to the list of addresses that confirm the transaction. This address therefore both initiates the transaction and confirms the transaction.

      The submitTransaction call occurs in block #7331149.

  2. A second owner (0x0063…ad4f) calls the function confirmTransaction to give the second of the three required signatures. This confirmTransaction call occurs in block #7331154.

  3. A third owner (0x003c…766c) calls the function confirmTransaction to give the third signature. This call leads to two events:

    1. The third owner confirms the transaction.

    2. The contract recognizes that all required signatures have been given and then executes the transaction using the details that were submitted in step 1.

      This confirmTransaction call occurs in block #7458500.

Decentralized Exchange Contracts

Before Ethereum, every cryptocurrency exchange had to be controlled and managed by a company—a centralized authority. Centralized exchanges still exist, with popular examples including Coinbase, Bitstamp, and Gemini. The purpose of an exchange is to act as a trusted platform where two parties can exchange cryptocurrencies securely. To accomplish this, an exchange must do the following for its customers:

  • Provide a secure place to deposit/withdraw crypto, and hold the funds in escrow.

  • Provide an order book, so that two parties can agree on a price to trade the crypto at.

  • Swap the cryptocurrencies between the two parties.

A smart contract has the capability to perform the following three actions:

  • Send/receive and hold ETH and ERC-20 tokens.

  • Record price requests from EOA accounts.

  • If two price requests match, change ownership of the corresponding cryptocurrencies.

A good example of a decentralized exchange on Ethereum is IDEX. Even though the IDEX website looks similar to a centralized exchange, there are significant differences between the two types of exchanges.

All of the code running a centralized exchange is deployed to a web hosting provider—for example, AWS or Azure. Frontend code running on a decentralized exchange is also deployed to a web hosting provider, but the backend code is written into a smart contract and deployed to the Ethereum network. The database is just the Ethereum or some other smart contract blockchain, as illustrated in Figure 5-4.

Figure 5-4. Infrastructure differences between centralized and decentralized exchanges

Advantages of a decentralized exchange include:

Greater transparency
Because the backend code is in a smart contract, anyone can audit it before using the exchange. The code in a centralized exchange is private. This type of transparency increases trust in the decentralized exchange.
Reduced counterparty risk
When you deposit cryptocurrency in a centralized exchange, it maintains custody of your funds, and customers expect that at any point in time they can get those funds back. However, there have been many cases where exchanges have lost all of their customers’ funds. In a decentralized exchange, the smart contract maintains custody of the cryptocurrency, and if an audit of the smart contract shows that the contract is safe to use, then it’s impossible for the exchange to lose your funds.

Decentralized exchanges also have a few downsides, though. Notably, they are:

Very slow
On a centralized exchange, users expect that if they execute a trade it will be completed instantly. On a decentralized exchange, in order for a trade to execute, a user must wait for their transaction to be included in a block, which often takes at least 10 seconds, or even a minute. By the time the user’s trade executes, the opportunity could be gone.
Expensive
Decentralized exchanges require users to generate a new transaction every time they want to perform an action, including adding an order and cancelling an order. Exchange users frequently make multiple orders and changes to those orders in a short period of time. On a centralized exchange, these order changes are free, but on a decentralized exchange, you have to pay gas to the network for each action, which makes it much more expensive to use.
Difficult for nontechnical users
Since users must sign a transaction every time they complete an action, nontechnical users may find using a decentralized exchange is too complicated and requires too much effort.

One other big difference between the two is that there is no need to ask anyone’s permission to add an ERC-20 token to a decentralized exchange. As soon as an ERC-20 token is created, it can instantly be traded on a decentralized exchange. Depending on whom you speak to, this can be considered a pro or a con. We’ll talk more about decentralized exchanges and how they’re used in Chapter 7.