Fungible Tokens and Decentralized Exchanges

1. What is a fungible token?

I previously blogged about smart contracts. Tl;dr, a smart contract is a piece of software that lives on a computer network and store data. One use-case is to create a token, which is in simple terms a virtual item with a limited supply. Tokens may be fungible, meaning that all tokens are indistinguishable, or non-fungible, meaning that each token is different. This post is about fungible tokens, there may be a followup about NFTs in the future.

2. How do they work?

To create a fungible token, all you need is to keep track of how many tokens each address owns. A typical fungible token contract will include a data structure that maps addresses to balances. Addresses not included in that data structure implicitly have a zero balance.

2.1 EIP-20

The EIP-20 standard by Fabian Vogelsteller and Vitalik Buterin defines an interface that fungible tokens should implement. Most apps that interact with fungible tokens will expect the tokens to implement that interface.

/** Optional */
function name() public view returns (string)
function symbol() public view returns (string)
function decimals() public view returns (uint8)
/** Required */
function totalSupply() public view returns (uint256)
function balanceOf(address _owner) public view returns (uint256 balance)
function transfer(address _to, uint256 _value) public returns (bool success)
function transferFrom(address _from, address _to, uint256 _value) public returns (bool success)
function approve(address _spender, uint256 _value) public returns (bool success)
function allowance(address _owner, address _spender) public view returns (uint256 remaining)
event Transfer(address indexed _from, address indexed _to, uint256 _value)
event Approval(address indexed _owner, address indexed _spender, uint256 _value)

The optional methods at the top only affect how the token should be displayed to an end user.

The totalSupply method returns, well, the total supply of the token. balanceOf returns the balance of one particular address. transfer sends tokens from the address making the call to a specified address. This will usually decrease the sender's balance, and increase the receivers balance, by the specified amount. Though not always, as we'll explore later.

The EIP-20 standard also has functionality for sending tokens on behalf of another address. Imagine for example a marketplace that sells virtual items in exchange for a certain EIP-20 token: When a user signs up to that marketplace, they would be prompted to call the approve method, with the marketplace's address as the spender and some spending limit as the _value .

Then, when the user purchases an item, the marketplace would call transferFrom to send the purchase price from the buyers address to the sellers address. If the price does not exceed the spending limit, the contract should send the tokens, and then reduce the spending limit by that price. The allowance method would return the remaining spending limit.

3. Implementation variants

The EIP-20 standard defines the interface, and specifies when the events must be triggered. Other than that, the standard only has SHOULD rules (which are optional). This gives smart contract authors a lot of freedom in how they implement their tokens. This is an incomplete list of some common implementation variants:

3.1 Taxes

Many tokens have transfer taxes, so every time you transfer tokens to another address, a percentage of those tokens are subtracted. The taxes may either be sent to the address of the contract owner, or some other specified address, or they may be destroyed by sending them to an address to which nobody knows the private key (such as the 0x0 address). Sometimes the transfer tax reduces if the sender has owned the tokens for a long time before transferring them.

3.2 Reflections

Some tokens distribute some or all of their earned transfer taxes to all holders of that token. Those are known as reflection tokens.

3.3 Rebases

Some tokens automatically adjust their total supply based on some algorithm. Usually, "adjusting the supply" means that all balances change by some ratio. For example, to increase the total supply by 5%, a rebase token would increase the balances of all addresses by 5%.

As a result, any address's balance might change frequently. So, instead of thinking of a certain balance of tokens, it's often more useful to think about the percentage of the total supply that an address owns.

4. Decentralized exchanges

If you own fungible tokens, you might want to trade them for other fungible tokens. There are three ways to do that:

  1. Find someone who wants to trade with you
  2. A centralized exchange (CEX)
  3. A decentralized exchange (DEX)

(option 2 is sort of a subset of option 1)

Centralized exchanges work like you'd probably expect: They're private companies that connect buyers and sellers and take a cut of every exchange.

From a technical standpoint, decentralized exchanges are more interesting. Unlike CEXs, they don't usually have an orderbook. They allow you to exchange tokens without needing to find a counterparty, and they determine the exchange value of tokens automatically based on supply and demand.

4.1 constant-product exchanges

