This project is intended to provide an easy and efficient way to exchange tokens and XTZ on Tezos blockchain in a wide number of directions. Using smart contracts listed in this repo users can add their tokens to exchange, provide liquidity, and potentially make a profit in a fully decentralized way.
The current implementation supports FA1.2 and FA2.
The solution consists of 4 types of contracts:
Factory
- singleton used to deploy new exchange pair;Dex
- contract for TokenX-XTZ pair exchanges;Token
- FA token implementation.MetadataStorage
- contract to store and upgrade the shares token metadata.
.
├── ci/ # scripts for continues integration
├── contracts/ # contracts
|──────── main/ # the contracts to be compiled
|──────── partial/ # the code parts imported by main contracts
├── test/ # test cases
├── storage/ # initial storage for contract originations
├── scripts/ # cli for dex/factory actions
├── test.md # cases covered by tests
├── README.md # current file
├── .env
├── .gitignore
├── package.json
└── Architecture.png
-
Installed NodeJS (tested with NodeJS v12+)
-
Installed Yarn (NPM isn't working properly with
ganache-cli@6.11.0-tezos.0
) -
Installed Ligo:
curl https://gitlab.com/ligolang/ligo/raw/dev/scripts/installer.sh | bash -s "next"
- Installed node modules:
cd quipuswap-core && yarn
- Configure
truffle-config.js
if needed.
To compile and deploy contracts to Delphinet
- Chose configure the version -
FA12
orFA2
- by settingEXCHANGE_TOKEN_STANDARD
in.env
and run:
yarn run migrate-delphinet
For other networks:
yarn run migrate # development
yarn run migrate --network NAME # other networks
Contracts are processed in the following stages:
- Compilation
- Deployment
- Configuration
- Interactions on-chain
As the Quipuswap supports 2 token standards that vary only in the token interface implementation and the inter contract communication between Dex and external tokens, the shared codebase is used. Therefore to work with the specific standard version, you should configure it by setting EXCHANGE_TOKEN_STANDARD
in .env
to either FA12
or FA2
.
To compile the contracts run:
yarn compile
Artifacts are stored in the build/contracts
directory.
For deployment step the following command should be used:
yarn migrate
Addresses of deployed contracts are displayed in terminal. At this stage, new MetadataStorage, Factory are originated. Aditionaly, for testnets two new pairs are deployed.
The Ligo interfaces of the contracts can be found in contracts/partials/I__CONTRACT_NAME__.ligo
Factory contains the code template for the Dex
Token-XTZ pair contracts, deploys them and keeps the list of deployed pairs. The functions for Dex
are stored in Factory
contract but because of gas and operation limits their code cannot be stored in Factory contract during the origination: they are added separately one by one.
New exchange pairs are registered and deployed via LaunchExchange
. The only difference between factory standards is the token identifier type, for F1.2 it is the token address and for FA2 it is the address the token id:
#if FA2_STANDARD_ENABLED
type token_identifier is (address * nat)
#else
type token_identifier is address
#endif
The contract has the following entrypoints:
type launch_exchange_params is record [
token : token_identifier;
token_amount : nat;
]
type set_token_function_params is record [
func : token_func;
index : nat;
]
type set_dex_function_params is record [
func : dex_func;
index : nat;
]
type exchange_action is
| LaunchExchange of launch_exchange_params
| SetDexFunction of set_dex_function_params
| SetTokenFunction of set_token_function_params
Sets the dex specific function. Is used before the whole system is launched.
index
: the key in functions map;
func
: function code.
Each index
is designed for a specific func
which functionality is described below.
Sets the FA1.2 function. Is used before the whole system is launched.
index
: the key in functions map;
func
: function code.
Each index
is designed for a specific func
which functionality is described below.
Deploys a new empty Dex
for token
, stores the address of the new contract, and puts initial liquidity; has to be called with XTZ amount that will be used as initial liquidity.
token
: address(address and token id for FA2) of the paired token;
token_amount
: amount of tokens that will be withdrawn from the user account and used as initial liquidity.
tez_amount
(not an argument) : the XTZ for initial liquidity should be sent along with the launch transaction.
Dex
fully implements FA1.2/FA2 token interface. For more details check the this spec and this spec. And the extends it with other exchange-related methods.
The contract has the following entrypoints common for both standards:
type tez_to_token_payment_params is record [
amount : nat;
receiver : address;
]
type token_to_tez_payment_params is record [
amount : nat;
min_out : nat;
receiver : address;
]
type divest_liquidity_params is record [
min_tez : nat;
min_tokens : nat;
shares : nat;
]
type vote_params is record [
candidate : key_hash;
value : nat;
voter : address;
]
type veto_params is record [
value : nat;
voter : address;
]
type dex_action is
| InitializeExchange of (nat)
| TezToTokenPayment of tez_to_token_payment_params
| TokenToTezPayment of token_to_tez_payment_params
| InvestLiquidity of (nat)
| DivestLiquidity of divest_liquidity_params
| Vote of vote_params
| Veto of veto_params
| WithdrawProfit of (address)
type default_params is unit
type use_params is dex_action
For FA1.2 standard compatibility the following entrypoints are implemented:
type transfer_params is michelson_pair(address, "from", michelson_pair(address, "to", nat, "value"), "")
type approve_params is michelson_pair(address, "spender", nat, "value")
type balance_params is michelson_pair(address, "owner", contract(nat), "")
type allowance_params is michelson_pair(michelson_pair(address, "owner", address, "spender"), "", contract(nat), "")
type total_supply_params is (unit * contract(nat))
type token_action is
| ITransfer of transfer_params
| IApprove of approve_params
| IGetBalance of balance_params
| IGetAllowance of allowance_params
| IGetTotalSupply of total_supply_params
type full_action is
| Use of use_params
| Default of default_params
| Transfer of transfer_params
| Approve of approve_params
| GetBalance of balance_params
| GetAllowance of allowance_params
| GetTotalSupply of total_supply_params
For FA2 standard compatibility the following entrypoints are implemented:
type transfer_params is list (transfer_param)
type token_metadata_registry_params is contract (address)
type update_operator_params is list (update_operator_param)
type token_action is
| ITransfer of transfer_params
| IBalance_of of balance_params
| IToken_metadata_registry of token_metadata_registry_params
| IUpdate_operators of update_operator_params
type full_action is
| Use of use_params
| Default of default_params
| Transfer of transfer_params
| Balance_of of balance_params
| Token_metadata_registry of token_metadata_registry_params
| Update_operators of update_operator_params
Used to collect rewards from bakers, donations or misguided payments without specified entrypoint.
Executes the exchange-related which code is stored in map of lamdas and thus the index
param is needed.
Actions have the following parameters (index in the list matches the index in lambdas
):
Sets initial liquidity, XTZ must be sent.
amount
: the token amount for initial liquidity;
tez_amount
(not an argument) : the XTZ for initial liquidity should be send along with the launch transaction.
Exchanges XTZ to tokens and sends them to receiver
; operation is reverted if the amount of exchanged tokens is less than amount
.
amount
: min amount of tokens received to accept exchange;
receiver
: tokens received;
tez_amount
(not an argument) : the XTZ to be exchanged.
Exchanges amount
tokens to XTZ and sends them to receiver
; operation is reverted if the amount of exchanged XTZ is less than min_out
.
amount
: amount of tokens to be exchanged;
min_out
: min amount of XTZ received to accept exchange;
receiver
: tokens received;
Withdraws delegation reward of the sender to receiver
address.
receiver
: XTZ received;
Mints min_shares
by investing tokens and XTZ; the corresponding amount of XTZ has to be sent via transaction and max amount of tokens to be spent should be approved for Dex
.
max_tokens
: the max amount of tokens to be invested;
tez_amount
(not an argument) : the XTZ to be provided as liquidity.
Burns shares
and sends tokens and XTZ to the owner; operation is reverted if the amount of divested tokens is smaller than min_tokens
or the amount of divested XTZ is smaller than min_tez
.
shares
: amount of shares to be burnt;
min_tez
: min amount of XTZ received to accept the divestment;
min_tokens
: min amount of tokens received to accept the divestment;
Votes for candidate
with shares of voter
.
candidate
: the chosen baker;
value
: amount of shares that are used to vote;
voter
: the account from which the voting is done.
Votes against current delegate with value
shares of voter
; the value
is frozen and can't be transferred or used for another voting.
value
: amount of shares that are used to vote against the chosen baker;
voter
: the account from which the veto voting is done.
Implements FA1.2 or FA2 token interface.
Stores the metadata for Dex as it can't be placed inside the Dex
contract because of operation size limits under the current protocol rules.
The metadata can be updated by authorities to follow the unstable metadata standards.
type update_owner_type is record [
owner : address;
add : bool;
]
type metadata_type is map (string, bytes)
type storage is record [
metadata : metadata_type;
owners : set(address);
]
type storage_action is
| Update_owners of update_owner_type
| Update_storage of metadata_type
| Get_metadata of contract (metadata_type)
If you'd like to run tests on the local environment, you might want to run ganache-cli
for Tezos using the following command:
yarn start-sandbox
Truffle framework is used for testing. Run:
yarn test
NOTE: if you want to use a different network, configure truffle-config.js
. If you need to use a different standard, configure $EXCHANGE_TOKEN_STANDARD
in .env