- Token exchange
- The token exchange is a distributed, censorship-resistant market place in the form of a smart contract on Ethereum, that performs automated trading of assets with liquidity-sensitive automated pricing. A token exchange allows traders to swap ETH with a specific ERC-20 token TOK in both directions by adding liquidity on one side and withdrawing from the reserves on the other side
- Constant Product Market Place CPMM
- The CPMM is a token exchange that uses
the constant product formula
xy = k
for liquidity-sensitive pricing, wherex
is the reserve of token X e.g. ETH, andy
is the reserves of token Y e.g. ERC-20 token TOK. The first liquidity provider that deposits the initial liquidity to the token exchange defines the initial price for TOK in terms of the deposited ETH. All subsequent deposits and withdrawals of liquidity are executed at the at the current constantly changing price. Buying a TOK increases TOK price and vice versa. The constant product formulaxy = k
of the CPMM protects the token exchange from being completely drained. The higher is the demand, the more costly is the asset. - Trade fee
- The trading fee e.g. 0.3% is taken from each trade and is added to the corresponding reserves hold on the token exchange. The total value of reserves increases over time due to the fee. The fee is distributed between liquidity providers proportionally to their liquidity contribution, that is measured by the value of their LIQ accounts. The more LIQ a liquidity provider owns, the more fees he earns
- Liquidity provisioning
- The liquidity provisioning allows anyone to become a liquidity provider and deposit the equivalent values of both ETH and TOK to the corresponding ETH and TOK reserves hold by the token exchange. When a liquidity provider deposits liquidity, the token exchange mints and deposits an equivalent value of the liquidity token LIQ to the liquidity provider account. There is no supply limit to LIQ. When a liquidity provider withdraws liquidity, the token exchange burns an equivalent value of the liquidity token LIQ from the liquidity provider account. The value of the liquidity token LIQ indicates the relative contribution of each liquidity provider to the ETH and TOK reserves of the token exchange
- Slippage
- The slippage is the difference in price between the expected price of a submitted transaction and the actual price of the executed transaction due to the constant price fluctuation based on the available liquidity and the volume of trading. A trader specifies the maximum acceptable slippage for every trade: min bought value on sell trades, max sell value on buy trades
- Token exchange
- The
TokenExchange
contract is the ERC-20 fungible token that represents the exchange liquidity tokens LIQ minted when the liquidity in the form of ETH and TOK is deposited, and burned when the liquidity in the form of ETH and TOK is withdrawn. The token exchange contract also holds the address of the ERC-20 fungible token TOK that is traded on the exchange. A fee e.g. 0.3% is charged to a trader that swaps ETH with TOK and vice versa on the exchangecontract TokenExchange is FungibleToken { address public factory; IFungibleToken public token; // TOK to swap uint fee; // 1000 - feePermille constructor(address tok, uint feePermille) FungibleToken("Liquidity token", "LIQ", 0) { validAddress(address(tok)); factory = msg.sender; token = IFungibleToken(tok); fee = 1000 - feePermille; } }
- Liquidity deposit
- The
EvLiquidityDeposit
event is emitted when liquidity is deposited by a liquidity provider. The liquidity deposit event provides the exchange address, the depositor address, the values of the deposited ETH and TOK, and the value of the minted liquidity token LIQevent EvLiquidityDeposit( address indexed exch, address indexed dps, uint eth, uint tok, uint liq );
- Liquidity withdraw
- The
EvLiquidityWithdraw
event is emitted when liquidity is withdrawn by a liquidity provider. The liquidity withdraw event provides the exchange address, the withdrawer address, the values of the withdrawn ETH and TOK, and the value of the burned liquidity token LIQevent EvLiquidityWithdraw( address indexed exch, address indexed wdr, uint eth, uint tok, uint liq );
- Deposit liquidity
- The
depositLiquidity
function takes the implicit ETH value, and the max TOK value that a liquidity provider is willing to deposit, and the min LIQ value that the liquidity provider whats to receive in return. The deposit liquidity function calculates the actual values of TOK to deposit and LIQ to mint using theliquidityDeposit
function. When the reserves of ETH and TOK are zero on the exchange, the first depositor defines the price of TOK in terms of LIQ that is initially is equal to the provided ETH. For every subsequent liquidity deposit- The value of the deposited TOK is directly proportional to the deposited ETH, and is inversely proportional to the ETH reserves
- The value of the received LIQ is directly proportional to the deposited ETH, and is inversely proportional to the ETH reserves
If the actual values of the deposited TOK and the received LIQ are within the requested limits, the TOK value is deposited to the exchange account and the LIQ value is minted to the depositor account. The deposit liquidity function returns the actual value of LIQ. The deposit liquidity function emit the
EvLiquidityDeposit
eventfunction depositLiquidity(uint maxTok, uint minLiq) external payable returns (uint valLiq) { // Only key decisions and actions uint valEth = msg.value; // ETH already deposited for the exchange uint resEth = exch.balance - valEth; uint resTok = token.balanceOf(exch); uint resLiq = totalSupply; if (resLiq == 0) { // The first depositor sets the TOK price in terms of LIQ valTok = maxTok; valLiq = valEth; } else { valTok = valEth * resTok / resEth; valLiq = valEth * resLiq / resEth; } bool success = mint(dps, valLiq); // Mint LIQ for the depositor success = token.transferFrom(dps, exch, valTok); // Deposit TOK for exchange emit EvLiquidityDeposit(exch, dps, valEth, valTok, valLiq); return valLiq; }
- Withdraw liquidity
- The
withdrawLiquidity
function takes the min ETH value and the min TOK value that a liquidity provider wants to withdraw and specifies the value of LIQ to burn from the liquidity provider account on the exchange. The withdraw liquidity function calculates the actual values of ETH and TOK to withdraw- The value of ETH to withdraw is directly proportional to the LIQ value, and is inversely proportional to the LIQ reserves
- The value of TOK to withdraw is directly proportional to the LIQ value, and is inversely proportional to the LIQ reserves
If the actual values of ETH and TOK to withdraw are within the requested limits, the LIQ value is burned from the withdrawer account, the TOK value is deposited to the withdrawer account, and the ETH value is deposited to the withdrawer account. The withdraw liquidity function returns the actual values of ETH and TOK withdrawn. The withdraw liquidity function emits the
EvLiquidityWithdraw
eventfunction withdrawLiquidity(uint minEth, uint minTok, uint valLiq) external returns (uint valEth, uint valTok) { // Only key decisions and actions uint resEth = exch.balance; uint resTok = token.balanceOf(exch); uint resLiq = totalSupply; uint valEth = valLiq * resEth / resLiq; uint valTok = valLiq * resTok / resLiq; bool success = burn(wdr, valLiq); // Burn LIQ from the withdrawer success = token.transfer(wdr, valTok); // Deposit TOK to the withdrawer (success, ) = wdr.call{value: valEth}(""); // Deposit ETH to the withdrawer emit EvLiquidityWithdraw(exch, wdr, valEth, valTok, valLiq); return (valEth, valTok); }
- Token buy
- The
EvTokenBuy
event is emitted when the value of TOK is bought for ETH. The token buy event provides the exchange address, the address of a buyer who initiated the trade, the address of a recipient who receives the TOK value, who may be the buyer, the value of ETH sold, and the value of TOK boughtevent EvTokenBuy( address indexed exch, address indexed byr, address indexed rcp, uint eth, uint tok );
- Ether refund
- The
EvEtherRefund
event is emitted when more than necessary ETH was provided to buy a fixed value of TOK. The refund of ETH is performed back to the buyer account who initiated the trade. The ETH refund event provides the exchange address, the address of a buyer who initiated the trade, and the value of ETH refundedevent EvEtherRefund(address indexed exch, address indexed byr, uint eth);
- In price
- The
inPrice
function takes the fixed value of ETH or any other cryptocurrency or token to sell and calculates the value of TOK or any other cryptocurrency or token to buy based on the current reserves of ETH and TOK in the exchange. The in price function returns the calculated value of TOK to buy given the fixed value of ETH to sellfunction inPrice(uint valIn, uint resIn, uint resOut) internal view returns (uint) { uint feeValIn = fee * valIn; uint valOut = feeValIn * resOut / (1000 * resIn + feeValIn); return valOut; }
- Out price
- The
outPrice
function takes the fixed value of TOK or any other cryptocurrency or token to buy and calculates the value of ETH or any other cryptocurrency or token to sell based on the current reserves of ETH and TOK in the exchange. The out price function returns the calculated value of ETH to sell given the fixed value of TOK to buyfunction outPrice(uint valOut, uint resIn, uint resOut) internal view returns (uint) { uint valIn = 1000 * valOut * resIn / (fee * (resOut - valOut)); return valIn; }
- In swap [to] ETH TOK
- The
inSwap[To]EthTok
function sells the implicitly provided value of ETH and buys the calculated value of TOK if the TOK value is above the specified min TOK limit. The TOK value is deposited either to the specified recipient (theinSwapTo
function) or directly to the buyer (theinSwap
function) who initiated the trade. The in swap function returns the TOK value bought. The in swap function emits theEvTokenBuy
eventfunction inSwapToEthTok(uint minTok, address rcp) public payable returns (uint valTok) { // Only key decisions and actions uint valEth = msg.value; uint resEth = exch.balance - valEth; uint resTok = token.balanceOf(exch); uint valTok = inPrice(valEth, resEth, resTok); bool success = token.transfer(rcp, valTok); emit EvTokenBuy(exch, byr, rcp, valEth, valTok); return valTok; }
- Out swap [to] ETH TOK
- The
outSwap[To]EthTok
function sells the implicitly provided value of ETH and buys the fixed value of TOK if the provided ETH value is enough. The extra provided ETH value is refunded to the buyer. The TOK value is deposited either to the specified recipient (theoutSwapTo
function) or directly to the buyer (theoutSwap
function) who initiated the trade. The out swap function returns the actual ETH value sold. The out swap function emits theEvTokenBuy
eventfunction outSwapToEthTok(uint valTok, address rcp) public payable returns (uint valEth) { // Only key decisions and actions uint maxEth = msg.value; uint resEth = exch.balance - maxEth; uint resTok = token.balanceOf(exch); uint valEth = outPrice(valTok, resEth, resTok); if (valEth < maxEth) { uint refEth = maxEth - valEth; (bool refSucc, ) = byr.call{value: refEth}(""); emit EvEtherRefund(exch, byr, refEth); } bool success = token.transfer(rcp, valTok); emit EvTokenBuy(exch, byr, rcp, valEth, valTok); return valEth; }
- Token sell
- The
EvTokenSell
event is emitted when the TOL value is sold for ETH. The token sell event provides the exchange address, the address of a seller who initiated the trades, the address of a recipient who receives the ETH value, who may be the seller, the value of TOK sold, and the value of ETH boughtevent EvTokenSell( address indexed exch, address indexed sel, address indexed rcp, uint tok, uint eth );
- In swap [to] TOK ETH
- The
inSwap[To]TokEth
function sells the TOK value and buys the ETH value if the ETH value is above the specified min ETH limit. The ETH value is deposited either to the specified recipient (theinSwapTo
function) or directly to the seller (theinSwap
function) who initiated the trade. The in swap function returns the ETH value bought. The in swap function emits theEvTokenSell
eventfunction inSwapToTokEth(uint valTok, uint minEth, address rcp) public returns (uint valEth) { // Only key decisions and actions uint resEth = exch.balance; uint resTok = token.balanceOf(exch); uint valEth = inPrice(valTok, resTok, resEth); bool success = token.transferFrom(sel, exch, valTok); (success, ) = rcp.call{value: valEth}(""); emit EvTokenSell(exch, sel, rcp, valTok, valEth); return valEth; }
- Out swap [to] TOK ETH
- The
outSwap[To]TokEth
function sells the TOK value and buys the fixed value of ETH if the provided TOK value is below the specified max TOK limit. The ETH value is deposited either to the specified recipient (theoutSwapTo
function) or directly to the seller (theoutSwap
function) who initiated the trade. The out swap function returns the actual TOK value sold. The out swap function emits theEvTokenSell
eventfunction outSwapToTokEth(uint maxTok, uint valEth, address rcp) public returns (uint valTok) { // Only key decisions and actions uint resEth = exch.balance; uint resTok = token.balanceOf(exch); uint valTok = outPrice(valEth, resTok, resEth); bool success = token.transferFrom(sel, exch, valTok); (success, ) = rcp.call{value: valEth}(""); emit EvTokenSell(exch, sel, rcp, valTok, valEth); return valTok; }