This is probably the most common DEX implementation: Constant-product exchanges use something called liquidity pairs, which are pools containing two different tokens. Those tokens can then be exchanged by users of that DEX. To determine the exchange price, they use this formula:

RaRb=kR_a * R_b = k

where:

Anyone can create a liquidity pair by providing some amount of tokens a and b to the exchange. If you're the first person to create liquidity for that pair, the constant k is determined by the amount of both tokens you provide. If you're not the first, you're forced to provide liquidity in such a way that k doesn't change.

Let's say you want to exchange a certain amount of token a for token b. How many of b will you receive? To determine that, we can use this equation:

(Ra+ain)(Rbbout)=k(R_a + a_{in}) * (R_b - b_{out}) = k

where:

k is a constant, so we can write:

(Ra+ain)(Rbbout)=RaRb(R_a + a_{in}) * (R_b - b_{out}) = R_a * R_b

(RaRb)/(Ra+ain)=Rbbout(R_a * R_b) / (R_a + a_{in}) = R_b - b_{out}

Rb(RaRb)/(Ra+ain)=boutR_b - (R_a * R_b) / (R_a + a_{in}) = b_{out}

For example, say a liquidity pool contains 10,000 of a and 2,500 of b. You have 500 of a and want to convert them to b. You would receive:

250010000250010000+5001192500 - \\frac{10000 * 2500}{10000 + 500} \\approx 119

500 of a are added to the liquidity pool, and 119 of b are removed. After the transaction, RaR_a = 10,500 and RbR_b = 2,381. If you exchanged another 500 of a, you would now receive:

238110500238110500+5001082381 - \\frac{10500 * 2381}{10500 + 500} \\approx 108

of b. This is an important property of constant-product exchanges: They adjust the exchange rate between tokens automatically based on supply and demand. Tokens that are bought more than they are sold will increase in value, and vice versa.

4.2 wrapped tokens

Typically, DEXs only let you exchange one EIP-20 token for another. What if you want to exchange the network's native token, such as Ether?

The workaround for this is to create a "wrapped" token: There's wrapped Ether or wETH, wrapped Avax or wAVAX, etc. Those wrapped tokens have the same value as the thing they wrap. This reason they have that value is because there's a smart contract that allows anyone to exchange ETH for wETH, and vice versa, at a 1:1 ratio. Any ETH that are exchanged that way are "stored" inside the contract and can only be retrieved by exchanging the wETH back to ETH. The contract is written in such a way that this exchange ratio can't be changed.

Once you've converted your ETH to wETH, you can trade it for other tokens. DEXs often allow you to make that swap and the transaction in one go, so it looks like you're directly exchanging ETH for some other token.

5. "Use cases"

The title is in air quotes because, well, most of these aren't useful in the traditional sense of the word.

It's important to note that those tokens have no intrinsic value. Native cryptocurrency tokens, like Ether, have value because some amount of Ether is required for every transaction on the Ethereum blockchain. So, as long as there is demand for applications running on that blockchain, there will also be demand for Ether. Other tokens that live on the Ethereum blockchain have no intrinsic demand.

If there is demand for fungible tokens, it might be for one of two reasons:

  1. The token can exchanged for things with intrinsic value (for example, some company lets you exchange the token for Ether)
  2. Speculation

Reflection and rebase tokens often sell themselves as a way to make a passive income. You buy the token once, and earn reflections or rebases forever. That's great, so long as you don't think about what happens when too many people try to cash out their "income".

Other tokens rely purely on the Greater Fool Theory: You buy it, not because you want to have it, but because you're hoping to sell it at a higher price to someone else with the same plan. That can work out great for some people, but it's easy to see that it's not sustainable.

Still, there are people willing to buy such tokens. And as long as that's true, there will be people willing to create them too.

For some time, someone was creating a new token on the Binance Smart Chain every 30 minutes. They waited for people to buy it, and then pulled the liquidity, every 30 minutes, 24/7.

("pulling the liquidity" means taking money out of the liquidity pool. If someone else bought the token, they can now no longer sell it.)

This strategy worked quite well for them: Based on what I tracked, they made around 3000 USD a week. That's not a ton of money, but it's quite notable seeing as it's all (probably) automated, and takes no continued effort.

I don't know who bought those tokens and what their thought process was, but the lesson to take away is: Don't put your money into something if you don't know what value (if any) is behind it.