đWhitepaper
Author: Alex Cohen - Jan 2020
Exohood's Protocol V1
Abstract
The Exohood protocol was designed to be used as a public good, providing open-source functionality to give adopters and developers the ability to create smart contracts and decentralized applications. The protocol allows developers to keep a public update of applications and smart contracts faster and efficiently, provided by the mutual collaboration concessions that they prioritize, thanks to decentralization. The development of the protocol is continuous, infinite and elastic because is being an open-source and controlled by a decentralized community, it will have resistance to censorship and security, thus guaranteeing its continuous updating. Unlike other protocols, Exohood does not require the permission of another network and does not have an end because one of the networks ceases to exist, for example the protocols that work in wherever chain are limited to the existence of the network, that is why Exohood is developed in an independent network called ExoChain in which the protocol could always be continuous and not limited to censorship or the existence of a third network.
1. Protocol Features
The Exohood protocol is open source and its main feature is to create decentralized applications, the protocol works as an open-source database found on GitHub, and developers can launch their applications using these codes, the advantage of developing these codes within the protocol is that it allows the community of Exohood development contributors to update them, optimize them, look for errors to correct them and make it more secure.
2. Creating value
To generate use-value in the ecosystem and create a unique protocol for the automated exchange of tokens on (Ethereum, BNB Chain, Polygon, Fantom, Avalanche and Cronos), which can be implemented in any decentralized project for a unique adoption environment, ease of use, a more efficient gas rate and generate greater security for users. We development two version, Exoswap Desktop version and Exoswap Widget/API will be helpful for traders and will work particularly well as a component of other smart contracts that require guaranteed on-chain liquidity. Most exchanges maintain an order book and facilitate matching between buyers and sellers.
Exoswap smart contracts hold liquidity reserves of various tokens, and transactions are executed directly against these reserves. Prices are set automatically using the constant product market-making mechanism (x*y=k), keeping general reserves in relative balance. Reserves are pooled among a network of liquidity providers who supply tokens to the system in exchange for a proportional share of transaction fees.
An essential feature of Exoswap is using a registry/factory contract that implements a separate exchange contract for each ERC20 token. Each of these exchange contracts has a reserve of ETH and its associated ERC20.
This allows trades between the two based on relative supply. Swap contracts are linked through the registry, allowing direct ERC20 to ERC20 swaps between any token using ETH as an intermediary. The implementation will be easy for anyone without programming knowledge, once the smart contract is launched, a code is generated to be copied and pasted on the web, where the exchange will work as a widget interface.
3. Creating Decentralised Exchange
Exo_DEX_Factory is a smart contract used to manufacture and register ExoDex trading contracts. The createDex() public function allows any Ethereum user to implement a trade contract for any ERC20 that does not already have one. The factory stores a record of all tokens and their associated exchanges. With a token or exchange address, the getDex() and getToken() functions can be used to look up the other. The generation of the DEX does not perform any verification on a token when a trade contract is launched, only the limit of one trade per token is met. Users and interfaces should only interact with exchanges associated with tokens they trust.
4. ETH â ERC20 Trades
Each exchange contract (exodex_exchange.vy) is associated with a single ERC20 token and has a liquidity pool of ETH and that token. The exchange rate between ETH and a ERC20 is based on the relative sizes of their liquidity pools within the contract. This is done by keeping the relationship eth_pool * token_pool = unchanged. This invariant remains constant during trades and only changes when liquidity is added or removed from the market.
Below is a simplified version of ethToTokenSwap(), the function for converting ETH to ERC20 tokens:
eth_pool: uint256
token_pool: uint256
token: address(BEP20)
@public
@payable
def ethToTokenSwap():
fee: uint256 = msg.value / 500
invariant: uint256 = self.eth_pool * self.token_pool
new_eth_pool: uint256 = self.eth_pool + msg.value
new_token_pool: uint256 = invariant / (new_eth_pool - fee)
tokens_out: uint256 = self.token_pool - new_token_pool
self.eth_pool = new_eth_pool
self.token_pool = new_token_pool
self.token.transfer(msg.sender, tokens_out)
Note: For gas efficiency, eth_pool and token_pool are not stored variables. They are found using self.balance and through an external call to self.token.balanceOf(self)
When ETH is sent to the function, eth_pool increases. To keep the relationship eth_pool* token_pool = unchanged, the pool of tokens is reduced by a proportional amount. The amount by which the token_pool is reduced is the number of tokens purchased. This change in the reserve index changes the ETH exchange rate to ERC20, incentivizing trades in the opposite direction.
The exchange of tokens for ETH is done with the tokenToETHSwap() function:
@public
def tokenToETHSwap(tokens_in: uint256):
fee: uint256 = tokens_in / 500
invariant: uint256 = self.eth_pool * self.token_pool
new_token_pool: uint256 = self.token_pool + tokens_in
new_eth_pool: uint256 = self.invariant / (new_token_pool - fee)
eth_out: uint256 = self.eth_pool - new_eth_pool
self.eth_pool = new_eth_pool
self.token_pool = new_token_pool
self.token.transferFrom(msg.sender, self, tokens_out)
send(msg.sender, eth_out)
This increases token_pool and decreases bnb_pool, changing the price in the opposite direction. Below is an example of buying ETH from EXO.
Example: ETH â EXO
Note: This example uses a rate of 0.4% as it is an example implementation. The Exohood contract has a commission of 0.3%.
Liquidity providers deposit 1 ETH and 15,000 EXO (ERC20) into a smart contract. An invariant is automatically set such that ETH_pool * EXO_pool = invariant.
ETH_pool = 2
EXO_pool = 15000
invariant = 2 * 15000 = 30000
An EXO buyer sends 1 ETH to the contract. Liquidity providers charge a 0.4% fee and the remaining 0.996 BNB is added to the ETH_pool. The invariant is then divided by the new amount of ETH in the liquidity pool to determine the new EXO_pool size. The remaining EXO is sent to the buyer.
Buyer sends: 1 ETH
Fee = 1 ETH / 15000 = 0,00006667 ETH
ETH_pool = 2 + 1 - 0,00006667 = 2,99993333
EXO_pool = 30000/2,99993333 = 10000,2222
Buyer receives: 15000 - 10000,2222 = 4999,7778 EXO
The fee is now added back to the liquidity pool, which acts as a payment to liquidity providers that is collected when liquidity is withdrawn from the market. Since the fee is added after the price calculation, the invariant increases slightly with each trade, making the system profitable for liquidity providers. In fact, what the invariant actually represents is ETH_pool * EXO_pool at the end of the above operation.
ETH_pool = 2,99993333 + 0,00006667 = 3
EXO_pool = 10000,2222
new invariant = 3 * 10000,2222 = 30000,6666
In this case, the buyer received a rate of 4999.7778 EXO/ETH. However, the price has changed. If another buyer makes a trade in the same direction, he will get a slightly worse EXO/ETH rate. However, if a buyer trades in the opposite direction, he will get a slightly better ETH/EXO rate.
1 ETH in
4995 EXO out
Rate = 4999,7778 EXO/ETH
Purchases that are large relative to the total size of the liquidity pools will cause a price drop. In an active market, arbitrage will ensure that the price does not stray too far from other exchanges.
5. ERC20 â ERC20 Trades
Since ETH is used as a common pair for all ERC20 tokens, it can be used as an intermediary for direct ERC20 to ERC20 trades. For example, it is possible to convert from EXO to ETH on one exchange and then from ETH to MUS on another within a single transaction.
To convert from EXO to MUS (for example), a buyer calls the tokenToTokenSwap() function on the EXO swap contract:
contract Factory():
def getExchange(token_addr: address) -> address: constant
contract Exchange():
def ethToTokenTransfer(recipent: address) -> bool: modifying
factory: Factory
@public
def tokenToTokenSwap(token_addr: address, tokens_sold: uint256):
exchange: address = self.factory.getExchange(token_addr)
fee: uint256 = tokens_sold / 500
invariant: uint256 = self.eth_pool * self.token_pool
new_token_pool: uint256 = self.token_pool + tokens_sold
new_eth_pool: uint256 = invariant / (new_token_pool - fee)
bnb_out: uint256 = self.eth_pool - new_eth_pool
self.eth_pool = new_eth_pool
self.token_pool = new_token_pool
Exchange(exchange).ethToTokenTransfer(msg.sender, value=eth_out)
where token_addr is the address of the MUS token and tokens_sold is the amount of EXO being sold. This function first checks the factory to retrieve the MUS exchange address. The exchange then converts the EXO entry to ETH. However, instead of returning the purchased ETH to the buyer, the function calls the bnbToTokenTransfer() payment function on the MUS exchange:
@public
@payable
def ethToTokenTransfer(recipent: address):
fee: uint256 = msg.value / 500
invariant: uint256 = self.eth_pool * self.token_pool
new_eth_pool: uint256 = self.eth_pool + msg.value
new_token_pool: uint256 = invariant / (new_eth_pool - fee)
tokens_out: uint256 = self.token_pool - new_token_pool
self.eth_pool = new_eth_pool
self.token_pool = new_token_pool
self.invariant = new_eth_pool * new_token_pool
self.token.transfer(recipent, tokens_out)
ethToTokenTransfer() receives the ETH and address from the buyer, verifies that the call is from an exchange on the registry, converts the ETH to MUS, and forwards the MUS to the original buyer. ethToTokenTransfer() works identically to ethToTokenSwap() but has the additional input parameter recipient: address. This is used to forward purchased tokens to the original buyer instead of msg.sender, which in this case would be the Exoswap.
6. Swaps vs Transfers
The ethToTokenSwap(), tokenToethSwap() and tokenToTokenSwap() functions return the purchased tokens to the buyers address.
The ethToTokenTransfer(), tokenToethTransfer(), and tokenToTokenTransfer() functions allow buyers to make a trade and then immediately transfer the purchased tokens to a recipient's address.
6. Providing Liquidity
6.1 Adding Liquidity:
Adding liquidity requires depositing an equivalent value of ETH and ERC20 tokens into the associated ERC20 token exchange contract.
The first liquidity provider to join a pool sets the initial exchange rate by depositing what it thinks is value equivalent to ETH and ERC20 tokens. If this ratio is out of whack, arbitrage traders will balance prices at the expense of the initial liquidity provider.
All future liquidity providers deposit ETH and ERC20 using the exchange rate at the time of your deposit. If the exchange rate is bad, there is a profitable arbitrage opportunity that will correct the price.
Liquidity Tokens
Liquidity tokens are minted to track the relative proportion of total reserves that each liquidity provider has contributed. They are highly divisible and can be burned at any time to return a proportionate share of the markets' liquidity to the provider.
Liquidity providers call the addLiquidity() function to deposit into reserves and mint new liquidity tokens:
@public
@payable
def addLiquidity():
token_amount: uint256 = msg.value * token_pool / eth_pool
liquidity_minted: uint256 = msg.value * total_liquidity / eth_pool
eth_added: uint256 = msg.value
shares_minted: uint256 = (eth_added * self.total_shares) / self.eth_pool
tokens_added: uint256 = (shares_minted * self.token_pool) / self.total_shares)
self.shares[msg.sender] = self.shares[msg.sender] + shares_minted
self.total_shares = self.total_shares + shares_minted
self.eth_pool = self.eth_pool + eth_added
self.token_pool = self.token_pool + tokens_added
self.token.transferFrom(msg.sender, self, tokens_added)
The amount of liquidity tokens minted is determined by the amount of ETH sent to the feature. It can be calculated using the equation:
Depositing ETH into reserves also requires depositing a value equivalent to ERC20 tokens. This is calculated with the equation:
6.2 Removing Liquidity
Providers can burn their liquidity tokens at any time to withdraw their proportionate share of ETH and ERC20 tokens from pools.
ETH and ERC20 tokens are withdrawn at the current exchange rate (reserve rate), not at the rate of your original investment. This means that some value may be lost due to market fluctuations and arbitrage.
Fees collected during trades are added to total liquidity pools without minting new liquidity tokens. Because of this, <ethWithdrawn> and <tokensWithdrawn> include a pro rata share of all fees collected since the liquidity was first added.
6.3 Liquidity Tokens
Exohood Liquidity Tokens represent a contribution from liquidity providers to a ETH-ERC20 pair. They are ERC20 tokens and include a full implementation of transferring tokens as well as allowing tokens to be approved so that another third party in the chain can spend them.
This allows liquidity providers to sell their liquidity tokens or transfer them between accounts without removing liquidity from the funds. Liquidity tokens are specific to a single ETHâERC20 exchange. There is no single unifying ERC20 token for this project.
7. Fee Structure
ETH to ERC20 trades
0.3% fee paid in ETH
ERC20 to ETH trades
0,3% fee paid in ERC20 tokens
ERC20 to ERC20 trades
0.3% fee paid in ERC20 tokens for ERC20 to ETH swap on input exchange
0.3% fee paid in ETH for ETH to ERC20 swap on output exchange
Effectively 0.5991% fee on input ERC20
There is a 0.3% fee to switch between ETH and ERC20 tokens. This commission is distributed among the liquidity providers in proportion to their contribution to the liquidity reserves. Since ERC20 to ERC20 transactions include a ERC20 to ETH swap and a ETH to ERC20 swap, the fee is paid on both exchanges. There are no platform fees.
Swap fees are immediately deposited into liquidity reserves. Since the total reserves increase without adding additional share tokens, this increases the value of all share tokens equally. This works as a payment to liquidity providers that can be collected by burning shares.
7.1 Custom Fee
The contract launched to create an Exchange will have a fee for the implementer of 0.1% of all transactions made on its website where the widget is implemented or any ramp used as an api exchanged tokens in the Ethereum network, this function It can be used as a B2B both for projects that launch their own token and companies that want to implement the Exchange function through a widget or api ramp in their business.
8. Custom Pools
8.1 ERC20 to Exchange
Additional functions tokenToExchangeSwap() and tokenToExchangeTransfer() add to the flexibility of Exoswap. These functions convert ERC20 tokens to ETH and attempt a ethToTokenTransfer() on a user input address. This allows ERC20 to ERC20 trades against custom Exoswap trades that are not from the same factory, as long as they implement the proper interface. Custom exchanges can have different curves, managers, private liquidity pools, FOMO-based Ponzi schemes, or anything else.
9. Upgrades
Updating censorship resistant decentralized smart contracts is difficult however Exoswap will work collaboratively with developers from the protocol community to always stay on top. If an improved Exoswap skin is created in higher versions, a new factory contract can be implemented. Liquidity providers can choose to move to the new system or remain in the old one, which would be versions for example (V1,V2,V3 and so on).
The tokenToExchange functions allow transactions with exchanges launched from different factories. This can be used for backward compatibility. Exchanges from ERC20 to ERC20 will be possible within versions that use the tokenToToken and tokenToExchange functions. However, in all versions only tokenToExchange will work. All updates are optional and backward compatible.
10. Exoswap technical market maker model and implementation
10.1 Formal Overview of x à y = k Model
Consider a decentralized exchange that exchanges two tokens X and Y. Let x and y be the number of tokens X and Y, respectively, that the exchange currently reserves. The token exchange price is determined by the ratio of x and y, so that the product x à y is preserved. That is, when you sell âx tokens, you will get ây tokens such that x à y = (x + âx) à (y â ây). Thus, the price (âx/ây) is a function of x/y. Specifically, when you trade âx with ây, the trading token reserves are updated as follows:
Now consider a fee for each token exchange. Let 0 ⤠Ī < 1 be a fee, eg Ī = 0.003 for the 0.3% fee schedule.
Note: that we have the same formula with the above if there is no fee, i.e. Îŗ = 1. Also note that the product of x and y increases slightly for each trade due to the fee. That is, xⲠĪ à yⲠĪ > x à y when Ī > 0, while xⲠĪ à yⲠĪ = x à y when Ī = 0 (no charge). In the contract implementation of this model, token X denotes ERC and token Y denotes the token to trade. In addition, you can invest and divest, sharing exchange token reserves, which we will formalize later using the concept of liquidity. Since the implementation uses integer arithmetic, we also formally analyze the approximation error caused by integer rounding, showing that the error is bounded and does not lead to violation of critical properties denoted by the mathematical model.
10.2 Minting Liquidity
An investor can generate liquidity by depositing both ERC and token.
10.3 Add Liquidity
We formulate the mathematical definition of mint liquidity.
Definition a. <add Liquidity spec> takes as input âe > 0 and updates the state as follows:
where (e, t, l) addLiquidityspec (âe) â (eâ˛, tâ˛, lâ˛)
eⲠ= (1 + ι)e
tⲠ= (1 + ι)t
lⲠ= (1 + ι)l
Here, an investor deposits both âe bnb and ât = tⲠâ t tokens and mints âl = lⲠâ l liquidity. The invariant is that the ratio of e : t : l is conserved and k = e à t increases, as formulated in the following theorem.
Theorem 1. Let (e,t,l) <addliquidityspec> (âe) â (eâ, tâ, lâ). Let k = e x t and kâ = eâ x tâ. Then, we have the following:
e : t : l = eâ : tâ : lâ
k < kâ
Definition b. <addLiquiditycode> takes as input an integer âe > 0 â Z and updates the state as follows:
(e, t, l) addliquiditycode âe â (eââ, tââ, lââ)
where eââ = e + âe = (1+Îą)e
10.4 Burning Liquidity
An investor can withdraw their ERC and token deposit by burning their portion of liquidity. We formulate the mathematical definition of burning liquidity, being dual to minting liquidity.
Definition c. <removeliquidityspec> takes as input 0 < âl < l and updates the state as follows:
where (e, t, l) removeliquidityspec (âl) â (eâ, tâ, lâ)
eâ = (1 - Îą)e
tâ = (1 - Îą)t
lâ = (1 - Îą)l
and
Here, an investor burns âl liquidity and withdraws âe = e â eⲠbnb and ât = t â tⲠtokens. The invariant is dual to that of coining liquidity.
Definition d. <removeliquiditycode> takes as input an integer 0 < âl < l and updates the state as follows:
where (e, t, l) removeliquiditycode (âl) â (eââ, tââ, lââ)
where
lââ = l - âl = (1 - Îą)l
10.5 Token Price Calculation
We formalize the functions by calculating the current price of the token. Suppose there are two tokens X and Y, and let x and y be the number of X and Y tokens currently reserved by the exchange, respectively. We have two price calculation functions: getInputPrice and getOutputPrice. Given âx, the getInputPrice function calculates how many Y tokens (ie ây) can be purchased by selling âx. On the other hand, given ây, the getOutputPrice function calculates how many tokens X (ie âx) must be sold to buy ây. Please note that these functions do not update the status of the exchange.
Definition e. Let Ī be the commercial rate. <getInputPricespec> takes as input âx > 0, x, y y, and outputs ây such that:
getinputpricespec
where . Also, we have:
Definition f. Let Ī be the trade fee. <getinputpricespec> takes as input âx > 0, x, and y, outputs ây such that:
getinputpricespec (âx) (x , y) =
where .Also, we have:
xâ = x + âx = (1 + Îą) x
yâ = y -
Definition h. Let Ī be the trade fee. <getinputpricecode> takes a input âx > 0, x, and y â Z, and outputs ây â Z such that:
getInputPricecode (âx) (x , y) =
where . Also, we have:
xââ = x + âx = (1 + Îą) x
yââ = y - ây
In the contract implementation Ī = 0.003, and <getinputpricecode> (âx)(x, y) is implemented as follows:
(997 * âx * y) / (1000 * x + 997 * âx)
Definition i. Let Ī be the trade fee. <getoutpricespec> takes a input 0 < ây < y , x , and y , and outputs âx such that:
geteoutputpricespec (ây)(x, y) = âx =
where β < 1 and Îŗ = 1 - Ī . Also, we have:
yâ = y - ây = (1 - β)y
Definition j. Let Ī be the trade fee. <getoutpupricecode> takes as input 0 < ây < y , x , and y â Z,and outputs âx â Z such that:
getoutpricespec (ây) (x , y) = âx
where β < 1 and Îŗ = 1 - Ī . Also, we have:
xââ = x - âx = (1 - β) x
In the contract implementation Ī = 0.003, and <getinputpricecode> (ây)(x, y) is implemented as follows:
(1000 * x * ây) / (997 * (y - ây) ) + 1
10.6 Trading Tokens
Now we formalize the token exchange functions that update the exchange state. In this section, we present a formal specification of bnbToToken (including swap and transfer).
a. ethToTokenspec
ethToTokenspec takes and input âe(âe > 0) and updates the state as follows:
(e , t, l) ethToTokenspec (âe) â (eâ , tâ , l)
where ;
eâ = e + âe
tâ = t - getinputpricespec (âe , e , t)
b. ethToTokencode
ethToTokenspec takes and integer input âe(âe > 0) and updates the state as follows
(e , t, l) ethToTokencode (âe) â (eââ , tââ , l)
where;
eââ = e + âe
tââ = t - getinputpricecode (âe , e , t) = [tâ]
We present a formal specification of tokenToToken (including swap and transfer). Suppose there are two exchange contracts A and B, whose states are (eA, tA, lA) and (eB , tB , lB ) respectively.
a. tokenToTokenspec takes an input âta (>0) and updates the states as follows:
{ ( ea, ta , la ) , (eb, tb , lb) } tokenToToken (ât a) â { ( eâ1 a, tâ a, la) , ( eâ b, tâ b, lb) }
where;
tâ˛a = ta + âta
âeAspec = getInputPricespec(âtA, tA, eA)
eⲠA = e â âeAspec
eâ˛B = eB + âeAspec
âtBspec = getInputPricespec(âeAspec , eB , tB )
tⲠB = tB â âtBspec
11. Disclaimer
This paper is for general information purposes only. It does not constitute investment advice or a recommendation or solicitation to buy or sell any investment and should not be used in the evaluation of the merits of making any investment decision. It should not be relied upon for accounting, legal or tax advice or investment recommendations. This paper reflects current opinions of the authors and the opinions reflected herein are subject to change without being updated.
Last updated