diff --git a/examples/python/oracle_arb_finder/.env-example b/examples/python/oracle_arb_finder/.env-example new file mode 100644 index 0000000..ba4acce --- /dev/null +++ b/examples/python/oracle_arb_finder/.env-example @@ -0,0 +1 @@ +BLOCKCHAIN_PROVIDER= \ No newline at end of file diff --git a/examples/python/oracle_arb_finder/.gitignore b/examples/python/oracle_arb_finder/.gitignore new file mode 100644 index 0000000..c888ee2 --- /dev/null +++ b/examples/python/oracle_arb_finder/.gitignore @@ -0,0 +1,148 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +.vscode/ +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +*.code-workspace + +# Local History for Visual Studio Code +.history/ diff --git a/examples/python/oracle_arb_finder/README.md b/examples/python/oracle_arb_finder/README.md new file mode 100644 index 0000000..ad6d962 --- /dev/null +++ b/examples/python/oracle_arb_finder/README.md @@ -0,0 +1,38 @@ +# Oracle OrFeed + +Oracle OrFeed is a Python program which retrieves the prices of the tokens provided by the oracle OrFeed, and try to find arb opportunities. + +## Environnement +You have to rename `.env-example` to `.env`. Then insert your BLOCKCHAIN_PROVIDER in `.env` file. This can be +NB : Leave the variable network as is. +``` +BLOCKCHAIN_PROVIDER= +NETWORK=mainnet +``` + +## Installation + +Create a virtual environment and nstall the package listed in requirements.txt + +```bash +python3 -m venv venv +source venv/bin/activate +pip3 install -r requirements.txt +``` + +## Usage + +```python +python3 app.py +``` + +## Author +This project is forked from [https://github.com/edd34/oracle_orfeed](https://github.com/edd34/oracle_orfeed) + +## Contributing +Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change. + +Please make sure to update tests as appropriate. + +## License +[MIT](https://choosealicense.com/licenses/mit/) \ No newline at end of file diff --git a/examples/python/oracle_arb_finder/app.py b/examples/python/oracle_arb_finder/app.py new file mode 100644 index 0000000..2f3466f --- /dev/null +++ b/examples/python/oracle_arb_finder/app.py @@ -0,0 +1,26 @@ +from helper.get_price import ( + get_raw_price_async, + get_clean_price, + compute_arb_opportunities, + get_output, +) +from pprint import pprint + + +def get_list_arb(): + """Run the arb finder + + Returns: + List: List sorted by % of all arb opportunities found. + """ + dict_price_raw = get_raw_price_async() + dict_clean_price = get_clean_price(dict_price_raw) + list_arb_price = compute_arb_opportunities(dict_clean_price) + res = get_output(list_arb_price) + sorted_list_arb = sorted(res.items(), key=lambda i: i[1]["%"], reverse=True) + pprint(sorted_list_arb) + return sorted_list_arb + + +if __name__ == "__main__": + get_list_arb() diff --git a/examples/python/oracle_arb_finder/core/__init__.py b/examples/python/oracle_arb_finder/core/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/examples/python/oracle_arb_finder/core/functions.py b/examples/python/oracle_arb_finder/core/functions.py new file mode 100644 index 0000000..9018fb9 --- /dev/null +++ b/examples/python/oracle_arb_finder/core/functions.py @@ -0,0 +1,83 @@ +import os +import time +import pprint +from core.token_infos import init_dict_token_dex +from core.orfeed import Orfeed +from dotenv import load_dotenv + +load_dotenv() +import web3 +from web3 import Web3 +from tqdm import tqdm + + +def getTokenToTokenPrice(orfeed_i, tokenSrc, tokenDst, dex, amount_src_token=1): + """Get the rate of swap tokenSrc to tokenDst in a given Dex + + Args: + orfeed_i (OrFeed): The instance of OrFeed class + tokenSrc (Symbol): Symbol of src token + tokenDst (Symbol): Symbol of dst token + dex (str): The Dex where the rate is going to be requested + amount_src_token (int, optional): Amount of src token. Defaults to 1 src token unit. + + Returns: + Dict: Return a dict containing all relevant infos about the request + """ + res = orfeed_i.getExchangeRate(tokenSrc, tokenDst, dex, amount_src_token) + + return { + "tokenSrc": tokenSrc, + "tokenDst": tokenDst, + "tokenPair": tokenSrc + "-" + tokenDst, + "provider": dex, + "price": res, + } + + +def simple_getTokenToTokenPrice( + orfeed_i, src_token, src_token_infos, dst_token, dst_token_infos +): + """For a pair of token, retrieve prices between Uniswap Dex and Kyber Dex. + + Args: + orfeed_i (OrFeed): Instance of OrFeed + src_token (Symbol): : Symbol of src token + src_token_infos (Dict): Dict containing src token infos (number of decimals and address) + dst_token (Symbol): Symbol of dst_token + dst_token_infos (Symbol): Dict containing dst token infos (number of decimals and address) + + Returns: + Dict: Dict containing all infos about buy/sell. + """ + result = {} + providers_list = ["UNISWAPBYSYMBOLV2", "KYBERBYSYMBOLV1"] + tmp_res = {} + for provider in providers_list: + buy = getTokenToTokenPrice( + orfeed_i, + src_token, + dst_token, + provider, + amount_src_token=10 ** src_token_infos["decimals"], + ) + sell = getTokenToTokenPrice( + orfeed_i, dst_token, src_token, provider, amount_src_token=buy["price"] + ) + tmp_res[provider] = { + "buy_price_wei": buy["price"] / (10 ** dst_token_infos["decimals"]), + "sell_price_wei": sell["price"] + * buy["price"] + / (10 ** (dst_token_infos["decimals"] + src_token_infos["decimals"])), + } + if buy["price"] > 0 and sell["price"] > 0: + tmp_res[provider] = { + "buy_price_wei": buy["price"] / (10 ** dst_token_infos["decimals"]), + "sell_price_wei": sell["price"] + * buy["price"] + / (10 ** (dst_token_infos["decimals"] + src_token_infos["decimals"])), + } + else: + return None + result[provider] = tmp_res[provider] + return result diff --git a/examples/python/oracle_arb_finder/core/orfeed.py b/examples/python/oracle_arb_finder/core/orfeed.py new file mode 100644 index 0000000..02821a9 --- /dev/null +++ b/examples/python/oracle_arb_finder/core/orfeed.py @@ -0,0 +1,69 @@ +from web3 import Web3 +from core.smartcontracts import my_smartcontracts + + +class Orfeed: + """OrFeed class + Initialise the blockchain provider + Implement some methods in order to call method in OrFeed smartcontract + https://etherscan.io/address/0x8316b082621cfedab95bf4a44a1d4b64a6ffc336 + """ + def __init__(self, web3): + """Initialize the blockchain provider + + Args: + web3 (string): url of blockchain provider, can be Infura or AlchemyAPI + """ + self.orfeed_address = Web3.toChecksumAddress( + my_smartcontracts["orfeed"]["address"] + ) + self.w3 = web3 + self.orfeed_contract = self.w3.eth.contract( + address=self.orfeed_address, abi=my_smartcontracts["orfeed"]["abi"] + ) + + def getExchangeRate(self, _from, _to, _provider, _amount): + """getExchange rate between two ERC20 tokens on a given DEX + + Args: + _from (str): symbol of ERC20 token. Source token. + _to (str): symbol of ERC20 token. Destination token. + _provider (str): the Dex where you want to swap your tokens. + _amount (str): amount of source token you want to exchange. + + Returns: + str: The amount of destination you get. + """ + if _from == _to or _amount <= 0: + return -1 + try: + return self.orfeed_contract.functions.getExchangeRate( + _from, _to, _provider, _amount + ).call() + except Exception: + return -1 + + def getTokenAddress(self, symbol): + """Return address of a ERC20 token. + + Args: + symbol (str): ERC20 token. + + Returns: + str: address of ERC20 token in the blockchain. + """ + try: + return self.orfeed_contract.functions.getTokenAddress(symbol).call() + except Exception: + return -1 + + def getTokenDecimalCount(self, address): + """Retrieve number of decimals of an ERC20 token. + + Args: + address (str): address of ERC20 token. + + Returns: + str: Number of decimals of the ERC20 token given in arg. + """ + return self.orfeed_contract.functions.getTokenDecimalCount(address).call() diff --git a/examples/python/oracle_arb_finder/core/registry.py b/examples/python/oracle_arb_finder/core/registry.py new file mode 100644 index 0000000..6f8efb0 --- /dev/null +++ b/examples/python/oracle_arb_finder/core/registry.py @@ -0,0 +1,22 @@ +import web3 +from web3 import Web3 +from smartcontracts import my_smartcontracts + + +class Registry: + def __init__(self, web3): + self.registry_address = Web3.toChecksumAddress( + my_smartcontracts["registry"]["address"] + ) + self.w3 = web3 + self.registry_contract = self.w3.eth.contract( + address=self.registry_address, abi=my_smartcontracts["registry"]["abi"] + ) + + def getAllOracles(self): + res = self.registry_contract.functions.getAllOracles().call() + return res + + def getOracleInfo(self, name_reference=None): + res = self.registry_contract.functions.getOracleInfo(name_reference).call() + return res diff --git a/examples/python/oracle_arb_finder/core/smartcontracts.py b/examples/python/oracle_arb_finder/core/smartcontracts.py new file mode 100644 index 0000000..d87e4a3 --- /dev/null +++ b/examples/python/oracle_arb_finder/core/smartcontracts.py @@ -0,0 +1,605 @@ +import os +from dotenv import load_dotenv + +load_dotenv() + +orfeed_contract_address_mainnet = "0x8316b082621cfedab95bf4a44a1d4b64a6ffc336" +registry_contract_address_mainnet = "0x74b5CE2330389391cC61bF2287BDC9Ac73757891" +aave_liquidity_provider = "0x3dfd23a6c5e8bbcfc9581d2e864a68feb6a076d3" + +registry_abi_mainnet = [ + { + "constant": False, + "inputs": [ + {"name": "name", "type": "string"}, + {"name": "newOrSameOracleAddress", "type": "address"}, + ], + "name": "editOracleAddress", + "outputs": [{"name": "", "type": "bool"}], + "payable": True, + "stateMutability": "payable", + "type": "function", + }, + { + "constant": True, + "inputs": [ + {"name": "selectedOracle", "type": "string"}, + {"name": "fromParam", "type": "string"}, + {"name": "toParam", "type": "string"}, + {"name": "side", "type": "string"}, + {"name": "amount", "type": "uint256"}, + ], + "name": "getPriceFromOracle", + "outputs": [{"name": "", "type": "uint256"}], + "payable": False, + "stateMutability": "view", + "type": "function", + }, + { + "constant": False, + "inputs": [], + "name": "withdrawBalance", + "outputs": [{"name": "", "type": "bool"}], + "payable": False, + "stateMutability": "nonpayable", + "type": "function", + }, + { + "constant": True, + "inputs": [], + "name": "getAllOracles", + "outputs": [{"name": "", "type": "string[]"}], + "payable": False, + "stateMutability": "view", + "type": "function", + }, + { + "constant": False, + "inputs": [{"name": "newFee", "type": "uint256"}], + "name": "changeFee", + "outputs": [{"name": "", "type": "bool"}], + "payable": False, + "stateMutability": "nonpayable", + "type": "function", + }, + { + "constant": False, + "inputs": [ + {"name": "name", "type": "string"}, + {"name": "requestedAddress", "type": "address"}, + {"name": "info", "type": "string"}, + ], + "name": "registerOracle", + "outputs": [{"name": "", "type": "bool"}], + "payable": True, + "stateMutability": "payable", + "type": "function", + }, + { + "constant": True, + "inputs": [{"name": "nameReference", "type": "string"}], + "name": "getOracleInfo", + "outputs": [{"name": "", "type": "string"}], + "payable": False, + "stateMutability": "view", + "type": "function", + }, + { + "constant": True, + "inputs": [{"name": "nameReference", "type": "string"}], + "name": "getOracleOwner", + "outputs": [{"name": "", "type": "address"}], + "payable": False, + "stateMutability": "view", + "type": "function", + }, + { + "constant": False, + "inputs": [ + {"name": "name", "type": "string"}, + {"name": "info", "type": "string"}, + ], + "name": "editOracleInfo", + "outputs": [{"name": "", "type": "bool"}], + "payable": True, + "stateMutability": "payable", + "type": "function", + }, + { + "constant": False, + "inputs": [{"name": "newOwner", "type": "address"}], + "name": "changeOwner", + "outputs": [{"name": "", "type": "bool"}], + "payable": False, + "stateMutability": "nonpayable", + "type": "function", + }, + { + "constant": True, + "inputs": [{"name": "nameReference", "type": "string"}], + "name": "getOracleAddress", + "outputs": [{"name": "", "type": "address"}], + "payable": False, + "stateMutability": "view", + "type": "function", + }, + { + "constant": False, + "inputs": [ + {"name": "name", "type": "string"}, + {"name": "toAddress", "type": "address"}, + ], + "name": "transferOracleName", + "outputs": [{"name": "", "type": "bool"}], + "payable": False, + "stateMutability": "nonpayable", + "type": "function", + }, + { + "inputs": [], + "payable": True, + "stateMutability": "payable", + "type": "constructor", + }, +] + +orfeed_abi_mainnet = [ + { + "constant": False, + "inputs": [ + {"name": "symb", "type": "string"}, + {"name": "tokenAddress", "type": "address"}, + {"name": "byteCode", "type": "bytes32"}, + ], + "name": "addFreeCurrency", + "outputs": [{"name": "", "type": "bool"}], + "payable": False, + "stateMutability": "nonpayable", + "type": "function", + }, + { + "constant": True, + "inputs": [ + {"name": "fromSymbol", "type": "string"}, + {"name": "toSymbol", "type": "string"}, + {"name": "venue", "type": "string"}, + {"name": "amount", "type": "uint256"}, + {"name": "referenceId", "type": "string"}, + ], + "name": "requestAsyncExchangeRateResult", + "outputs": [{"name": "", "type": "uint256"}], + "payable": False, + "stateMutability": "view", + "type": "function", + }, + { + "constant": True, + "inputs": [ + {"name": "eventName", "type": "string"}, + {"name": "source", "type": "string"}, + {"name": "referenceId", "type": "string"}, + ], + "name": "getAsyncEventResult", + "outputs": [{"name": "", "type": "string"}], + "payable": False, + "stateMutability": "view", + "type": "function", + }, + { + "constant": False, + "inputs": [ + {"name": "newDiv", "type": "uint256"}, + {"name": "newMul", "type": "uint256"}, + ], + "name": "updateMulDivConverter2", + "outputs": [{"name": "", "type": "bool"}], + "payable": False, + "stateMutability": "nonpayable", + "type": "function", + }, + { + "constant": False, + "inputs": [ + {"name": "synth", "type": "bytes32"}, + {"name": "token", "type": "address"}, + {"name": "inputAmount", "type": "uint256"}, + ], + "name": "getSynthToTokenOutputAmount", + "outputs": [{"name": "", "type": "uint256"}], + "payable": False, + "stateMutability": "nonpayable", + "type": "function", + }, + { + "constant": False, + "inputs": [ + {"name": "symb", "type": "string"}, + {"name": "tokenAddress", "type": "address"}, + ], + "name": "addFreeToken", + "outputs": [{"name": "", "type": "bool"}], + "payable": False, + "stateMutability": "nonpayable", + "type": "function", + }, + { + "constant": False, + "inputs": [{"name": "_a", "type": "string"}, {"name": "_b", "type": "string"}], + "name": "compare", + "outputs": [{"name": "", "type": "int256"}], + "payable": False, + "stateMutability": "nonpayable", + "type": "function", + }, + { + "constant": False, + "inputs": [{"name": "newOracle", "type": "address"}], + "name": "updateForexOracleAddress", + "outputs": [{"name": "", "type": "bool"}], + "payable": False, + "stateMutability": "nonpayable", + "type": "function", + }, + { + "constant": False, + "inputs": [{"name": "_a", "type": "string"}, {"name": "_b", "type": "string"}], + "name": "equal", + "outputs": [{"name": "", "type": "bool"}], + "payable": False, + "stateMutability": "nonpayable", + "type": "function", + }, + { + "constant": True, + "inputs": [ + {"name": "eventName", "type": "string"}, + {"name": "source", "type": "string"}, + ], + "name": "getEventResult", + "outputs": [{"name": "", "type": "string"}], + "payable": False, + "stateMutability": "view", + "type": "function", + }, + { + "constant": False, + "inputs": [{"name": "newOracle", "type": "address"}], + "name": "updateSynthAddress", + "outputs": [{"name": "", "type": "bool"}], + "payable": False, + "stateMutability": "nonpayable", + "type": "function", + }, + { + "constant": False, + "inputs": [ + {"name": "newDiv", "type": "uint256"}, + {"name": "newMul", "type": "uint256"}, + ], + "name": "updateMulDivConverter1", + "outputs": [{"name": "", "type": "bool"}], + "payable": False, + "stateMutability": "nonpayable", + "type": "function", + }, + { + "constant": False, + "inputs": [ + {"name": "newDiv", "type": "uint256"}, + {"name": "newMul", "type": "uint256"}, + ], + "name": "updateMulDivConverter3", + "outputs": [{"name": "", "type": "bool"}], + "payable": False, + "stateMutability": "nonpayable", + "type": "function", + }, + { + "constant": True, + "inputs": [ + {"name": "fromSymbol", "type": "string"}, + {"name": "toSymbol", "type": "string"}, + {"name": "venue", "type": "string"}, + {"name": "amount", "type": "uint256"}, + ], + "name": "getExchangeRate", + "outputs": [{"name": "", "type": "uint256"}], + "payable": False, + "stateMutability": "view", + "type": "function", + }, + { + "constant": False, + "inputs": [{"name": "symb", "type": "string"}], + "name": "removeFreeToken", + "outputs": [{"name": "", "type": "bool"}], + "payable": False, + "stateMutability": "nonpayable", + "type": "function", + }, + { + "constant": False, + "inputs": [{"name": "newOracle", "type": "address"}], + "name": "updateEthTokenAddress", + "outputs": [{"name": "", "type": "bool"}], + "payable": False, + "stateMutability": "nonpayable", + "type": "function", + }, + { + "constant": False, + "inputs": [ + {"name": "fundsReturnToAddress", "type": "address"}, + {"name": "liquidityProviderContractAddress", "type": "address"}, + {"name": "tokens", "type": "string[]"}, + {"name": "amount", "type": "uint256"}, + {"name": "exchanges", "type": "string[]"}, + ], + "name": "arb", + "outputs": [{"name": "", "type": "bool"}], + "payable": True, + "stateMutability": "payable", + "type": "function", + }, + { + "constant": False, + "inputs": [{"name": "newOracle", "type": "address"}], + "name": "updatePremiumSubOracleAddress", + "outputs": [{"name": "", "type": "bool"}], + "payable": False, + "stateMutability": "nonpayable", + "type": "function", + }, + { + "constant": False, + "inputs": [ + {"name": "_haystack", "type": "string"}, + {"name": "_needle", "type": "string"}, + ], + "name": "indexOf", + "outputs": [{"name": "", "type": "int256"}], + "payable": False, + "stateMutability": "nonpayable", + "type": "function", + }, + { + "constant": False, + "inputs": [{"name": "symb", "type": "string"}], + "name": "removeFreeCurrency", + "outputs": [{"name": "", "type": "bool"}], + "payable": False, + "stateMutability": "nonpayable", + "type": "function", + }, + { + "constant": False, + "inputs": [{"name": "newOracle", "type": "address"}], + "name": "updateAsyncOracleAddress", + "outputs": [{"name": "", "type": "bool"}], + "payable": False, + "stateMutability": "nonpayable", + "type": "function", + }, + { + "constant": False, + "inputs": [{"name": "venueToCheck", "type": "string"}], + "name": "isFreeVenueCheck", + "outputs": [{"name": "", "type": "bool"}], + "payable": False, + "stateMutability": "nonpayable", + "type": "function", + }, + { + "constant": False, + "inputs": [{"name": "symToCheck", "type": "string"}], + "name": "isFree", + "outputs": [{"name": "", "type": "bool"}], + "payable": False, + "stateMutability": "nonpayable", + "type": "function", + }, + { + "constant": False, + "inputs": [{"name": "newAddress", "type": "address"}], + "name": "updateArbContractAddress", + "outputs": [{"name": "", "type": "bool"}], + "payable": False, + "stateMutability": "nonpayable", + "type": "function", + }, + { + "constant": False, + "inputs": [{"name": "newOwner", "type": "address"}], + "name": "changeOwner", + "outputs": [{"name": "", "type": "bool"}], + "payable": False, + "stateMutability": "nonpayable", + "type": "function", + }, + { + "constant": False, + "inputs": [{"name": "newOracle", "type": "address"}], + "name": "updateAsyncEventsAddress", + "outputs": [{"name": "", "type": "bool"}], + "payable": False, + "stateMutability": "nonpayable", + "type": "function", + }, + { + "constant": True, + "inputs": [{"name": "tokenAddress", "type": "address"}], + "name": "getTokenDecimalCount", + "outputs": [{"name": "", "type": "uint256"}], + "payable": False, + "stateMutability": "view", + "type": "function", + }, + { + "constant": True, + "inputs": [{"name": "a", "type": "string"}, {"name": "b", "type": "string"}], + "name": "compareStrings", + "outputs": [{"name": "", "type": "bool"}], + "payable": False, + "stateMutability": "view", + "type": "function", + }, + { + "constant": False, + "inputs": [ + {"name": "eventName", "type": "string"}, + {"name": "source", "type": "string"}, + ], + "name": "requestAsyncEvent", + "outputs": [{"name": "", "type": "string"}], + "payable": False, + "stateMutability": "nonpayable", + "type": "function", + }, + { + "constant": True, + "inputs": [{"name": "symbol", "type": "string"}], + "name": "getTokenAddress", + "outputs": [{"name": "", "type": "address"}], + "payable": False, + "stateMutability": "view", + "type": "function", + }, + { + "constant": False, + "inputs": [ + {"name": "token", "type": "address"}, + {"name": "synth", "type": "bytes32"}, + {"name": "inputAmount", "type": "uint256"}, + ], + "name": "getTokenToSynthOutputAmount", + "outputs": [{"name": "", "type": "uint256"}], + "payable": False, + "stateMutability": "nonpayable", + "type": "function", + }, + { + "constant": False, + "inputs": [{"name": "source", "type": "string"}], + "name": "stringToBytes32", + "outputs": [{"name": "result", "type": "bytes32"}], + "payable": False, + "stateMutability": "nonpayable", + "type": "function", + }, + { + "constant": False, + "inputs": [ + {"name": "fromSymbol", "type": "string"}, + {"name": "toSymbol", "type": "string"}, + {"name": "venue", "type": "string"}, + {"name": "amount", "type": "uint256"}, + ], + "name": "requestAsyncExchangeRate", + "outputs": [{"name": "", "type": "string"}], + "payable": False, + "stateMutability": "nonpayable", + "type": "function", + }, + { + "constant": False, + "inputs": [{"name": "newOracle", "type": "address"}], + "name": "updateTokenOracleAddress2", + "outputs": [{"name": "", "type": "bool"}], + "payable": False, + "stateMutability": "nonpayable", + "type": "function", + }, + { + "constant": False, + "inputs": [{"name": "newOracle", "type": "address"}], + "name": "updateSyncEventsAddress", + "outputs": [{"name": "", "type": "bool"}], + "payable": False, + "stateMutability": "nonpayable", + "type": "function", + }, + { + "constant": True, + "inputs": [{"name": "symbol", "type": "string"}], + "name": "getSynthBytes32", + "outputs": [{"name": "", "type": "bytes32"}], + "payable": False, + "stateMutability": "view", + "type": "function", + }, + { + "constant": False, + "inputs": [ + {"name": "fromSymb", "type": "string"}, + {"name": "toSymb", "type": "string"}, + {"name": "amount", "type": "uint256"}, + ], + "name": "getFreeExchangeRate", + "outputs": [{"name": "", "type": "uint256"}], + "payable": False, + "stateMutability": "nonpayable", + "type": "function", + }, + { + "constant": False, + "inputs": [{"name": "newOracle", "type": "address"}], + "name": "updateTokenOracleAddress", + "outputs": [{"name": "", "type": "bool"}], + "payable": False, + "stateMutability": "nonpayable", + "type": "function", + }, + { + "constant": False, + "inputs": [ + {"name": "newDiv", "type": "uint256"}, + {"name": "newMul", "type": "uint256"}, + ], + "name": "updateMulDivConverter4", + "outputs": [{"name": "", "type": "bool"}], + "payable": False, + "stateMutability": "nonpayable", + "type": "function", + }, + { + "constant": True, + "inputs": [{"name": "symbol", "type": "string"}], + "name": "getForexAddress", + "outputs": [{"name": "", "type": "address"}], + "payable": False, + "stateMutability": "view", + "type": "function", + }, + { + "constant": False, + "inputs": [ + {"name": "param1", "type": "string"}, + {"name": "param2", "type": "string"}, + {"name": "param3", "type": "string"}, + {"name": "param4", "type": "string"}, + ], + "name": "callExtraFunction", + "outputs": [{"name": "", "type": "string"}], + "payable": False, + "stateMutability": "nonpayable", + "type": "function", + }, + { + "inputs": [], + "payable": True, + "stateMutability": "payable", + "type": "constructor", + }, + {"payable": True, "stateMutability": "payable", "type": "fallback"}, +] + +my_smartcontracts = {} +if os.getenv("NETWORK") == "mainnet": + my_smartcontracts["orfeed"] = { + "address": orfeed_contract_address_mainnet, + "abi": orfeed_abi_mainnet, + } + my_smartcontracts["registry"] = { + "address": registry_contract_address_mainnet, + "abi": registry_abi_mainnet, + } diff --git a/examples/python/oracle_arb_finder/core/token_infos.py b/examples/python/oracle_arb_finder/core/token_infos.py new file mode 100644 index 0000000..bea8d11 --- /dev/null +++ b/examples/python/oracle_arb_finder/core/token_infos.py @@ -0,0 +1,33 @@ +token_symbols = { + "DAI": {"address": 0x6B175474E89094C44DA98B954EEDEAC495271D0F, "decimals": 18}, + "USDC": {"address": 0xA0B86991C6218B36C1D19D4A2E9EB0CE3606EB48, "decimals": 6}, + "MKR": {"address": 0x9F8F72AA9304C8B593D555F12EF6589CC3A579A2, "decimals": 18}, + "LINK": {"address": 0x514910771AF9CA656AF840DFF83E8264ECF986CA, "decimals": 18}, + "BAT": {"address": 0x0D8775F648430679A709E98D2B0CB6250D2887EF, "decimals": 18}, + "WBTC": {"address": 0x2260FAC5E5542A773AA44FBCFEDF7C193BC2C599, "decimals": 8}, + "USDT": {"address": 0xDAC17F958D2EE523A2206206994597C13D831EC7, "decimals": 6}, + "OMG": {"address": 0xD26114CD6EE289ACCF82350C8D8487FEDB8A0C07, "decimals": 18}, + "ZRX": {"address": 0xE41D2489571D322189246DAFA5EBDE1F4699F498, "decimals": 18}, + "TUSD": {"decimals": 18}, + "LEND": {"address": 0x80FB784B7ED66730E8B1DBD9820AFD29931AAB03, "decimals": 18}, + "REP": {"address": 0x221657776846890989A759BA2973E427DFF5C9BB, "decimals": 18}, + "BNT": {"address": 0x1F573D6FB3F13D689FF844B4CE37794D79A7FF1C, "decimals": 18}, + "PAX": {"address": 0x8E870D67F660D95D5BE530380D0EC0BD388289E1, "decimals": 18}, + "SUSD": {"address": 0x57AB1EC28D129707052DF4DF418D58A2D46D5F51, "decimals": 18}, + "KNC": {"address": 0xDD974D5C2E2928DEA5F71B9825B8B646686BD200, "decimals": 18}, + "ETH": { + # 'address' : 0xdd974d5c2e2928dea5f71b9825b8b646686bd200, + "decimals": 18 + }, +} + +orfeed_list_providers = ["UNISWAPBYSYMBOLV1", "UNISWAPBYSYMBOLV2", "KYBERBYSYMBOLV1"] + + +def init_dict_token_dex(): + token_dex = {} + for token in token_symbols: + token_dex[token] = {} + for provider in orfeed_list_providers: + token_dex[token][provider] = 0 + return token_dex diff --git a/examples/python/oracle_arb_finder/helper/get_price.py b/examples/python/oracle_arb_finder/helper/get_price.py new file mode 100644 index 0000000..4537529 --- /dev/null +++ b/examples/python/oracle_arb_finder/helper/get_price.py @@ -0,0 +1,157 @@ +from threading import Thread +import os, time, pprint +from web3 import Web3, eth +from tqdm import tqdm +from core.functions import simple_getTokenToTokenPrice +from core.orfeed import Orfeed +from core.token_infos import token_symbols + +w3 = Web3(Web3.HTTPProvider(os.getenv("BLOCKCHAIN_PROVIDER"))) +# w3 = Web3(Web3.HTTPProvider('http://127.0.0.1:8545')) # uncomment if you use ganache-cli +orfeed_i = Orfeed(w3) + + +class ThreadWithReturnValue(Thread): + def __init__( + self, group=None, target=None, name=None, args=(), kwargs={}, Verbose=None + ): + Thread.__init__(self, group, target, name, args, kwargs) + self._return = None + + def run(self): + # print(type(self._target)) + if self._target is not None: + self._return = self._target(*self._args, **self._kwargs) + + def join(self, *args): + Thread.join(self, *args) + return self._return + + +def get_raw_price_async(): + list_thread = {} + for src_token in token_symbols: + for dst_token in token_symbols: + if src_token != dst_token: + list_thread[src_token + "/" + dst_token] = ThreadWithReturnValue( + target=simple_getTokenToTokenPrice, + args=( + orfeed_i, + src_token, + token_symbols[src_token], + dst_token, + token_symbols[dst_token], + ), + ) + list_thread[src_token + "/" + dst_token].start() + + # res = [{i: list_thread[i].join()} for i in list_thread if list_thread[i].join() != -1] + res = {} + for i in list_thread: + res[i] = list_thread[i].join() + return res + + +def get_clean_price(list_raw_price, coeff=2000): + result = {} + dex_list = ["UNISWAPBYSYMBOLV2", "KYBERBYSYMBOLV1"] + for pair in list_raw_price: + if list_raw_price[pair] is None: + continue + + buy_price_ratio = ( + list_raw_price[pair][dex_list[0]]["buy_price_wei"] + / list_raw_price[pair][dex_list[1]]["buy_price_wei"] + ) + sell_price_ratio = ( + list_raw_price[pair][dex_list[0]]["sell_price_wei"] + / list_raw_price[pair][dex_list[1]]["sell_price_wei"] + ) + if (buy_price_ratio > coeff or 1 / buy_price_ratio > coeff) or ( + sell_price_ratio > coeff or 1 / sell_price_ratio > coeff + ): + continue + + for dex in dex_list: + if ( + list_raw_price[pair][dex]["buy_price_wei"] > 0 + and list_raw_price[pair][dex]["sell_price_wei"] > 0 + ): + result[pair] = list_raw_price[pair] + return result + + +def compute_arb_opportunities(list_clean_price): + dex_list = ["UNISWAPBYSYMBOLV2", "KYBERBYSYMBOLV1"] + for pair in list_clean_price: + path = { + "one": ( + list_clean_price[pair][dex_list[1]]["sell_price_wei"] + - list_clean_price[pair][dex_list[0]]["buy_price_wei"] + ) + / list_clean_price[pair][dex_list[0]]["buy_price_wei"], + "two": ( + list_clean_price[pair][dex_list[0]]["sell_price_wei"] + - list_clean_price[pair][dex_list[1]]["buy_price_wei"] + ) + / list_clean_price[pair][dex_list[1]]["buy_price_wei"], + } + if path["one"] > path["two"] and path["one"] > 0: + list_clean_price[pair]["%"] = path["one"] + list_clean_price[pair]["code"] = 1 + elif path["two"] > path["one"] and path["two"] > 0: + list_clean_price[pair]["%"] = path["two"] + list_clean_price[pair]["code"] = 2 + + return { + k: v + for k, v in list_clean_price.items() + if list_clean_price[k].get("%") is not None + } + + +def get_output(list_arb_price): + hint_msg = "swap {amount_src_token} {src_token} for {amount_dst_token} {dst_token} in {dex}" + [dex_1, dex_2] = ["UNISWAPBYSYMBOLV2", "KYBERBYSYMBOLV1"] + for pair in list_arb_price: + [src_token, dst_token] = pair.split("/") + if list_arb_price[pair]["code"] == 1: + amount_src_token = list_arb_price[pair][dex_1]["buy_price_wei"] + amount_dst_token = list_arb_price[pair][dex_2]["sell_price_wei"] + dict_var_msg_1 = { + "src_token": src_token, + "dst_token": dst_token, + "amount_src_token": 1, + "amount_dst_token": amount_src_token, + "dex": dex_1, + } + dict_var_msg_2 = { + "src_token": dst_token, + "dst_token": src_token, + "amount_src_token": amount_dst_token, + "amount_dst_token": 1, + "dex": dex_2, + } + list_arb_price[pair]["swap_1"] = hint_msg.format(**dict_var_msg_1) + list_arb_price[pair]["swap_2"] = hint_msg.format(**dict_var_msg_2) + else: + amount_src_token = list_arb_price[pair][dex_2]["buy_price_wei"] + amount_dst_token = list_arb_price[pair][dex_1]["sell_price_wei"] + dict_var_msg_1 = { + "src_token": dst_token, + "dst_token": src_token, + "amount_src_token": amount_src_token, + "amount_dst_token": 1, + "dex": dex_1, + } + dict_var_msg_2 = { + "src_token": src_token, + "dst_token": dst_token, + "amount_src_token": 1, + "amount_dst_token": amount_dst_token, + "dex": dex_2, + } + list_arb_price[pair]["swap_1"] = hint_msg.format(**dict_var_msg_2) + list_arb_price[pair]["swap_2"] = hint_msg.format(**dict_var_msg_1) + + return list_arb_price diff --git a/examples/python/oracle_arb_finder/requirements.txt b/examples/python/oracle_arb_finder/requirements.txt new file mode 100644 index 0000000..534c302 --- /dev/null +++ b/examples/python/oracle_arb_finder/requirements.txt @@ -0,0 +1,2 @@ +web3==5.13.1 +python-dotenv==0.15.0 diff --git a/examples/python/oracle_arb_finder/server.py b/examples/python/oracle_arb_finder/server.py new file mode 100644 index 0000000..8f20e2b --- /dev/null +++ b/examples/python/oracle_arb_finder/server.py @@ -0,0 +1,13 @@ +from flask import Flask +from app import get_list_arb + +app = Flask(__name__) + + +@app.route("/arb") +def arb(): + return {"result": get_list_arb()}, 200 + + +if __name__ == "__main__": + app.run(debug=True)