├── .gitignore ├── .travis.yml ├── CHANGELOG.rst ├── LICENSE ├── README.md ├── appveyor.yml ├── docs ├── Makefile ├── cli.rst ├── conf.py ├── contribute.rst ├── hiveengine.api.rst ├── hiveengine.collection.rst ├── hiveengine.exceptions.rst ├── hiveengine.market.rst ├── hiveengine.nft.rst ├── hiveengine.nftmarket.rst ├── hiveengine.nfts.rst ├── hiveengine.rpc.rst ├── hiveengine.tokenobject.rst ├── hiveengine.tokens.rst ├── hiveengine.wallet.rst ├── index.rst ├── indices.rst ├── installation.rst ├── make.bat ├── modules.rst ├── quickstart.rst ├── requirements.txt ├── support.rst └── tutorials.rst ├── example ├── send_token.py ├── stream_all_blocks.py └── token_upvote_bot.py ├── hiveengine-onedir.spec ├── hiveengine-onefile.spec ├── hiveengine.ico ├── hiveengine ├── __init__.py ├── api.py ├── cli.py ├── collection.py ├── exceptions.py ├── market.py ├── nft.py ├── nftmarket.py ├── nfts.py ├── rpc.py ├── tokenobject.py ├── tokens.py ├── version.py └── wallet.py ├── requirements-test.txt ├── setup.py ├── tests ├── __init__.py ├── test_api.py ├── test_rpc.py ├── test_tokenobject.py └── test_tokens.py ├── tox.ini └── util ├── appveyor └── build.cmd ├── package-linux.sh ├── package-osx.sh └── travis_osx_install.sh /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | *.wpr 10 | *.wpu 11 | 12 | # Distribution / packaging 13 | .Python 14 | build/ 15 | develop-eggs/ 16 | dist/ 17 | downloads/ 18 | eggs/ 19 | .eggs/ 20 | lib/ 21 | lib64/ 22 | parts/ 23 | sdist/ 24 | var/ 25 | wheels/ 26 | *.egg-info/ 27 | .installed.cfg 28 | *.egg 29 | MANIFEST 30 | 31 | # PyInstaller 32 | # Usually these files are written by a python script from a template 33 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 34 | *.manifest 35 | # *.spec 36 | 37 | # Installer logs 38 | pip-log.txt 39 | pip-delete-this-directory.txt 40 | 41 | # Unit test / coverage reports 42 | htmlcov/ 43 | .tox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | 53 | # Translations 54 | *.mo 55 | *.pot 56 | 57 | *.wpr 58 | *.wpu 59 | 60 | # Django stuff: 61 | *.log 62 | local_settings.py 63 | db.sqlite3 64 | 65 | # Flask stuff: 66 | instance/ 67 | .webassets-cache 68 | 69 | # Scrapy stuff: 70 | .scrapy 71 | 72 | # Sphinx documentation 73 | docs/_build/ 74 | 75 | # PyBuilder 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # pyenv 82 | .python-version 83 | 84 | # celery beat schedule file 85 | celerybeat-schedule 86 | 87 | # SageMath parsed files 88 | *.sage.py 89 | 90 | # Environments 91 | .env 92 | .venv 93 | env/ 94 | venv/ 95 | ENV/ 96 | env.bak/ 97 | venv.bak/ 98 | 99 | # Spyder project settings 100 | .spyderproject 101 | .spyproject 102 | 103 | # Rope project settings 104 | .ropeproject 105 | 106 | # mkdocs documentation 107 | /site 108 | 109 | # mypy 110 | .mypy_cache/ 111 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # After changing this file, check it on: 2 | # http://lint.travis-ci.org/ 3 | language: python 4 | sudo: false 5 | 6 | matrix: 7 | include: 8 | #- os: linux 9 | # python: 3.6 10 | # env: 11 | # - TOXENV=pylint 12 | #- os: linux 13 | # python: 3.6 14 | # env: 15 | # - TOXENV=flake8 16 | #- os: linux 17 | # python: 3.6 18 | # env: 19 | # - TOXENV=bandit 20 | #- os: linux 21 | # python: 3.6 22 | # env: 23 | # - TOXENV=readme 24 | - os: linux 25 | python: 2.7 26 | # env: 27 | # - TOXENV=short 28 | - os: linux 29 | python: 3.5 30 | # env: 31 | # - TOXENV=short 32 | - os: linux 33 | python: 3.6 34 | env: 35 | # - TOXENV=py36short 36 | - BUILD_LINUX=yes 37 | 38 | cache: pip 39 | 40 | before_install: 41 | - uname -a 42 | - df -h 43 | - ulimit -a 44 | - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then source util/travis_osx_install.sh; fi 45 | - ccache -s 46 | - which python; python --version 47 | - pip install --upgrade pip 48 | - pip install --upgrade wheel 49 | # Set numpy version first, other packages link against it 50 | - pip install six nose coverage codecov tox-travis pytest pytest-cov coveralls codacy-coverage parameterized secp256k1 cryptography scrypt 51 | - pip install pycryptodomex pyyaml appdirs pylibscrypt 52 | - pip install ecdsa requests future websocket-client pytz six Click events prettytable 53 | - pip install beem 54 | 55 | script: 56 | - tox 57 | 58 | after_success: 59 | - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then bash util/package-osx.sh; fi 60 | - if [[ "$BUILD_LINUX" == "yes" ]]; then bash util/package-linux.sh; fi 61 | - coveralls 62 | # - codecov 63 | # - python-codacy-coverage -r coverage.xml 64 | -------------------------------------------------------------------------------- /CHANGELOG.rst: -------------------------------------------------------------------------------- 1 | 0.2.2 2 | ----- 3 | * fix hiveengine info for NFT symbols 4 | * add min-hive parameter to nftsellbook 5 | * add cheapest-only parameter to nftsellbook 6 | * Read groupBy when grouping is empty in nftsellbook 7 | * update_url, update_metadata, update_name, update_org_name, update_product_name, add_authorized_issuing_accounts, 8 | add_authorized_issuing_contracts, remove_authorized_issuing_accounts, remove_authorized_issuing_contracts, 9 | transfer_ownership, add_property, set_property_permissions, set_properties, set_group_by, 10 | update_property_definition, issue, issue_multiple, enable_delegation added to Nft 11 | * New nft command to view properties of an NFT object 12 | * Fix nfttrades command and lists a trades summary when now symbol was given 13 | * Add interactive mode to nftsellbook for buy, cancel or change price 14 | 15 | 0.2.1 16 | ----- 17 | * Fix nft_id list in nftbuy, nftcancel, nftchangeprice and nftsell 18 | 19 | 0.2.0 20 | ----- 21 | * add NFT support (collection, nft, nftmarket, nfts) 22 | * add NFT related commands to cli (collection, nftbuy, nftcancel, nftchangeprice, nftlist, nftopen, nftparams, nftsell, nftsellbook, nfttrades) 23 | 24 | 0.1.5 25 | ----- 26 | * allow to cancel all buy / sell orders at once 27 | * show all buy/sell order for an account 28 | 29 | 0.1.4 30 | ----- 31 | * allow so transfer / stake / unstake / sell all tokens at once 32 | * When no amount is given by transfer / stake / unstake the token balance is used 33 | * new balance command 34 | 35 | 0.1.3 36 | ----- 37 | * some bug-fixes 38 | 39 | 0.1.2 40 | ----- 41 | * Fix stake and cancel unstake operation 42 | 43 | 0.1.1 44 | ----- 45 | * Allow to add key directly without wallet in cli 46 | 47 | 0.1.0 48 | ----- 49 | * Inital version 50 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Holger Nahrstaedt 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # hiveengine 2 | Python tools for obtaining and processing hive engine tokens 3 | 4 | [![Build Status](https://travis-ci.org/holgern/hiveengine.svg?branch=master)](https://travis-ci.org/holgern/hiveengine) 5 | 6 | ## Installation 7 | ``` 8 | pip install hiveengine 9 | ``` 10 | 11 | 12 | ## Commands 13 | Get the latest block of the sidechain 14 | ``` 15 | from hiveengine.api import Api 16 | api = Api() 17 | print(api.get_latest_block_info()) 18 | ``` 19 | 20 | Get the block with the specified block number of the sidechain 21 | ``` 22 | from hiveengine.api import Api 23 | api = Api() 24 | print(api.get_block_info(1910)) 25 | ``` 26 | 27 | Retrieve the specified transaction info of the sidechain 28 | ``` 29 | from hiveengine.api import Api 30 | api = Api() 31 | print(api.get_transaction_info("e6c7f351b3743d1ed3d66eb9c6f2c102020aaa5d")) 32 | ``` 33 | 34 | Get the contract specified from the database 35 | ``` 36 | from hiveengine.api import Api 37 | api = Api() 38 | print(api.get_contract("tokens")) 39 | ``` 40 | 41 | Get an array of objects that match the query from the table of the specified contract 42 | ``` 43 | from hiveengine.api import Api 44 | api = Api() 45 | print(api.find("tokens", "tokens")) 46 | ``` 47 | 48 | Get the object that matches the query from the table of the specified contract 49 | ``` 50 | from hiveengine.api import Api 51 | api = Api() 52 | print(api.find_one("tokens", "tokens")) 53 | ``` 54 | 55 | Get the transaction history for an account and a token 56 | ``` 57 | from hiveengine.api import Api 58 | api = Api() 59 | print(api.get_history("holger80", "FOODIE")) 60 | ``` 61 | ## Token transfer 62 | ``` 63 | from beem import Steem 64 | from hiveengine.wallet import Wallet 65 | stm = Steem(keys=["5xx"]) 66 | wallet = Wallet("test_user", steem_instance=stm) 67 | wallet.transfer("test1",1,"TST", memo="This is a test") 68 | ``` 69 | ## Buy/Sell 70 | ### Create a buy order 71 | ``` 72 | from beem import Steem 73 | from hiveengine.market import Market 74 | stm = Steem(keys=["5xx"]) 75 | m=Market(steem_instance=stm) 76 | m.buy("test_user", 1, "TST", 9.99) 77 | ``` 78 | ### Create a sell order 79 | 80 | ``` 81 | from beem import Steem 82 | from hiveengine.market import Market 83 | stm = Steem(keys=["5xx"]) 84 | m=Market(steem_instance=stm) 85 | m.sell("test_user", 1, "TST", 9.99) 86 | ``` 87 | ### Cancel a buy order 88 | ``` 89 | from beem import Steem 90 | from hiveengine.market import Market 91 | stm = Steem(keys=["5xx"]) 92 | m=Market(steem_instance=stm) 93 | open_buy_orders = m.get_buy_book("TST", "test_user") 94 | m.cancel("test_user", "buy", open_buy_orders[0]["_id"]) 95 | ``` 96 | ### Cancel a sell order 97 | ``` 98 | from beem import Steem 99 | from hiveengine.market import Market 100 | stm = Steem(keys=["5xx"]) 101 | m=Market(steem_instance=stm) 102 | open_sell_orders = m.get_sell_book("TST", "test_user") 103 | m.cancel("test_user", "sell", open_sell_orders[0]["_id"]) 104 | ``` 105 | ### Deposit Steem 106 | ``` 107 | from beem import Steem 108 | from hiveengine.market import Market 109 | stm = Steem(keys=["5xx"]) 110 | m=Market(steem_instance=stm) 111 | m.deposit("test_user", 10) 112 | ``` 113 | ### Withdrawal 114 | ``` 115 | from beem import Steem 116 | from hiveengine.market import Market 117 | stm = Steem(keys=["5xx"]) 118 | m=Market(steem_instance=stm) 119 | m.withdraw("test_user", 10) 120 | ``` 121 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/holgern/hiveengine/d04f3fe7d94c528183026a48f52344c968072c83/appveyor.yml -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # User-friendly check for sphinx-build 11 | ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) 12 | $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) 13 | endif 14 | 15 | # Internal variables. 16 | PAPEROPT_a4 = -D latex_paper_size=a4 17 | PAPEROPT_letter = -D latex_paper_size=letter 18 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 19 | # the i18n builder cannot share the environment and doctrees with the others 20 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 21 | 22 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest coverage gettext 23 | 24 | help: 25 | @echo "Please use \`make ' where is one of" 26 | @echo " html to make standalone HTML files" 27 | @echo " dirhtml to make HTML files named index.html in directories" 28 | @echo " singlehtml to make a single large HTML file" 29 | @echo " pickle to make pickle files" 30 | @echo " json to make JSON files" 31 | @echo " htmlhelp to make HTML files and a HTML help project" 32 | @echo " qthelp to make HTML files and a qthelp project" 33 | @echo " applehelp to make an Apple Help Book" 34 | @echo " devhelp to make HTML files and a Devhelp project" 35 | @echo " epub to make an epub" 36 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 37 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 38 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" 39 | @echo " text to make text files" 40 | @echo " man to make manual pages" 41 | @echo " texinfo to make Texinfo files" 42 | @echo " info to make Texinfo files and run them through makeinfo" 43 | @echo " gettext to make PO message catalogs" 44 | @echo " changes to make an overview of all changed/added/deprecated items" 45 | @echo " xml to make Docutils-native XML files" 46 | @echo " pseudoxml to make pseudoxml-XML files for display purposes" 47 | @echo " linkcheck to check all external links for integrity" 48 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 49 | @echo " coverage to run coverage check of the documentation (if enabled)" 50 | 51 | clean: 52 | rm -rf $(BUILDDIR)/* 53 | 54 | html: 55 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 56 | @echo 57 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 58 | 59 | dirhtml: 60 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 61 | @echo 62 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 63 | 64 | singlehtml: 65 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 66 | @echo 67 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 68 | 69 | pickle: 70 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 71 | @echo 72 | @echo "Build finished; now you can process the pickle files." 73 | 74 | json: 75 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 76 | @echo 77 | @echo "Build finished; now you can process the JSON files." 78 | 79 | htmlhelp: 80 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 81 | @echo 82 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 83 | ".hhp project file in $(BUILDDIR)/htmlhelp." 84 | 85 | qthelp: 86 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 87 | @echo 88 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 89 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 90 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/python-bitshares.qhcp" 91 | @echo "To view the help file:" 92 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/python-bitshares.qhc" 93 | 94 | applehelp: 95 | $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp 96 | @echo 97 | @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." 98 | @echo "N.B. You won't be able to view it unless you put it in" \ 99 | "~/Library/Documentation/Help or install it in your application" \ 100 | "bundle." 101 | 102 | devhelp: 103 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 104 | @echo 105 | @echo "Build finished." 106 | @echo "To view the help file:" 107 | @echo "# mkdir -p $$HOME/.local/share/devhelp/python-bitshares" 108 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/python-bitshares" 109 | @echo "# devhelp" 110 | 111 | epub: 112 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 113 | @echo 114 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 115 | 116 | latex: 117 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 118 | @echo 119 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 120 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 121 | "(use \`make latexpdf' here to do that automatically)." 122 | 123 | latexpdf: 124 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 125 | @echo "Running LaTeX files through pdflatex..." 126 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 127 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 128 | 129 | latexpdfja: 130 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 131 | @echo "Running LaTeX files through platex and dvipdfmx..." 132 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja 133 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 134 | 135 | text: 136 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 137 | @echo 138 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 139 | 140 | man: 141 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 142 | @echo 143 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 144 | 145 | texinfo: 146 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 147 | @echo 148 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 149 | @echo "Run \`make' in that directory to run these through makeinfo" \ 150 | "(use \`make info' here to do that automatically)." 151 | 152 | info: 153 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 154 | @echo "Running Texinfo files through makeinfo..." 155 | make -C $(BUILDDIR)/texinfo info 156 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 157 | 158 | gettext: 159 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 160 | @echo 161 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 162 | 163 | changes: 164 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 165 | @echo 166 | @echo "The overview file is in $(BUILDDIR)/changes." 167 | 168 | linkcheck: 169 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 170 | @echo 171 | @echo "Link check complete; look for any errors in the above output " \ 172 | "or in $(BUILDDIR)/linkcheck/output.txt." 173 | 174 | doctest: 175 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 176 | @echo "Testing of doctests in the sources finished, look at the " \ 177 | "results in $(BUILDDIR)/doctest/output.txt." 178 | 179 | coverage: 180 | $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage 181 | @echo "Testing of coverage in the sources finished, look at the " \ 182 | "results in $(BUILDDIR)/coverage/python.txt." 183 | 184 | xml: 185 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml 186 | @echo 187 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml." 188 | 189 | pseudoxml: 190 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml 191 | @echo 192 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." 193 | -------------------------------------------------------------------------------- /docs/cli.rst: -------------------------------------------------------------------------------- 1 | hiveengine CLI 2 | ============== 3 | 4 | Commands 5 | -------- 6 | 7 | .. click:: hiveengine.cli:cli 8 | :prog: hiveengine 9 | :show-nested: 10 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # hiveengine documentation build configuration file, created by 5 | # sphinx-quickstart on Fri Jun 5 14:06:38 2015. 6 | # 7 | # This file is execfile()d with the current directory set to its 8 | # containing dir. 9 | # 10 | # Note that not all possible configuration values are present in this 11 | # autogenerated file. 12 | # 13 | # All configuration values have a default; values that are commented out 14 | # serve to show the default. 15 | 16 | import sys 17 | import os 18 | import shlex 19 | import hiveengine 20 | from packaging.version import parse 21 | 22 | # If extensions (or modules to document with autodoc) are in another directory, 23 | # add these directories to sys.path here. If the directory is relative to the 24 | # documentation root, use os.path.abspath to make it absolute, like shown here. 25 | sys.path.insert(0, os.path.abspath('..')) 26 | sys.path.insert(0, os.path.abspath('../scripts/')) 27 | 28 | # -- General configuration ------------------------------------------------ 29 | 30 | # If your documentation needs a minimal Sphinx version, state it here. 31 | #needs_sphinx = '1.0' 32 | 33 | # Add any Sphinx extension module names here, as strings. They can be 34 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 35 | # ones. 36 | extensions = ["sphinx.ext.autodoc", "sphinx.ext.doctest", "sphinx_click.ext"] 37 | 38 | # Add any paths that contain templates here, relative to this directory. 39 | templates_path = ['_templates'] 40 | 41 | # The suffix(es) of source filenames. 42 | # You can specify multiple suffix as a list of string: 43 | # source_suffix = ['.rst', '.md'] 44 | source_suffix = '.rst' 45 | 46 | # The encoding of source files. 47 | #source_encoding = 'utf-8-sig' 48 | 49 | # The master toctree document. 50 | master_doc = 'index' 51 | 52 | # General information about the project. 53 | project = 'hiveengine' 54 | copyright = '2018-2020, holger80' 55 | author = 'holger80' 56 | 57 | # The version info for the project you're documenting, acts as replacement for 58 | # |version| and |release|, also used in various other places throughout the 59 | # built documents. 60 | # 61 | # The short X.Y version 62 | version = parse(hiveengine.__version__).base_version 63 | version = ".".join(version.split(".")[:2]) 64 | # The full version, including alpha/beta/rc tags 65 | release = hiveengine.__version__ 66 | 67 | # The language for content autogenerated by Sphinx. Refer to documentation 68 | # for a list of supported languages. 69 | # 70 | # This is also used if you do content translation via gettext catalogs. 71 | # Usually you set "language" from the command line for these cases. 72 | language = None 73 | 74 | # There are two options for replacing |today|: either, you set today to some 75 | # non-false value, then it is used: 76 | #today = '' 77 | # Else, today_fmt is used as the format for a strftime call. 78 | #today_fmt = '%B %d, %Y' 79 | 80 | # List of patterns, relative to source directory, that match files and 81 | # directories to ignore when looking for source files. 82 | exclude_patterns = ['_build'] 83 | 84 | # The reST default role (used for this markup: `text`) to use for all 85 | # documents. 86 | #default_role = None 87 | 88 | # If true, '()' will be appended to :func: etc. cross-reference text. 89 | #add_function_parentheses = True 90 | 91 | # If true, the current module name will be prepended to all description 92 | # unit titles (such as .. function::). 93 | #add_module_names = True 94 | 95 | # If true, sectionauthor and moduleauthor directives will be shown in the 96 | # output. They are ignored by default. 97 | #show_authors = False 98 | 99 | # The name of the Pygments (syntax highlighting) style to use. 100 | pygments_style = 'sphinx' 101 | 102 | # A list of ignored prefixes for module index sorting. 103 | #modindex_common_prefix = [] 104 | 105 | # If true, keep warnings as "system message" paragraphs in the built documents. 106 | #keep_warnings = False 107 | 108 | # If true, `todo` and `todoList` produce output, else they produce nothing. 109 | todo_include_todos = False 110 | 111 | 112 | # -- Options for HTML output ---------------------------------------------- 113 | 114 | # The theme to use for HTML and HTML Help pages. See the documentation for 115 | # a list of builtin themes. 116 | html_theme = 'sphinx_rtd_theme' 117 | 118 | # Theme options are theme-specific and customize the look and feel of a theme 119 | # further. For a list of options available for each theme, see the 120 | # documentation. 121 | #html_theme_options = {} 122 | 123 | # Add any paths that contain custom themes here, relative to this directory. 124 | #html_theme_path = [] 125 | 126 | # The name for this set of Sphinx documents. If None, it defaults to 127 | # " v documentation". 128 | #html_title = None 129 | 130 | # A shorter title for the navigation bar. Default is the same as html_title. 131 | #html_short_title = None 132 | 133 | # The name of an image file (relative to this directory) to place at the top 134 | # of the sidebar. 135 | #html_logo = '_static/hiveengine-logo_2.svg' 136 | 137 | # The name of an image file (within the static path) to use as favicon of the 138 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 139 | # pixels large. 140 | #html_favicon = '_static/hiveengine-icon_bw.png' 141 | 142 | # Add any paths that contain custom static files (such as style sheets) here, 143 | # relative to this directory. They are copied after the builtin static files, 144 | # so a file named "default.css" will overwrite the builtin "default.css". 145 | html_static_path = ['_static'] 146 | 147 | # Add any extra paths that contain custom files (such as robots.txt or 148 | # .htaccess) here, relative to this directory. These files are copied 149 | # directly to the root of the documentation. 150 | #html_extra_path = [] 151 | 152 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 153 | # using the given strftime format. 154 | #html_last_updated_fmt = '%b %d, %Y' 155 | 156 | # If true, SmartyPants will be used to convert quotes and dashes to 157 | # typographically correct entities. 158 | #html_use_smartypants = True 159 | 160 | # Custom sidebar templates, maps document names to template names. 161 | #html_sidebars = {} 162 | 163 | # Additional templates that should be rendered to pages, maps page names to 164 | # template names. 165 | #html_additional_pages = {} 166 | 167 | # If false, no module index is generated. 168 | #html_domain_indices = True 169 | 170 | # If false, no index is generated. 171 | #html_use_index = True 172 | 173 | # If true, the index is split into individual pages for each letter. 174 | #html_split_index = False 175 | 176 | # If true, links to the reST sources are added to the pages. 177 | #html_show_sourcelink = True 178 | 179 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 180 | #html_show_sphinx = True 181 | 182 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 183 | #html_show_copyright = True 184 | 185 | # If true, an OpenSearch description file will be output, and all pages will 186 | # contain a tag referring to it. The value of this option must be the 187 | # base URL from which the finished HTML is served. 188 | #html_use_opensearch = '' 189 | 190 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 191 | #html_file_suffix = None 192 | 193 | # Language to be used for generating the HTML full-text search index. 194 | # Sphinx supports the following languages: 195 | # 'da', 'de', 'en', 'es', 'fi', 'fr', 'h', 'it', 'ja' 196 | # 'nl', 'no', 'pt', 'ro', 'r', 'sv', 'tr' 197 | #html_search_language = 'en' 198 | 199 | # A dictionary with options for the search language support, empty by default. 200 | # Now only 'ja' uses this config value 201 | #html_search_options = {'type': 'default'} 202 | 203 | # The name of a javascript file (relative to the configuration directory) that 204 | # implements a search results scorer. If empty, the default will be used. 205 | #html_search_scorer = 'scorer.js' 206 | 207 | # Output file base name for HTML help builder. 208 | htmlhelp_basename = 'hiveenginedoc' 209 | 210 | # -- Options for LaTeX output --------------------------------------------- 211 | 212 | latex_elements = { 213 | # The paper size ('letterpaper' or 'a4paper'). 214 | #'papersize': 'letterpaper', 215 | 216 | # The font size ('10pt', '11pt' or '12pt'). 217 | #'pointsize': '10pt', 218 | 219 | # Additional stuff for the LaTeX preamble. 220 | #'preamble': '', 221 | 222 | # Latex figure (float) alignment 223 | #'figure_align': 'htbp', 224 | } 225 | 226 | # Grouping the document tree into LaTeX files. List of tuples 227 | # (source start file, target name, title, 228 | # author, documentclass [howto, manual, or own class]). 229 | latex_documents = [ 230 | (master_doc, 'hiveengine.tex', 'hiveengine Documentation', 231 | author, 'manual'), 232 | ] 233 | 234 | # The name of an image file (relative to this directory) to place at the top of 235 | # the title page. 236 | #latex_logo = None 237 | 238 | # For "manual" documents, if this is true, then toplevel headings are parts, 239 | # not chapters. 240 | #latex_use_parts = False 241 | 242 | # If true, show page references after internal links. 243 | #latex_show_pagerefs = False 244 | 245 | # If true, show URL addresses after external links. 246 | #latex_show_urls = False 247 | 248 | # Documents to append as an appendix to all manuals. 249 | #latex_appendices = [] 250 | 251 | # If false, no module index is generated. 252 | #latex_domain_indices = True 253 | 254 | 255 | # -- Options for manual page output --------------------------------------- 256 | 257 | # One entry per manual page. List of tuples 258 | # (source start file, name, description, authors, manual section). 259 | man_pages = [ 260 | (master_doc, 'hiveengine', 'hiveengine Documentation', 261 | [author], 1) 262 | ] 263 | 264 | # If true, show URL addresses after external links. 265 | #man_show_urls = False 266 | 267 | 268 | # -- Options for Texinfo output ------------------------------------------- 269 | 270 | # Grouping the document tree into Texinfo files. List of tuples 271 | # (source start file, target name, title, author, 272 | # dir menu entry, description, category) 273 | texinfo_documents = [ 274 | (master_doc, 'hiveengine', 'hiveengine Documentation', 275 | author, 'hiveengine', 'python library for hive-engine', 276 | 'Miscellaneous'), 277 | ] 278 | 279 | # Documents to append as an appendix to all manuals. 280 | #texinfo_appendices = [] 281 | 282 | # If false, no module index is generated. 283 | #texinfo_domain_indices = True 284 | 285 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 286 | #texinfo_show_urls = 'footnote' 287 | 288 | # If true, do not generate a @detailmenu in the "Top" node's menu. 289 | #texinfo_no_detailmenu = False 290 | -------------------------------------------------------------------------------- /docs/contribute.rst: -------------------------------------------------------------------------------- 1 | Contributing to hiveengine 2 | ========================== 3 | 4 | We welcome your contributions to our project. 5 | 6 | Repository 7 | ---------- 8 | 9 | The repository of beem is currently located at: 10 | 11 | https://github.com/holgern/hiveengine 12 | 13 | Flow 14 | ---- 15 | 16 | This project makes heavy use of `git flow `_. 17 | If you are not familiar with it, then the most important thing for your 18 | to understand is that: 19 | 20 | pull requests need to be made against the develop branch 21 | 22 | How to Contribute 23 | ----------------- 24 | 25 | 0. Familiarize yourself with `contributing on github `_ 26 | 1. Fork or branch from the master. 27 | 2. Create commits following the commit style 28 | 3. Start a pull request to the master branch 29 | 4. Wait for a @holger80 or another member to review 30 | 31 | Issues 32 | ------ 33 | 34 | Feel free to submit issues and enhancement requests. 35 | 36 | Contributing 37 | ------------ 38 | 39 | Please refer to each project's style guidelines and guidelines for 40 | submitting patches and additions. In general, we follow the 41 | "fork-and-pull" Git workflow. 42 | 43 | 1. **Fork** the repo on GitHub 44 | 2. **Clone** the project to your own machine 45 | 3. **Commit** changes to your own branch 46 | 4. **Push** your work back up to your fork 47 | 5. Submit a **Pull request** so that we can review your changes 48 | 49 | .. note:: Be sure to merge the latest from "upstream" before making a pull 50 | request! 51 | 52 | Copyright and Licensing 53 | ----------------------- 54 | 55 | This library is open sources under the MIT license. We require your to 56 | release your code under that license as well. 57 | -------------------------------------------------------------------------------- /docs/hiveengine.api.rst: -------------------------------------------------------------------------------- 1 | hiveengine\.api 2 | =============== 3 | 4 | .. automodule:: hiveengine.api 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: -------------------------------------------------------------------------------- /docs/hiveengine.collection.rst: -------------------------------------------------------------------------------- 1 | hiveengine\.collection 2 | ====================== 3 | 4 | .. automodule:: hiveengine.collection 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: -------------------------------------------------------------------------------- /docs/hiveengine.exceptions.rst: -------------------------------------------------------------------------------- 1 | hiveengine\.exceptions 2 | ====================== 3 | 4 | .. automodule:: hiveengine.exceptions 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: -------------------------------------------------------------------------------- /docs/hiveengine.market.rst: -------------------------------------------------------------------------------- 1 | hiveengine\.market 2 | ================== 3 | 4 | .. automodule:: hiveengine.market 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: -------------------------------------------------------------------------------- /docs/hiveengine.nft.rst: -------------------------------------------------------------------------------- 1 | hiveengine\.nft 2 | =============== 3 | 4 | .. automodule:: hiveengine.nft 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: -------------------------------------------------------------------------------- /docs/hiveengine.nftmarket.rst: -------------------------------------------------------------------------------- 1 | hiveengine\.nftmarket 2 | ===================== 3 | 4 | .. automodule:: hiveengine.nftmarket 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: -------------------------------------------------------------------------------- /docs/hiveengine.nfts.rst: -------------------------------------------------------------------------------- 1 | hiveengine\.nfts 2 | ================ 3 | 4 | .. automodule:: hiveengine.nfts 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: -------------------------------------------------------------------------------- /docs/hiveengine.rpc.rst: -------------------------------------------------------------------------------- 1 | hiveengine\.rpc 2 | =============== 3 | 4 | .. automodule:: hiveengine.rpc 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: -------------------------------------------------------------------------------- /docs/hiveengine.tokenobject.rst: -------------------------------------------------------------------------------- 1 | hiveengine\.tokenobject 2 | ======================= 3 | 4 | .. automodule:: hiveengine.tokenobject 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: -------------------------------------------------------------------------------- /docs/hiveengine.tokens.rst: -------------------------------------------------------------------------------- 1 | hiveengine\.tokens 2 | ================== 3 | 4 | .. automodule:: hiveengine.tokens 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: -------------------------------------------------------------------------------- /docs/hiveengine.wallet.rst: -------------------------------------------------------------------------------- 1 | hiveengine\.wallet 2 | ================== 3 | 4 | .. automodule:: hiveengine.wallet 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | Welcome to hiveengine's documentation! 2 | ====================================== 3 | 4 | General 5 | ------- 6 | .. toctree:: 7 | :maxdepth: 1 8 | 9 | installation 10 | quickstart 11 | cli 12 | tutorials 13 | configuration 14 | modules 15 | contribute 16 | support 17 | indices 18 | 19 | 20 | 21 | Indices and tables 22 | ================== 23 | 24 | * :ref:`genindex` 25 | * :ref:`modindex` 26 | * :ref:`search` 27 | -------------------------------------------------------------------------------- /docs/indices.rst: -------------------------------------------------------------------------------- 1 | Indices and Tables 2 | ================== 3 | 4 | * :ref:`genindex` 5 | * :ref:`modindex` 6 | -------------------------------------------------------------------------------- /docs/installation.rst: -------------------------------------------------------------------------------- 1 | Installation 2 | ============ 3 | The minimal working python version is 3.5.x 4 | 5 | 6 | Install beem with pip: 7 | 8 | .. code:: bash 9 | 10 | pip install -U hiveengine 11 | 12 | Sometimes this does not work. Please try:: 13 | 14 | pip3 install -U hiveengine 15 | 16 | or:: 17 | 18 | python -m pip install hiveengine 19 | 20 | Manual installation 21 | ------------------- 22 | 23 | You can install beem from this repository if you want the latest 24 | but possibly non-compiling version:: 25 | 26 | git clone https://github.com/holgern/hiveengine.git 27 | cd hiveengine 28 | python setup.py build 29 | 30 | python setup.py install --user 31 | 32 | Run tests after install:: 33 | 34 | pytest 35 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | REM Command file for Sphinx documentation 4 | 5 | if "%SPHINXBUILD%" == "" ( 6 | set SPHINXBUILD=sphinx-build 7 | ) 8 | set BUILDDIR=_build 9 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . 10 | set I18NSPHINXOPTS=%SPHINXOPTS% . 11 | if NOT "%PAPER%" == "" ( 12 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% 13 | set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% 14 | ) 15 | 16 | if "%1" == "" goto help 17 | 18 | if "%1" == "help" ( 19 | :help 20 | echo.Please use `make ^` where ^ is one of 21 | echo. html to make standalone HTML files 22 | echo. dirhtml to make HTML files named index.html in directories 23 | echo. singlehtml to make a single large HTML file 24 | echo. pickle to make pickle files 25 | echo. json to make JSON files 26 | echo. htmlhelp to make HTML files and a HTML help project 27 | echo. qthelp to make HTML files and a qthelp project 28 | echo. devhelp to make HTML files and a Devhelp project 29 | echo. epub to make an epub 30 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter 31 | echo. text to make text files 32 | echo. man to make manual pages 33 | echo. texinfo to make Texinfo files 34 | echo. gettext to make PO message catalogs 35 | echo. changes to make an overview over all changed/added/deprecated items 36 | echo. xml to make Docutils-native XML files 37 | echo. pseudoxml to make pseudoxml-XML files for display purposes 38 | echo. linkcheck to check all external links for integrity 39 | echo. doctest to run all doctests embedded in the documentation if enabled 40 | echo. coverage to run coverage check of the documentation if enabled 41 | goto end 42 | ) 43 | 44 | if "%1" == "clean" ( 45 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i 46 | del /q /s %BUILDDIR%\* 47 | goto end 48 | ) 49 | 50 | 51 | REM Check if sphinx-build is available and fallback to Python version if any 52 | %SPHINXBUILD% 2> nul 53 | if errorlevel 9009 goto sphinx_python 54 | goto sphinx_ok 55 | 56 | :sphinx_python 57 | 58 | set SPHINXBUILD=python -m sphinx.__init__ 59 | %SPHINXBUILD% 2> nul 60 | if errorlevel 9009 ( 61 | echo. 62 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 63 | echo.installed, then set the SPHINXBUILD environment variable to point 64 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 65 | echo.may add the Sphinx directory to PATH. 66 | echo. 67 | echo.If you don't have Sphinx installed, grab it from 68 | echo.http://sphinx-doc.org/ 69 | exit /b 1 70 | ) 71 | 72 | :sphinx_ok 73 | 74 | 75 | if "%1" == "html" ( 76 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html 77 | if errorlevel 1 exit /b 1 78 | echo. 79 | echo.Build finished. The HTML pages are in %BUILDDIR%/html. 80 | goto end 81 | ) 82 | 83 | if "%1" == "dirhtml" ( 84 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml 85 | if errorlevel 1 exit /b 1 86 | echo. 87 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. 88 | goto end 89 | ) 90 | 91 | if "%1" == "singlehtml" ( 92 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml 93 | if errorlevel 1 exit /b 1 94 | echo. 95 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. 96 | goto end 97 | ) 98 | 99 | if "%1" == "pickle" ( 100 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle 101 | if errorlevel 1 exit /b 1 102 | echo. 103 | echo.Build finished; now you can process the pickle files. 104 | goto end 105 | ) 106 | 107 | if "%1" == "json" ( 108 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json 109 | if errorlevel 1 exit /b 1 110 | echo. 111 | echo.Build finished; now you can process the JSON files. 112 | goto end 113 | ) 114 | 115 | if "%1" == "htmlhelp" ( 116 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp 117 | if errorlevel 1 exit /b 1 118 | echo. 119 | echo.Build finished; now you can run HTML Help Workshop with the ^ 120 | .hhp project file in %BUILDDIR%/htmlhelp. 121 | goto end 122 | ) 123 | 124 | if "%1" == "qthelp" ( 125 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp 126 | if errorlevel 1 exit /b 1 127 | echo. 128 | echo.Build finished; now you can run "qcollectiongenerator" with the ^ 129 | .qhcp project file in %BUILDDIR%/qthelp, like this: 130 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\python-graphenelib.qhcp 131 | echo.To view the help file: 132 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\python-graphenelib.ghc 133 | goto end 134 | ) 135 | 136 | if "%1" == "devhelp" ( 137 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp 138 | if errorlevel 1 exit /b 1 139 | echo. 140 | echo.Build finished. 141 | goto end 142 | ) 143 | 144 | if "%1" == "epub" ( 145 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub 146 | if errorlevel 1 exit /b 1 147 | echo. 148 | echo.Build finished. The epub file is in %BUILDDIR%/epub. 149 | goto end 150 | ) 151 | 152 | if "%1" == "latex" ( 153 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 154 | if errorlevel 1 exit /b 1 155 | echo. 156 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. 157 | goto end 158 | ) 159 | 160 | if "%1" == "latexpdf" ( 161 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 162 | cd %BUILDDIR%/latex 163 | make all-pdf 164 | cd %~dp0 165 | echo. 166 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 167 | goto end 168 | ) 169 | 170 | if "%1" == "latexpdfja" ( 171 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 172 | cd %BUILDDIR%/latex 173 | make all-pdf-ja 174 | cd %~dp0 175 | echo. 176 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 177 | goto end 178 | ) 179 | 180 | if "%1" == "text" ( 181 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text 182 | if errorlevel 1 exit /b 1 183 | echo. 184 | echo.Build finished. The text files are in %BUILDDIR%/text. 185 | goto end 186 | ) 187 | 188 | if "%1" == "man" ( 189 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man 190 | if errorlevel 1 exit /b 1 191 | echo. 192 | echo.Build finished. The manual pages are in %BUILDDIR%/man. 193 | goto end 194 | ) 195 | 196 | if "%1" == "texinfo" ( 197 | %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo 198 | if errorlevel 1 exit /b 1 199 | echo. 200 | echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. 201 | goto end 202 | ) 203 | 204 | if "%1" == "gettext" ( 205 | %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale 206 | if errorlevel 1 exit /b 1 207 | echo. 208 | echo.Build finished. The message catalogs are in %BUILDDIR%/locale. 209 | goto end 210 | ) 211 | 212 | if "%1" == "changes" ( 213 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes 214 | if errorlevel 1 exit /b 1 215 | echo. 216 | echo.The overview file is in %BUILDDIR%/changes. 217 | goto end 218 | ) 219 | 220 | if "%1" == "linkcheck" ( 221 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck 222 | if errorlevel 1 exit /b 1 223 | echo. 224 | echo.Link check complete; look for any errors in the above output ^ 225 | or in %BUILDDIR%/linkcheck/output.txt. 226 | goto end 227 | ) 228 | 229 | if "%1" == "doctest" ( 230 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest 231 | if errorlevel 1 exit /b 1 232 | echo. 233 | echo.Testing of doctests in the sources finished, look at the ^ 234 | results in %BUILDDIR%/doctest/output.txt. 235 | goto end 236 | ) 237 | 238 | if "%1" == "coverage" ( 239 | %SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage 240 | if errorlevel 1 exit /b 1 241 | echo. 242 | echo.Testing of coverage in the sources finished, look at the ^ 243 | results in %BUILDDIR%/coverage/python.txt. 244 | goto end 245 | ) 246 | 247 | if "%1" == "xml" ( 248 | %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml 249 | if errorlevel 1 exit /b 1 250 | echo. 251 | echo.Build finished. The XML files are in %BUILDDIR%/xml. 252 | goto end 253 | ) 254 | 255 | if "%1" == "pseudoxml" ( 256 | %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml 257 | if errorlevel 1 exit /b 1 258 | echo. 259 | echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. 260 | goto end 261 | ) 262 | 263 | :end 264 | -------------------------------------------------------------------------------- /docs/modules.rst: -------------------------------------------------------------------------------- 1 | Modules 2 | ======= 3 | 4 | hiveengine Modules 5 | ------------------ 6 | 7 | .. toctree:: 8 | 9 | hiveengine.api 10 | hiveengine.collection 11 | hiveengine.exceptions 12 | hiveengine.market 13 | hiveengine.nft 14 | hiveengine.rpc 15 | hiveengine.tokenobject 16 | hiveengine.tokens 17 | hiveengine.wallet 18 | -------------------------------------------------------------------------------- /docs/quickstart.rst: -------------------------------------------------------------------------------- 1 | Quickstart 2 | ========== 3 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | autobahn>=0.14 2 | future 3 | ecdsa 4 | requests 5 | beem 6 | pyyaml 7 | pytest 8 | pytest-mock 9 | coverage 10 | mock 11 | appdirs 12 | Click 13 | prettytable 14 | sphinx_rtd_theme 15 | sphinx-click 16 | -------------------------------------------------------------------------------- /docs/support.rst: -------------------------------------------------------------------------------- 1 | ********************* 2 | Support and Questions 3 | ********************* 4 | 5 | Help and discussion channel for hiveengine can be found here: 6 | 7 | * https://discord.gg/4HM592V 8 | -------------------------------------------------------------------------------- /docs/tutorials.rst: -------------------------------------------------------------------------------- 1 | ********* 2 | Tutorials 3 | ********* 4 | -------------------------------------------------------------------------------- /example/send_token.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | from __future__ import division 3 | from __future__ import print_function 4 | from __future__ import unicode_literals 5 | import sys 6 | from datetime import datetime, timedelta 7 | import time 8 | import io 9 | import logging 10 | from beem import Steem 11 | from hiveengine.wallet import Wallet 12 | from beembase import transactions, operations 13 | log = logging.getLogger(__name__) 14 | logging.basicConfig(level=logging.INFO) 15 | 16 | 17 | if __name__ == "__main__": 18 | stm = Steem() 19 | stm.wallet.unlock(pwd="wallet_pass") 20 | wallet = Wallet("beembot", steem_instance=stm) 21 | dragon_token = wallet.get_token("DRAGON") 22 | if dragon_token is not None and float(dragon_token["balance"]) >= 0.01: 23 | print("balance %.2f" % float(dragon_token["balance"])) 24 | print(wallet.transfer("holger80", 0.01, "DRAGON", "test")) 25 | else: 26 | print("Could not sent") 27 | time.sleep(15) 28 | wallet.refresh() 29 | dragon_token = wallet.get_token("DRAGON") 30 | print("new balance %.2f" % float(dragon_token["balance"])) 31 | 32 | -------------------------------------------------------------------------------- /example/stream_all_blocks.py: -------------------------------------------------------------------------------- 1 | import time 2 | import logging 3 | import json 4 | from hiveengine.api import Api 5 | from beem.block import Block 6 | log = logging.getLogger(__name__) 7 | logging.basicConfig(level=logging.INFO) 8 | 9 | 10 | if __name__ == "__main__": 11 | api = Api() 12 | latest_block = api.get_latest_block_info() 13 | 14 | scan_token = ['BTC'] 15 | print("Scanning all blocks from 0 to %d..." % latest_block['blockNumber']) 16 | steemp_payments = [] 17 | 18 | for block_num in range(14500, latest_block['blockNumber']): 19 | block = api.get_block_info(block_num) 20 | if block_num % 1000 == 0: 21 | print("%.2f %%" % (block["blockNumber"]/latest_block['blockNumber'] * 100)) 22 | for trx in block["transactions"]: 23 | 24 | if trx["contract"] not in ['market']: 25 | continue 26 | if trx["action"] not in ['buy', 'sell']: 27 | continue 28 | 29 | 30 | logs = json.loads(trx["logs"]) 31 | sender = trx["sender"] 32 | payload = json.loads(trx["payload"]) 33 | contract = trx["contract"] 34 | action = trx["action"] 35 | 36 | if action == "sell": 37 | if "events" not in logs: 38 | continue 39 | elif len(logs["events"]) == 1: 40 | continue 41 | else: 42 | token_found = False 43 | for transfer in logs["events"]: 44 | if transfer["data"]["symbol"] in scan_token: 45 | token_found = True 46 | if token_found: 47 | steem_block = Block(block["refSteemBlockNumber"]) 48 | print("%d (%s) - %s:" % (block["blockNumber"], steem_block.json()["timestamp"], trx['transactionId'])) 49 | print("%s sold %s %s for %s" % (trx["sender"], payload["quantity"], payload["symbol"], payload["price"])) 50 | for transfer in logs["events"]: 51 | print(" - %s transfers %s %s to %s" % (transfer["data"]["from"], transfer["data"]["quantity"], transfer["data"]["symbol"], transfer["data"]["to"])) 52 | 53 | elif action == "buy": 54 | if "events" not in logs: 55 | continue 56 | elif len(logs["events"]) == 1: 57 | continue 58 | else: 59 | token_found = False 60 | for transfer in logs["events"]: 61 | if transfer["data"]["symbol"] in scan_token: 62 | token_found = True 63 | if token_found: 64 | steem_block = Block(block["refSteemBlockNumber"]) 65 | print("%d (%s) - %s" % (block["blockNumber"], steem_block.json()["timestamp"], trx['transactionId'])) 66 | print("%s bought %s %s for %s" % (trx["sender"], payload["quantity"], payload["symbol"], payload["price"])) 67 | for transfer in logs["events"]: 68 | print(" - %s transfers %s %s to %s" % (transfer["data"]["from"], transfer["data"]["quantity"], transfer["data"]["symbol"], transfer["data"]["to"])) 69 | 70 | -------------------------------------------------------------------------------- /example/token_upvote_bot.py: -------------------------------------------------------------------------------- 1 | # This Python file uses the following encoding: utf-8 2 | # (c) holger80 3 | from __future__ import absolute_import 4 | from __future__ import division 5 | from __future__ import print_function 6 | from __future__ import unicode_literals 7 | from beem import Steem 8 | from beem.comment import Comment 9 | from beem.nodelist import NodeList 10 | from hiveengine.wallet import Wallet 11 | import time 12 | 13 | 14 | if __name__ == "__main__": 15 | nodelist = NodeList() 16 | nodelist.update_nodes() 17 | stm = Steem(node=nodelist.get_hive_nodes()) 18 | 19 | 20 | # edit here 21 | upvote_account = "beembot" 22 | upvote_token = "DRAGON" 23 | token_weight_factor = 100 # multiply token amount to get weight 24 | min_token_amount = 0.01 25 | max_post_age_days = 3 26 | whitelist = [] # When empty, the whitelist is disabled 27 | blacklist_tags = [] # When empty, the tag blacklist is disabled 28 | reply_comment = "" # When empty, no reply comment is created 29 | only_main_posts = True 30 | stm.wallet.unlock("wallet-passwd") 31 | 32 | wallet = Wallet(upvote_account, steem_instance=stm) 33 | 34 | last_steem_block = 1950 # It is a good idea to store this block, otherwise all transfers will be checked again 35 | while True: 36 | history = wallet.get_history(upvote_token) 37 | for h in history: 38 | if int(h["block"]) <= last_steem_block: 39 | continue 40 | if h["to"] != upvote_account: 41 | continue 42 | last_steem_block = int(h["block"]) 43 | if len(whitelist) > 0 and h["from"] not in whitelist: 44 | print("%s is not in the whitelist, skipping" % h["from"]) 45 | continue 46 | if float(h["quantity"]) < min_token_amount: 47 | print("Below min token amount skipping...") 48 | continue 49 | try: 50 | c = Comment(h["memo"], steem_instance=stm) 51 | except: 52 | print("%s is not a valid url, skipping" % h["memo"]) 53 | continue 54 | 55 | if c.is_comment() and only_main_posts: 56 | print("%s from %s is a comment, skipping" % (c["permlink"], c["author"])) 57 | continue 58 | if (c.time_elapsed().total_seconds() / 60 / 60 / 24) > max_post_age_days: 59 | print("Post is to old, skipping") 60 | continue 61 | tags_ok = True 62 | if len(blacklist_tags) > 0 and "tags" in c: 63 | for t in blacklist_tags: 64 | if t in c["tags"]: 65 | tags_ok = False 66 | if not tags_ok: 67 | print("skipping, as one tag is blacklisted") 68 | continue 69 | already_voted = False 70 | for v in c["active_votes"]: 71 | if v["voter"] == upvote_account: 72 | already_voted = True 73 | if already_voted: 74 | print("skipping, as already upvoted") 75 | continue 76 | 77 | upvote_weight = float(h["quantity"]) * token_weight_factor 78 | if upvote_weight > 100: 79 | upvote_weight = 100 80 | print("upvote %s from %s with %.2f %%" % (c["permlink"], c["author"], upvote_weight)) 81 | print(c.upvote(weight=upvote_weight, voter=upvote_account)) 82 | if len(reply_comment) > 0: 83 | time.sleep(4) 84 | print(c.reply(reply_comment, author=upvote_account)) 85 | 86 | time.sleep(60) 87 | -------------------------------------------------------------------------------- /hiveengine-onedir.spec: -------------------------------------------------------------------------------- 1 | # -*- mode: python -*- 2 | 3 | import os 4 | import glob 5 | import platform 6 | from PyInstaller.utils.hooks import exec_statement 7 | import websocket 8 | import certifi 9 | from os.path import join, dirname, basename 10 | 11 | block_cipher = None 12 | os_name = platform.system() 13 | binaries = [] 14 | 15 | websocket_lib_path = dirname(certifi.__file__) 16 | websocket_cacert_file_path = join(websocket_lib_path, 'cacert.pem') 17 | analysis_data = [ 18 | # For websocket library to find "cacert.pem" file, it must be in websocket 19 | # directory inside of distribution directory. 20 | # This line can be removed once PyInstaller adds hook-websocket.py 21 | (websocket_cacert_file_path, join('.', basename(websocket_lib_path))) 22 | ] 23 | 24 | a = Analysis(['hiveengine/cli.py'], 25 | pathex=['hiveengine'], 26 | binaries=binaries, 27 | datas=analysis_data, 28 | hiddenimports=['scrypt', '_scrypt', 'websocket', 'pylibscrypt', 'cffi', 'cryptography.hazmat.backends.openssl', 'cryptography', 'pkg_resources.py2_warn'], 29 | hookspath=[], 30 | runtime_hooks=[], 31 | excludes=['matplotlib', 'scipy', 'pandas', 'numpy', 'PyQt5', 'tkinter'], 32 | win_no_prefer_redirects=False, 33 | win_private_assemblies=False, 34 | cipher=block_cipher) 35 | pyz = PYZ(a.pure, a.zipped_data, 36 | cipher=block_cipher) 37 | 38 | exe = EXE( 39 | pyz, 40 | a.scripts, 41 | exclude_binaries=True, 42 | name='hiveengine', 43 | debug=False, 44 | strip=False, 45 | upx=False, 46 | console=True, 47 | icon='hiveengine.ico', 48 | ) 49 | coll = COLLECT( 50 | exe, 51 | a.binaries, 52 | a.zipfiles, 53 | a.datas, 54 | name='hiveengine', 55 | strip=False, 56 | upx=False 57 | ) 58 | 59 | if platform.system() == 'Darwin': 60 | info_plist = {'NSHighResolutionCapable': 'True', 'NSPrincipalClass': 'NSApplication'} 61 | app = BUNDLE(exe, 62 | name='hiveengine.app', 63 | icon='hiveengine.ico', 64 | bundle_identifier=None 65 | ) -------------------------------------------------------------------------------- /hiveengine-onefile.spec: -------------------------------------------------------------------------------- 1 | # -*- mode: python -*- 2 | 3 | import os 4 | import glob 5 | import platform 6 | from PyInstaller.utils.hooks import exec_statement 7 | import websocket 8 | import certifi 9 | from os.path import join, dirname, basename 10 | 11 | block_cipher = None 12 | os_name = platform.system() 13 | binaries = [] 14 | 15 | websocket_lib_path = dirname(certifi.__file__) 16 | websocket_cacert_file_path = join(websocket_lib_path, 'cacert.pem') 17 | analysis_data = [ 18 | # For websocket library to find "cacert.pem" file, it must be in websocket 19 | # directory inside of distribution directory. 20 | # This line can be removed once PyInstaller adds hook-websocket.py 21 | (websocket_cacert_file_path, join('.', basename(websocket_lib_path))) 22 | ] 23 | 24 | 25 | a = Analysis(['hiveengine/cli.py'], 26 | pathex=['hiveengine'], 27 | binaries=binaries, 28 | datas=analysis_data, 29 | hiddenimports=['scrypt', '_scrypt', 'websocket', 'pylibscrypt', 'cffi', 'cryptography.hazmat.backends.openssl', 'cryptography', 'pkg_resources.py2_warn'], 30 | hookspath=[], 31 | runtime_hooks=[], 32 | excludes=['matplotlib', 'scipy', 'pandas', 'numpy', 'PyQt5', 'tkinter'], 33 | win_no_prefer_redirects=False, 34 | win_private_assemblies=False, 35 | cipher=block_cipher) 36 | pyz = PYZ(a.pure, a.zipped_data, 37 | cipher=block_cipher) 38 | exe = EXE(pyz, 39 | a.scripts, 40 | a.binaries, 41 | a.zipfiles, 42 | a.datas, 43 | name='hiveengine', 44 | debug=False, 45 | strip=False, 46 | upx=False, 47 | runtime_tmpdir=None, 48 | console=True, 49 | icon='hiveengine.ico', 50 | ) 51 | 52 | if platform.system() == 'Darwin': 53 | info_plist = {'NSHighResolutionCapable': 'True', 'NSPrincipalClass': 'NSApplication'} 54 | app = BUNDLE(exe, 55 | name='hiveengine.app', 56 | icon='hiveengine.ico', 57 | bundle_identifier=None 58 | ) -------------------------------------------------------------------------------- /hiveengine.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/holgern/hiveengine/d04f3fe7d94c528183026a48f52344c968072c83/hiveengine.ico -------------------------------------------------------------------------------- /hiveengine/__init__.py: -------------------------------------------------------------------------------- 1 | """ hiveengine.""" 2 | from .version import version as __version__ 3 | 4 | __all__ = [ 5 | "api", 6 | "cli", 7 | "collection", 8 | "exceptions", 9 | "market", 10 | "nftmarket", 11 | "nft", 12 | "nfts", 13 | "rpc", 14 | "tokenobject", 15 | "tokens", 16 | "wallet" 17 | ] 18 | -------------------------------------------------------------------------------- /hiveengine/api.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | from __future__ import division 3 | from __future__ import print_function 4 | from __future__ import unicode_literals 5 | import sys 6 | from datetime import datetime, timedelta, date 7 | import time 8 | import json 9 | import requests 10 | from timeit import default_timer as timer 11 | import logging 12 | from .rpc import RPC 13 | 14 | 15 | class Api(object): 16 | """ Access the hive-engine API 17 | """ 18 | def __init__(self, url=None, rpcurl=None, user=None, password=None, **kwargs): 19 | if url is None: 20 | self.url = 'https://api.hive-engine.com/' 21 | else: 22 | self.url = url 23 | if url is not None and rpcurl is None: 24 | self.rpc = RPC(url=url) 25 | else: 26 | self.rpc = RPC(url=rpcurl) 27 | 28 | def get_history(self, account, symbol, limit=1000, offset=0): 29 | """"Get the transaction history for an account and a token""" 30 | response = requests.get("https://accounts.hive-engine.com/accountHistory?account=%s&limit=%d&offset=%d&symbol=%s" % (account, limit, offset, symbol)) 31 | cnt2 = 0 32 | while response.status_code != 200 and cnt2 < 10: 33 | response = requests.get("https://accounts.hive-engine.com/accountHistory?account=%s&limit=%d&offset=%d&symbol=%s" % (account, limit, offset, symbol)) 34 | cnt2 += 1 35 | return response.json() 36 | 37 | def get_latest_block_info(self): 38 | """get the latest block of the sidechain""" 39 | ret = self.rpc.getLatestBlockInfo(endpoint="blockchain") 40 | if isinstance(ret, list) and len(ret) == 1: 41 | return ret[0] 42 | else: 43 | return ret 44 | 45 | def get_status(self): 46 | """gets the status of the sidechain""" 47 | ret = self.rpc.getStatus(endpoint="blockchain") 48 | if isinstance(ret, list) and len(ret) == 1: 49 | return ret[0] 50 | else: 51 | return ret 52 | 53 | def get_block_info(self, blocknumber): 54 | """get the block with the specified block number of the sidechain""" 55 | ret = self.rpc.getBlockInfo({"blockNumber": blocknumber}, endpoint="blockchain") 56 | if isinstance(ret, list) and len(ret) == 1: 57 | return ret[0] 58 | else: 59 | return ret 60 | 61 | def get_transaction_info(self, txid): 62 | """Retrieve the specified transaction info of the sidechain""" 63 | ret = self.rpc.getTransactionInfo({"txid": txid}, endpoint="blockchain") 64 | if isinstance(ret, list) and len(ret) == 1: 65 | return ret[0] 66 | else: 67 | return ret 68 | 69 | def get_contract(self, contract_name): 70 | """ Get the contract specified from the database""" 71 | ret = self.rpc.getContract({"name": contract_name}, endpoint="contracts") 72 | if isinstance(ret, list) and len(ret) == 1: 73 | return ret[0] 74 | else: 75 | return ret 76 | 77 | def find_one(self, contract_name, table_name, query = {}): 78 | """Get the object that matches the query from the table of the specified contract""" 79 | ret = self.rpc.findOne({"contract": contract_name, "table": table_name, "query": query}, endpoint="contracts") 80 | return ret 81 | 82 | def find(self, contract_name, table_name, query = {}, limit=1000, offset=0, indexes=[]): 83 | """Get an array of objects that match the query from the table of the specified contract""" 84 | ret = self.rpc.find({"contract": contract_name, "table": table_name, "query": query, 85 | "limit": limit, "offset": offset, "indexes": indexes}, endpoint="contracts") 86 | if isinstance(ret, list) and len(ret) == 1: 87 | return ret[0] 88 | else: 89 | return ret 90 | 91 | def find_all(self, contract_name, table_name, query = {}): 92 | """Get an array of objects that match the query from the table of the specified contract""" 93 | limit = 1000 94 | offset = 0 95 | last_result = [] 96 | cnt = 0 97 | result = [] 98 | while last_result is not None and len(last_result) == limit or cnt == 0: 99 | cnt += 1 100 | last_result = self.find(contract_name, table_name, query, limit=limit, offset=offset) 101 | if last_result is not None: 102 | result += last_result 103 | offset += limit 104 | return result 105 | 106 | -------------------------------------------------------------------------------- /hiveengine/cli.py: -------------------------------------------------------------------------------- 1 | # This Python file uses the following encoding: utf-8 2 | from __future__ import absolute_import 3 | from __future__ import division 4 | from __future__ import print_function 5 | from __future__ import unicode_literals 6 | from builtins import bytes, int, str 7 | from beem import Hive 8 | from beem.account import Account 9 | from beem.nodelist import NodeList 10 | from beem.utils import formatTimeString, addTzInfo 11 | from beem.blockchain import Blockchain 12 | from beem.exceptions import WrongMasterPasswordException 13 | from hiveengine.api import Api 14 | from hiveengine.tokens import Tokens 15 | from hiveengine.tokenobject import Token 16 | from hiveengine.market import Market 17 | from hiveengine.nftmarket import NftMarket 18 | from hiveengine.wallet import Wallet 19 | from hiveengine.nfts import Nfts 20 | from hiveengine.nft import Nft 21 | from hiveengine.collection import Collection 22 | from prettytable import PrettyTable 23 | import time 24 | import json 25 | import click 26 | from click_shell import shell 27 | import logging 28 | import sys 29 | import os 30 | import io 31 | import argparse 32 | import re 33 | import six 34 | from datetime import datetime 35 | from beem.instance import set_shared_blockchain_instance, shared_blockchain_instance 36 | from hiveengine.version import version as __version__ 37 | click.disable_unicode_literals_warning = True 38 | log = logging.getLogger(__name__) 39 | try: 40 | import keyring 41 | if not isinstance(keyring.get_keyring(), keyring.backends.fail.Keyring): 42 | KEYRING_AVAILABLE = True 43 | else: 44 | KEYRING_AVAILABLE = False 45 | except ImportError: 46 | KEYRING_AVAILABLE = False 47 | 48 | 49 | def unlock_wallet(stm, password=None, allow_wif=True): 50 | if stm.unsigned and stm.nobroadcast: 51 | return True 52 | if not stm.wallet.locked(): 53 | return True 54 | password_storage = stm.config["password_storage"] 55 | if not password and KEYRING_AVAILABLE and password_storage == "keyring": 56 | password = keyring.get_password("beem", "wallet") 57 | if not password and password_storage == "environment" and "UNLOCK" in os.environ: 58 | password = os.environ.get("UNLOCK") 59 | if bool(password): 60 | stm.wallet.unlock(password) 61 | else: 62 | if allow_wif: 63 | password = click.prompt("Password to unlock wallet or posting/active wif", confirmation_prompt=False, hide_input=True) 64 | else: 65 | password = click.prompt("Password to unlock wallet", confirmation_prompt=False, hide_input=True) 66 | try: 67 | stm.wallet.unlock(password) 68 | except: 69 | try: 70 | stm.wallet.setKeys([password]) 71 | print("Wif accepted!") 72 | return True 73 | except: 74 | if allow_wif: 75 | raise WrongMasterPasswordException("entered password is not a valid password/wif") 76 | else: 77 | raise WrongMasterPasswordException("entered password is not a valid password") 78 | 79 | if stm.wallet.locked(): 80 | if password_storage == "keyring" or password_storage == "environment": 81 | print("Wallet could not be unlocked with %s!" % password_storage) 82 | password = click.prompt("Password to unlock wallet", confirmation_prompt=False, hide_input=True) 83 | if bool(password): 84 | unlock_wallet(stm, password=password) 85 | if not stm.wallet.locked(): 86 | return True 87 | else: 88 | print("Wallet could not be unlocked!") 89 | return False 90 | else: 91 | print("Wallet Unlocked!") 92 | return True 93 | 94 | 95 | @shell(prompt='hiveengine> ', intro='Starting hiveengine... (use help to list all commands)', chain=True) 96 | # click.group(chain=True) 97 | @click.option( 98 | '--no-broadcast', '-d', is_flag=True, default=False, help="Do not broadcast") 99 | @click.option( 100 | '--verbose', '-v', default=3, help='Verbosity') 101 | @click.version_option(version=__version__) 102 | def cli(no_broadcast, verbose): 103 | # Logging 104 | log = logging.getLogger(__name__) 105 | verbosity = ["critical", "error", "warn", "info", "debug"][int( 106 | min(verbose, 4))] 107 | log.setLevel(getattr(logging, verbosity.upper())) 108 | formatter = logging.Formatter( 109 | '%(asctime)s - %(name)s - %(levelname)s - %(message)s') 110 | ch = logging.StreamHandler() 111 | ch.setLevel(getattr(logging, verbosity.upper())) 112 | ch.setFormatter(formatter) 113 | log.addHandler(ch) 114 | debug = verbose > 0 115 | stm = Hive( 116 | nobroadcast=no_broadcast, 117 | ) 118 | set_shared_blockchain_instance(stm) 119 | pass 120 | 121 | 122 | @cli.command() 123 | @click.argument('objects', nargs=-1) 124 | def info(objects): 125 | """ Show basic blockchain info 126 | 127 | General information about hive-engine, a block, an account, a token, 128 | and a transaction id 129 | """ 130 | stm = None 131 | api = Api() 132 | 133 | if not objects: 134 | latest_block = api.get_latest_block_info() 135 | tokens = Tokens() 136 | nfts = Nfts() 137 | status = api.get_status() 138 | t = PrettyTable(["Key", "Value"]) 139 | t.align = "l" 140 | t.add_row(["latest block number", latest_block["blockNumber"]]) 141 | t.add_row(["latest hive block", latest_block["refHiveBlockNumber"]]) 142 | t.add_row(["latest timestamp", latest_block["timestamp"]]) 143 | t.add_row(["transactions", len(latest_block["transactions"])]) 144 | t.add_row(["virtualTransactions", len(latest_block["virtualTransactions"])]) 145 | t.add_row(["Number of created tokens", len(tokens)]) 146 | t.add_row(["Number of created Nft symbols", len(nfts)]) 147 | t.add_row(["lastParsedHiveBlockNumber", status["lastParsedHiveBlockNumber"]]) 148 | t.add_row(["SSCnodeVersion", status["SSCnodeVersion"]]) 149 | print(t.get_string()) 150 | for obj in objects: 151 | if re.match("^[0-9-]*$", obj): 152 | block_info = api.get_block_info(int(obj)) 153 | print("Block info: %d" % (int(obj))) 154 | trx_nr = 0 155 | for trx in block_info["transactions"]: 156 | trx_nr += 1 157 | t = PrettyTable(["Key", "Value"]) 158 | t.align = "l" 159 | t.add_row(["trx_nr", str(trx_nr)]) 160 | t.add_row(["action", trx["action"]]) 161 | t.add_row(["contract", trx["contract"]]) 162 | t.add_row(["logs", json.dumps(json.loads(trx["logs"]), indent=4)]) 163 | t.add_row(["payload", json.dumps(json.loads(trx["payload"]), indent=4)]) 164 | t.add_row(["refHiveBlockNumber", trx["refHiveBlockNumber"]]) 165 | t.add_row(["timestamp", block_info["timestamp"]]) 166 | t.add_row(["sender", trx["sender"]]) 167 | t.add_row(["transactionId", trx["transactionId"]]) 168 | print(t.get_string()) 169 | elif re.match("^[A-Z0-9\-\._]{1,16}$", obj): 170 | 171 | tokens = Tokens() 172 | nfts = Nfts() 173 | token = tokens.get_token(obj) 174 | nft = nfts.get_nft(obj) 175 | if token is None and nft is None: 176 | print("Could not found symbol %s" % obj) 177 | return 178 | if token is not None: 179 | t = PrettyTable(["Key", "Value"]) 180 | t.align = "l" 181 | print("Token: %s" % obj) 182 | metadata = json.loads(token["metadata"]) 183 | for key in token: 184 | if key == "metadata": 185 | if "url" in metadata: 186 | t.add_row(["metadata_url", metadata["url"]]) 187 | if "icon" in metadata: 188 | t.add_row(["metadata_icon", metadata["icon"]]) 189 | if "desc" in metadata: 190 | t.add_row(["metadata_desc", metadata["desc"]]) 191 | else: 192 | t.add_row([key, token[key]]) 193 | market_info = token.get_market_info() 194 | if market_info is not None: 195 | for key in market_info: 196 | if key in ["_id", "symbol"]: 197 | continue 198 | t.add_row([key, market_info[key]]) 199 | print(t.get_string()) 200 | if nft is not None: 201 | t = PrettyTable(["Key", "Value"]) 202 | t.align = "l" 203 | print("NFT: %s" % obj) 204 | metadata = json.loads(nft["metadata"]) 205 | properties = nft["properties"] 206 | for key in nft: 207 | if key == "metadata": 208 | if "url" in metadata: 209 | t.add_row(["metadata_url", metadata["url"]]) 210 | if "icon" in metadata: 211 | t.add_row(["metadata_icon", metadata["icon"]]) 212 | if "desc" in metadata: 213 | t.add_row(["metadata_desc", metadata["desc"]]) 214 | elif key == "properties": 215 | t.add_row([key, list(properties.keys())]) 216 | else: 217 | t.add_row([key, nft[key]]) 218 | 219 | print(t.get_string()) 220 | print("%s properties" % obj) 221 | t = PrettyTable(["Property", "type", "isReadOnly", "authorizedEditingAccounts", "authorizedEditingContracts"]) 222 | t.align = "l" 223 | for prop in properties: 224 | t.add_row([prop, properties[prop]["type"], properties[prop]["isReadOnly"], properties[prop]["authorizedEditingAccounts"], properties[prop]["authorizedEditingContracts"]]) 225 | print(t) 226 | elif re.match("^[a-zA-Z0-9\-\._]{2,16}$", obj): 227 | print("Token Wallet: %s" % obj) 228 | if stm is None: 229 | nodelist = NodeList() 230 | nodelist.update_nodes() 231 | stm = Hive(node=nodelist.get_hive_nodes()) 232 | wallet = Wallet(obj, blockchain_instance=stm) 233 | t = PrettyTable(["id", "symbol", "balance", "stake", "pendingUnstake", "delegationsIn", "delegationsOut", "pendingUndelegations"]) 234 | t.align = "l" 235 | for token in wallet: 236 | if "stake" in token: 237 | stake = token["stake"] 238 | else: 239 | stake = "-" 240 | if "pendingUnstake" in token: 241 | pendingUnstake = token["pendingUnstake"] 242 | else: 243 | pendingUnstake = "-" 244 | t.add_row([token["_id"], token["symbol"], token["balance"], stake, pendingUnstake, token["delegationsIn"], 245 | token["delegationsOut"], token["pendingUndelegations"]]) 246 | print(t.get_string(sortby="id")) 247 | elif len(obj.split("-")[0]) == 40: 248 | print("Transaction Id: %s" % obj) 249 | trx = api.get_transaction_info(obj) 250 | if trx is None: 251 | print("trx_id: %s is not a valid hive-engine trx_id!" % obj) 252 | return 253 | payload = json.loads(trx["payload"]) 254 | logs = json.loads(trx["logs"]) 255 | t = PrettyTable(["Key", "Value"]) 256 | t.align = "l" 257 | t.add_row(["blockNumber", str(trx["blockNumber"])]) 258 | t.add_row(["action", trx["action"]]) 259 | t.add_row(["contract", trx["contract"]]) 260 | t.add_row(["logs", json.dumps(logs, indent=4)]) 261 | t.add_row(["payload", json.dumps(payload, indent=4)]) 262 | t.add_row(["refHiveBlockNumber", trx["refHiveBlockNumber"]]) 263 | t.add_row(["sender", trx["sender"]]) 264 | t.add_row(["transactionId", trx["transactionId"]]) 265 | print(t.get_string()) 266 | 267 | @cli.command() 268 | def tokenlist(): 269 | """ Show list of all tokens 270 | 271 | """ 272 | tokens = Tokens() 273 | t = PrettyTable(["id", "Symbol", "name"]) 274 | t.align = "l" 275 | for token in tokens: 276 | t.add_row([token["_id"], token["symbol"], token["name"]]) 277 | print(t) 278 | 279 | 280 | @cli.command() 281 | def nftlist(): 282 | """ Show list of all NFTs 283 | 284 | """ 285 | nfts = Nfts() 286 | t = PrettyTable(["id", "Symbol", "Name"]) 287 | t.align = "l" 288 | for nft in nfts: 289 | t.add_row([nft["_id"], nft["symbol"], nft["name"]]) 290 | print(t) 291 | 292 | 293 | @cli.command() 294 | def nftparams(): 295 | """ Show params of all NFTs 296 | 297 | """ 298 | nfts = Nfts() 299 | t = PrettyTable(["key", "value"]) 300 | t.align = "l" 301 | params = nfts.get_nft_params() 302 | for key in params: 303 | t.add_row([key, str(params[key])]) 304 | print(t) 305 | 306 | 307 | @cli.command() 308 | @click.argument('account', nargs=1) 309 | @click.argument('symbol', nargs=-1) 310 | @click.option('--sort-by-id', '-s', is_flag=True, default=False, help="Sort NFTs by their ID") 311 | def collection(account, symbol, sort_by_id): 312 | """Return NFT collection for an account""" 313 | if len(symbol) == 0: 314 | nfts = Nfts() 315 | symbol = nfts.get_symbol_list() 316 | collection = Collection(account) 317 | for s in symbol: 318 | if s not in collection: 319 | continue 320 | nft = Nft(s) 321 | groupBy = nft["groupBy"] 322 | 323 | print("NFT: %s" % s) 324 | if sort_by_id: 325 | table_header = ['_id', 'lockedTokens'] 326 | for prop in nft.properties[:3]: 327 | table_header.append(prop) 328 | t = PrettyTable(table_header) 329 | t.align = "l" 330 | for nft_object in collection[s]: 331 | row = [nft_object["_id"], nft_object["lockedTokens"]] 332 | for prop in nft.properties[:3]: 333 | if prop in nft_object["properties"]: 334 | row.append(nft_object["properties"][prop]) 335 | else: 336 | row.append("") 337 | t.add_row(row) 338 | print(t) 339 | else: 340 | nft_id_by_prop = {} 341 | nft_prop = {} 342 | 343 | for nft_obj in collection[s]: 344 | ident = "" 345 | for g in groupBy: 346 | if g in nft_obj["properties"] and nft_obj["properties"][g] is not None: 347 | ident += nft_obj["properties"][g].lower() + " - " 348 | if ident[:-3] in nft_id_by_prop: 349 | nft_id_by_prop[ident[:-3]].append(str(nft_obj["_id"])) 350 | else: 351 | nft_id_by_prop[ident[:-3]] = [str(nft_obj["_id"])] 352 | nft_prop[ident[:-3]] = nft_obj["properties"] 353 | table_header = ['nftIds'] 354 | 355 | for prop in nft.properties[:3]: 356 | table_header.append(prop) 357 | t = PrettyTable(table_header) 358 | t.align = "l" 359 | t._max_width = {"nftIds": 60} 360 | for key in nft_id_by_prop: 361 | row = [" ".join(nft_id_by_prop[key])] 362 | for prop in nft.properties[:3]: 363 | if prop in nft_prop[key]: 364 | row.append(nft_prop[key][prop]) 365 | else: 366 | row.append("") 367 | t.add_row(row) 368 | print(t) 369 | 370 | @cli.command() 371 | @click.argument('symbol', nargs=-1) 372 | def nftinfo(symbol): 373 | """Returns information about an NFT symbol""" 374 | if len(symbol) == 0: 375 | nfts = Nfts() 376 | symbol = nfts.get_symbol_list() 377 | for s in symbol: 378 | nft = Nft(s) 379 | print("NFT: %s" % s) 380 | t = PrettyTable(["key", "value"]) 381 | t._max_width = {"value": 60} 382 | t.align = "l" 383 | for key in list(nft.keys()): 384 | if key != "properties": 385 | t.add_row([key, nft[key]]) 386 | else: 387 | t.add_row([key, nft.properties]) 388 | print(t) 389 | t = PrettyTable(["property", "type", "isReadOnly", "authorizedEditingAccounts", "authorizedEditingContracts"]) 390 | t._max_width = {"value": 60} 391 | t.align = "l" 392 | for prop in nft.properties: 393 | value = [prop] 394 | for p_key in nft["properties"][prop]: 395 | value.append(nft["properties"][prop][p_key]) 396 | t.add_row(value) 397 | print(t) 398 | 399 | @cli.command() 400 | @click.argument('symbol', nargs=1) 401 | @click.argument('nftid', nargs=-1) 402 | def nft(symbol, nftid): 403 | """Returns information about an NFT ID""" 404 | nft = Nft(symbol) 405 | if len(nftid) == 0: 406 | groupBy = nft["groupBy"] 407 | max_id = nft["supply"] 408 | nft_count = {} 409 | 410 | for _id in range(max_id): 411 | nft_obj = nft.get_id(int(_id) + 1) 412 | ident = "" 413 | for g in groupBy: 414 | if g in nft_obj["properties"] and nft_obj["properties"][g] is not None: 415 | ident += nft_obj["properties"][g].lower() + " - " 416 | if ident[:-3] in nft_count: 417 | nft_count[ident[:-3]] += 1 418 | else: 419 | nft_count[ident[:-3]] = 1 420 | t = PrettyTable(["type", "N", "percentage"]) 421 | t.align = "l" 422 | for key in nft_count: 423 | t.add_row([key, nft_count[key], round(nft_count[key]/max_id*100, 2)]) 424 | print(t) 425 | 426 | else: 427 | t = PrettyTable(["_id", "account", "ownedBy", "lockedTokens", "properties"]) 428 | t._max_width = {"properties": 60} 429 | t.align = "l" 430 | for _id in nftid: 431 | nft_obj = nft.get_id(int(_id)) 432 | t.add_row([_id, nft_obj["account"], nft_obj["ownedBy"], nft_obj["lockedTokens"], nft_obj["properties"]]) 433 | print(t) 434 | 435 | 436 | @cli.command() 437 | @click.argument('symbol', nargs=1) 438 | @click.option('--top', '-t', help='Show only the top n accounts', default=50) 439 | def richlist(symbol, top): 440 | """ Shows the richlist of a token 441 | 442 | """ 443 | token = Token(symbol) 444 | holder = token.get_holder() 445 | market_info = token.get_market_info() 446 | last_price = float(market_info["lastPrice"]) 447 | sorted_holder = sorted(holder, key=lambda account: float(account["balance"]), reverse=True) 448 | t = PrettyTable(["Balance", "Account", "Value [HIVE]"]) 449 | t.align = "l" 450 | for balance in sorted_holder[:int(top)]: 451 | t.add_row([balance["balance"], balance["account"], "%.3f" % (float(balance["balance"]) * last_price)]) 452 | print(t.get_string()) 453 | 454 | 455 | @cli.command() 456 | @click.argument('to', nargs=1) 457 | @click.argument('amount', nargs=1, required=False) 458 | @click.argument('token', nargs=1, required=False) 459 | @click.argument('memo', nargs=1, required=False) 460 | @click.option('--memos', '-m', help="Can be used when all tokens should be send") 461 | @click.option('--account', '-a', help='Transfer from this account') 462 | def transfer(to, amount, token, memo, memos, account): 463 | """Transfer a token""" 464 | stm = shared_blockchain_instance() 465 | if stm.rpc is not None: 466 | stm.rpc.rpcconnect() 467 | if not stm.is_hive: 468 | print("Please set a Hive node") 469 | return 470 | if not account: 471 | account = stm.config["default_account"] 472 | if not bool(memo): 473 | memo = '' 474 | if not bool(memos): 475 | memos = '' 476 | if not unlock_wallet(stm): 477 | return 478 | 479 | wallet = Wallet(account, blockchain_instance=stm) 480 | if amount is None and token is None: 481 | token = amount 482 | amount = 0 483 | tokens = Tokens() 484 | 485 | for t in wallet: 486 | token = t["symbol"] 487 | amount = float(t["balance"]) 488 | if amount == 0: 489 | continue 490 | token_obj = tokens.get_token(token) 491 | market_info = token_obj.get_market_info() 492 | if market_info is None: 493 | print("transfer %.8f %s to %s?" % (amount, token, to)) 494 | else: 495 | last_price = float(market_info["lastPrice"]) 496 | highest_bid = float(market_info["highestBid"]) 497 | if highest_bid == 0: 498 | price = last_price 499 | else: 500 | price = highest_bid 501 | hive_amount = price*amount 502 | print("transfer %.8f %s (value %.3f HIVE) to %s?" % (amount, token, hive_amount, to)) 503 | ret = input("continue [y/n]?") 504 | if ret not in ["y", "yes"]: 505 | continue 506 | tx = wallet.transfer(to, amount, token, memos) 507 | tx = json.dumps(tx, indent=4) 508 | print(tx) 509 | return 510 | elif token is None: 511 | token = amount 512 | amount = 0 513 | for t in wallet: 514 | if t["symbol"] == token: 515 | amount = float(t["balance"]) 516 | if amount == 0: 517 | print("Amount of %s is 0" % token) 518 | return 519 | tokens = Tokens() 520 | token_obj = tokens.get_token(token) 521 | market_info = token_obj.get_market_info() 522 | last_price = float(market_info["lastPrice"]) 523 | highest_bid = float(market_info["highestBid"]) 524 | if highest_bid == 0: 525 | price = last_price 526 | else: 527 | price = highest_bid 528 | hive_amount = price*amount 529 | print("transfer %.8f %s (value %.3f HIVE) to %s?" % (amount, token, hive_amount, to)) 530 | ret = input("continue [y/n]?") 531 | if ret not in ["y", "yes"]: 532 | return 533 | memo = memos 534 | tx = wallet.transfer(to, amount, token, memo) 535 | tx = json.dumps(tx, indent=4) 536 | print(tx) 537 | 538 | 539 | @cli.command() 540 | @click.argument('to', nargs=1) 541 | @click.argument('amount', nargs=1) 542 | @click.argument('token', nargs=1) 543 | @click.option('--account', '-a', help='Transfer from this account') 544 | def issue(to, amount, token, account): 545 | """Issue a token""" 546 | stm = shared_blockchain_instance() 547 | if stm.rpc is not None: 548 | stm.rpc.rpcconnect() 549 | if not stm.is_hive: 550 | print("Please set a Hive node") 551 | return 552 | if not account: 553 | account = stm.config["default_account"] 554 | if not unlock_wallet(stm): 555 | return 556 | wallet = Wallet(account, blockchain_instance=stm) 557 | tx = wallet.issue(to, amount, token) 558 | tx = json.dumps(tx, indent=4) 559 | print(tx) 560 | 561 | 562 | @cli.command() 563 | @click.argument('amount', nargs=1, required=False) 564 | @click.argument('token', nargs=1, required=False) 565 | @click.option('--account', '-a', help='Stake token from this account') 566 | @click.option('--receiver', '-r', help='Stake to this account (default is sender account)') 567 | def stake(amount, token, account, receiver): 568 | """stake a token / all tokens""" 569 | stm = shared_blockchain_instance() 570 | if stm.rpc is not None: 571 | stm.rpc.rpcconnect() 572 | if not stm.is_hive: 573 | print("Please set a Hive node") 574 | return 575 | if not account: 576 | account = stm.config["default_account"] 577 | if not receiver: 578 | receiver = account 579 | if not unlock_wallet(stm): 580 | return 581 | wallet = Wallet(account, blockchain_instance=stm) 582 | if amount is None and token is None: 583 | token = amount 584 | amount = 0 585 | tokens = Tokens() 586 | 587 | for t in wallet: 588 | token = t["symbol"] 589 | amount = float(t["balance"]) 590 | if amount == 0: 591 | continue 592 | token_obj = tokens.get_token(token) 593 | if not token_obj["stakingEnabled"]: 594 | continue 595 | market_info = token_obj.get_market_info() 596 | if market_info is None: 597 | print("stake %.8f %s?" % (amount, token)) 598 | else: 599 | last_price = float(market_info["lastPrice"]) 600 | highest_bid = float(market_info["highestBid"]) 601 | if highest_bid == 0: 602 | price = last_price 603 | else: 604 | price = highest_bid 605 | hive_amount = price*amount 606 | print("stake %.8f %s (value %.3f HIVE)?" % (amount, token, hive_amount)) 607 | ret = input("continue [y/n]?") 608 | if ret not in ["y", "yes"]: 609 | continue 610 | tx = wallet.stake(amount, token, receiver=receiver) 611 | tx = json.dumps(tx, indent=4) 612 | print(tx) 613 | return 614 | elif token is None: 615 | token = amount 616 | amount = 0 617 | for t in wallet: 618 | if t["symbol"] == token: 619 | amount = float(t["balance"]) 620 | if amount == 0: 621 | print("Amount of %s is 0" % token) 622 | return 623 | tokens = Tokens() 624 | token_obj = tokens.get_token(token) 625 | if not token_obj["stakingEnabled"]: 626 | print("%s is not stakable" % token) 627 | return 628 | market_info = token_obj.get_market_info() 629 | last_price = float(market_info["lastPrice"]) 630 | highest_bid = float(market_info["highestBid"]) 631 | if highest_bid == 0: 632 | price = last_price 633 | else: 634 | price = highest_bid 635 | hive_amount = price*amount 636 | print("stake %.8f %s (value %.3f HIVE)?" % (amount, token, hive_amount)) 637 | ret = input("continue [y/n]?") 638 | if ret not in ["y", "yes"]: 639 | return 640 | 641 | tx = wallet.stake(amount, token, receiver=receiver) 642 | tx = json.dumps(tx, indent=4) 643 | print(tx) 644 | 645 | 646 | @cli.command() 647 | @click.argument('amount', nargs=1, required=False) 648 | @click.argument('token', nargs=1, required=False) 649 | @click.option('--account', '-a', help='Transfer from this account') 650 | def unstake(amount, token, account): 651 | """unstake a token / all tokens""" 652 | stm = shared_blockchain_instance() 653 | if stm.rpc is not None: 654 | stm.rpc.rpcconnect() 655 | if not stm.is_hive: 656 | print("Please set a Hive node") 657 | return 658 | if not account: 659 | account = stm.config["default_account"] 660 | if not unlock_wallet(stm): 661 | return 662 | wallet = Wallet(account, blockchain_instance=stm) 663 | if amount is None and token is None: 664 | token = amount 665 | amount = 0 666 | tokens = Tokens() 667 | 668 | for t in wallet: 669 | token = t["symbol"] 670 | amount = float(t["balance"]) 671 | stake = float(t["stake"]) 672 | if stake == 0: 673 | continue 674 | token_obj = tokens.get_token(token) 675 | if not token_obj["stakingEnabled"]: 676 | continue 677 | market_info = token_obj.get_market_info() 678 | if market_info is None: 679 | print("unstake %.8f %s?" % (stake, token)) 680 | else: 681 | last_price = float(market_info["lastPrice"]) 682 | highest_bid = float(market_info["highestBid"]) 683 | if highest_bid == 0: 684 | price = last_price 685 | else: 686 | price = highest_bid 687 | hive_amount = price*stake 688 | print("unstake %.8f %s (value %.3f HIVE)?" % (stake, token, hive_amount)) 689 | ret = input("continue [y/n]?") 690 | if ret not in ["y", "yes"]: 691 | continue 692 | tx = wallet.unstake(stake, token) 693 | tx = json.dumps(tx, indent=4) 694 | print(tx) 695 | return 696 | elif token is None: 697 | token = amount 698 | amount = 0 699 | for t in wallet: 700 | if t["symbol"] == token: 701 | amount = float(t["stake"]) 702 | if amount == 0: 703 | print("Staked Amount of %s is 0" % token) 704 | return 705 | tokens = Tokens() 706 | token_obj = tokens.get_token(token) 707 | if not token_obj["stakingEnabled"]: 708 | print("%s is not stakable" % token) 709 | return 710 | market_info = token_obj.get_market_info() 711 | last_price = float(market_info["lastPrice"]) 712 | highest_bid = float(market_info["highestBid"]) 713 | if highest_bid == 0: 714 | price = last_price 715 | else: 716 | price = highest_bid 717 | hive_amount = price*amount 718 | print("unstake %.8f %s (value %.3f HIVE)?" % (amount, token, hive_amount)) 719 | ret = input("continue [y/n]?") 720 | if ret not in ["y", "yes"]: 721 | return 722 | 723 | tx = wallet.unstake(amount, token) 724 | tx = json.dumps(tx, indent=4) 725 | print(tx) 726 | 727 | 728 | @cli.command() 729 | @click.argument('trx_id', nargs=1) 730 | @click.option('--account', '-a', help='Transfer from this account') 731 | def cancel_unstake(account, trx_id): 732 | """unstake a token""" 733 | stm = shared_blockchain_instance() 734 | if stm.rpc is not None: 735 | stm.rpc.rpcconnect() 736 | if not stm.is_hive: 737 | print("Please set a Hive node") 738 | return 739 | if not account: 740 | account = stm.config["default_account"] 741 | if not unlock_wallet(stm): 742 | return 743 | wallet = Wallet(account, blockchain_instance=stm) 744 | tx = wallet.cancel_unstake(trx_id) 745 | tx = json.dumps(tx, indent=4) 746 | print(tx) 747 | 748 | 749 | @cli.command() 750 | @click.argument('amount', nargs=1) 751 | @click.option('--account', '-a', help='withdraw from this account') 752 | def withdraw(amount, account): 753 | """Widthdraw SWAP.HIVE to account as HIVE.""" 754 | stm = shared_blockchain_instance() 755 | if stm.rpc is not None: 756 | stm.rpc.rpcconnect() 757 | if not stm.is_hive: 758 | print("Please set a Hive node") 759 | return 760 | if not account: 761 | account = stm.config["default_account"] 762 | if not unlock_wallet(stm): 763 | return 764 | market = Market(blockchain_instance=stm) 765 | tx = market.withdraw(account, amount) 766 | tx = json.dumps(tx, indent=4) 767 | print(tx) 768 | 769 | 770 | @cli.command() 771 | @click.argument('amount', nargs=1) 772 | @click.option('--account', '-a', help='withdraw from this account') 773 | def deposit(amount, account): 774 | """Deposit HIVE to market in exchange for SWAP.HIVE.""" 775 | stm = shared_blockchain_instance() 776 | if stm.rpc is not None: 777 | stm.rpc.rpcconnect() 778 | if not stm.is_hive: 779 | print("Please set a Hive node") 780 | return 781 | if not account: 782 | account = stm.config["default_account"] 783 | if not unlock_wallet(stm): 784 | return 785 | market = Market(blockchain_instance=stm) 786 | tx = market.deposit(account, amount) 787 | tx = json.dumps(tx, indent=4) 788 | print(tx) 789 | 790 | 791 | @cli.command() 792 | @click.argument('amount', nargs=1) 793 | @click.argument('token', nargs=1) 794 | @click.argument('price', nargs=1) 795 | @click.option('--account', '-a', help='Buy with this account (defaults to "default_account")') 796 | def buy(amount, token, price, account): 797 | """Put a buy-order for a token to the hive-engine market 798 | 799 | """ 800 | stm = shared_blockchain_instance() 801 | if stm.rpc is not None: 802 | stm.rpc.rpcconnect() 803 | if not stm.is_hive: 804 | print("Please set a Hive node") 805 | return 806 | if account is None: 807 | account = stm.config["default_account"] 808 | market = Market(blockchain_instance=stm) 809 | if not unlock_wallet(stm): 810 | return 811 | tx = market.buy(account, amount, token, price) 812 | tx = json.dumps(tx, indent=4) 813 | print(tx) 814 | 815 | 816 | @cli.command() 817 | @click.argument('amount', nargs=1, required=False) 818 | @click.argument('token', nargs=1, required=False) 819 | @click.argument('price', nargs=1, required=False) 820 | @click.option('--account', '-a', help='Buy with this account (defaults to "default_account")') 821 | def sell(amount, token, price, account): 822 | """Put a sell-order for a token to the hive-engine market 823 | 824 | """ 825 | stm = shared_blockchain_instance() 826 | if stm.rpc is not None: 827 | stm.rpc.rpcconnect() 828 | if not stm.is_hive: 829 | print("Please set a Hive node") 830 | return 831 | if account is None: 832 | account = stm.config["default_account"] 833 | 834 | if amount is None and price is None and token is None: 835 | if not unlock_wallet(stm): 836 | return 837 | token = amount 838 | amount = 0 839 | tokens = Tokens() 840 | wallet = Wallet(account, blockchain_instance=stm) 841 | market = Market(blockchain_instance=stm) 842 | for t in wallet: 843 | token = t["symbol"] 844 | amount = float(t["balance"]) 845 | if amount == 0: 846 | continue 847 | token_obj = tokens.get_token(token) 848 | market_info = token_obj.get_market_info() 849 | if market_info is None: 850 | continue 851 | last_price = float(market_info["lastPrice"]) 852 | highest_bid = float(market_info["highestBid"]) 853 | if highest_bid == 0: 854 | price = last_price 855 | else: 856 | price = highest_bid 857 | hive_amount = price*amount 858 | if hive_amount < 0.001: 859 | continue 860 | print("%s: using %.8f as price to sell %.8f %s for %.8f HIVE" % (token, price, amount, token, hive_amount)) 861 | ret = input("continue [y/n]?") 862 | if ret not in ["y", "yes"]: 863 | continue 864 | tx = market.sell(account, amount, token, price) 865 | tx = json.dumps(tx, indent=4) 866 | print(tx) 867 | return 868 | 869 | elif price is None and token is None: 870 | token = amount 871 | amount = 0 872 | wallet = Wallet(account, blockchain_instance=stm) 873 | for t in wallet: 874 | if t["symbol"] == token: 875 | amount = float(t["balance"]) 876 | if amount == 0: 877 | print("Amount of %s is 0" % token) 878 | return 879 | tokens = Tokens() 880 | token_obj = tokens.get_token(token) 881 | market_info = token_obj.get_market_info() 882 | last_price = float(market_info["lastPrice"]) 883 | highest_bid = float(market_info["highestBid"]) 884 | if highest_bid == 0: 885 | price = last_price 886 | else: 887 | price = highest_bid 888 | hive_amount = price*amount 889 | print("using %.8f as price to sell %.8f %s for %.8f HIVE" % (price, amount, token, hive_amount)) 890 | ret = input("continue [y/n]?") 891 | if ret not in ["y", "yes"]: 892 | return 893 | 894 | 895 | 896 | market = Market(blockchain_instance=stm) 897 | if not unlock_wallet(stm): 898 | return 899 | tx = market.sell(account, amount, token, price) 900 | tx = json.dumps(tx, indent=4) 901 | print(tx) 902 | 903 | 904 | @cli.command() 905 | @click.option('--account', '-a', help='Buy with this account (defaults to "default_account")') 906 | def balance(account): 907 | """Show token balance and value 908 | 909 | """ 910 | stm = shared_blockchain_instance() 911 | if stm.rpc is not None: 912 | stm.rpc.rpcconnect() 913 | if not stm.is_hive: 914 | print("Please set a Hive node") 915 | return 916 | if account is None: 917 | account = stm.config["default_account"] 918 | token = "" 919 | amount = 0 920 | tokens = Tokens() 921 | wallet = Wallet(account, blockchain_instance=stm) 922 | table = PrettyTable(["symbol", "balance", "stake", "liquid HIVE", "staked HIVE"]) 923 | table.align = "l" 924 | sum_amount = 0 925 | sum_staked = 0 926 | for t in wallet: 927 | token = t["symbol"] 928 | amount = float(t["balance"]) 929 | stake = float(t["stake"]) 930 | token_obj = tokens.get_token(token) 931 | market_info = token_obj.get_market_info() 932 | if market_info is None: 933 | continue 934 | last_price = float(market_info["lastPrice"]) 935 | highest_bid = float(market_info["highestBid"]) 936 | if highest_bid == 0: 937 | price = last_price 938 | else: 939 | price = highest_bid 940 | balance_hive = price*amount 941 | stake_hive = price*stake 942 | sum_amount += balance_hive 943 | sum_staked += stake_hive 944 | if token_obj["stakingEnabled"]: 945 | table.add_row([token, amount, stake, "%.3f" % balance_hive, "%.3f" % stake_hive]) 946 | else: 947 | table.add_row([token, amount, stake, "%.3f" % balance_hive, "-"]) 948 | table.add_row(["-", "-", "-", "-", "-"]) 949 | table.add_row(["SUM", "", "", "%.3f" % sum_amount, "%.3f" % sum_staked]) 950 | print(table) 951 | 952 | 953 | @cli.command() 954 | @click.argument('order_type', nargs=1) 955 | @click.argument('order_id', nargs=1, required=False) 956 | @click.option('--account', '-a', help='Buy with this account (defaults to "default_account")') 957 | @click.option('--yes', '-y', help='Answer yes to all questions', is_flag=True, default=False) 958 | def cancel(order_type, order_id, account, yes): 959 | """Cancel a buy/sell order 960 | 961 | order_type is either sell or buy 962 | """ 963 | stm = shared_blockchain_instance() 964 | if stm.rpc is not None: 965 | stm.rpc.rpcconnect() 966 | if not stm.is_hive: 967 | print("Please set a Hive node") 968 | return 969 | if account is None: 970 | account = stm.config["default_account"] 971 | market = Market(blockchain_instance=stm) 972 | if not unlock_wallet(stm): 973 | return 974 | if order_id is None and order_type == "buy": 975 | wallet = Wallet(account, blockchain_instance=stm) 976 | for t in wallet: 977 | token = t["symbol"] 978 | buy_book = market.get_buy_book(token, account) 979 | sorted_buy_book = sorted(buy_book, key=lambda account: float(account["price"]), reverse=True) 980 | for order in sorted_buy_book: 981 | print("Cancel sell order with id: %s for %.8f %s at %s" % (order["_id"], float(order["quantity"]), token, float(order["price"]))) 982 | if not yes: 983 | ret = input("continue [y/n]?") 984 | if ret not in ["y", "yes"]: 985 | continue 986 | tx = market.cancel(account, order_type, int(order_id)) 987 | tx = json.dumps(tx, indent=4) 988 | print(tx) 989 | if yes: 990 | time.sleep(4) 991 | return 992 | elif order_id is None and order_type == "sell": 993 | wallet = Wallet(account, blockchain_instance=stm) 994 | for t in wallet: 995 | token = t["symbol"] 996 | sell_book = market.get_sell_book(token, account) 997 | sorted_sell_book = sorted(sell_book, key=lambda account: float(account["price"]), reverse=True) 998 | for order in sorted_sell_book: 999 | print("Cancel sell order with id: %s for %.8f %s at %s" % (order["_id"], float(order["quantity"]), token, float(order["price"]))) 1000 | if not yes: 1001 | ret = input("continue [y/n]?") 1002 | if ret not in ["y", "yes"]: 1003 | continue 1004 | tx = market.cancel(account, order_type, int(order_id)) 1005 | tx = json.dumps(tx, indent=4) 1006 | print(tx) 1007 | if yes: 1008 | time.sleep(4) 1009 | return 1010 | tx = market.cancel(account, order_type, int(order_id)) 1011 | tx = json.dumps(tx, indent=4) 1012 | print(tx) 1013 | 1014 | 1015 | @cli.command() 1016 | @click.argument('token', nargs=1, required=False) 1017 | @click.option('--account', '-a', help='Buy with this account (defaults to "default_account")') 1018 | def buybook(token, account): 1019 | """Returns the buy book for the given token 1020 | 1021 | """ 1022 | stm = shared_blockchain_instance() 1023 | if stm.rpc is not None: 1024 | stm.rpc.rpcconnect() 1025 | if not stm.is_hive: 1026 | print("Please set a Hive node") 1027 | return 1028 | market = Market(blockchain_instance=stm) 1029 | if token is None: 1030 | if account is None: 1031 | account = stm.config["default_account"] 1032 | wallet = Wallet(account, blockchain_instance=stm) 1033 | table = PrettyTable(["token", "order_id", "account", "quantity", "price"]) 1034 | table.align = "l" 1035 | for t in wallet: 1036 | token = t["symbol"] 1037 | buy_book = market.get_buy_book(token, account) 1038 | sorted_buy_book = sorted(buy_book, key=lambda account: float(account["price"]), reverse=True) 1039 | for order in sorted_buy_book: 1040 | table.add_row([token, order["_id"], order["account"], order["quantity"], order["price"]]) 1041 | print(table.get_string()) 1042 | else: 1043 | buy_book = market.get_buy_book(token, account) 1044 | sorted_buy_book = sorted(buy_book, key=lambda account: float(account["price"]), reverse=True) 1045 | t = PrettyTable(["order_id", "account", "quantity", "price"]) 1046 | t.align = "l" 1047 | for order in sorted_buy_book: 1048 | t.add_row([order["_id"], order["account"], order["quantity"], order["price"]]) 1049 | print(t.get_string()) 1050 | 1051 | 1052 | @cli.command() 1053 | @click.argument('token', nargs=1, required=False) 1054 | @click.option('--account', '-a', help='Buy with this account (defaults to "default_account")') 1055 | def sellbook(token, account): 1056 | """Returns the sell book for the given token 1057 | """ 1058 | stm = shared_blockchain_instance() 1059 | if stm.rpc is not None: 1060 | stm.rpc.rpcconnect() 1061 | if not stm.is_hive: 1062 | print("Please set a Hive node") 1063 | return 1064 | market = Market(blockchain_instance=stm) 1065 | if token is None: 1066 | if account is None: 1067 | account = stm.config["default_account"] 1068 | wallet = Wallet(account, blockchain_instance=stm) 1069 | table = PrettyTable(["token", "order_id", "account", "quantity", "price"]) 1070 | table.align = "l" 1071 | for t in wallet: 1072 | token = t["symbol"] 1073 | sell_book = market.get_sell_book(token, account) 1074 | sorted_sell_book = sorted(sell_book, key=lambda account: float(account["price"]), reverse=False) 1075 | for order in sorted_sell_book: 1076 | table.add_row([token, order["_id"], order["account"], order["quantity"], order["price"]]) 1077 | print(table.get_string()) 1078 | else: 1079 | sell_book = market.get_sell_book(token, account) 1080 | sorted_sell_book = sorted(sell_book, key=lambda account: float(account["price"]), reverse=False) 1081 | t = PrettyTable(["order_id", "account", "quantity", "price"]) 1082 | t.align = "l" 1083 | for order in sorted_sell_book: 1084 | t.add_row([order["_id"], order["account"], order["quantity"], order["price"]]) 1085 | print(t.get_string()) 1086 | 1087 | 1088 | @cli.command() 1089 | @click.argument('symbol', nargs=1) 1090 | @click.option('--account', '-a', help='Buy with this account (defaults to "default_account")') 1091 | @click.option('--grouping', '-g', help='Can be set to a grouping parameter, or to parameter.value') 1092 | @click.option('--value', '-v', help='Set property value, can be used when grouping is set to a property parameter') 1093 | @click.option('--price-symbol', '-s', help='Limit to this price symbol') 1094 | @click.option('--nft-id', '-n', help='Limit to this nft id') 1095 | @click.option('--cheapest-only', '-c', help='Show only the cheapest open sell for each type', is_flag=True, default=False) 1096 | @click.option('--min-hive', '-m', help='Show only NFT which have a higher price') 1097 | @click.option('--limit', '-l', help='Limit to shown entries') 1098 | @click.option('--interactive', '-i', help='Show only the cheapest open sell for each type', is_flag=True, default=False) 1099 | def nftsellbook(symbol, account, grouping, value, price_symbol, nft_id, cheapest_only, min_hive, limit, interactive): 1100 | """Returns the sell book for the given symbol 1101 | """ 1102 | stm = shared_blockchain_instance() 1103 | if stm.rpc is not None: 1104 | stm.rpc.rpcconnect() 1105 | if not stm.is_hive: 1106 | print("Please set a Hive node") 1107 | return 1108 | market = NftMarket(blockchain_instance=stm) 1109 | nft = Nft(symbol) 1110 | grouping_name = None 1111 | grouping_value = None 1112 | if value is not None and grouping is not None: 1113 | grouping_name = grouping 1114 | grouping_value = value 1115 | elif grouping is not None and len(grouping.split(".")) >= 2: 1116 | grouping_name = grouping.split(".")[0] 1117 | grouping_value = grouping.split(".")[1] 1118 | sell_book = market.get_sell_book(symbol, account, grouping_name, grouping_value, price_symbol, nft_id) 1119 | new_sell_book = [] 1120 | market_info = {} 1121 | 1122 | for order in sell_book: 1123 | if order["priceSymbol"] != "SWAP.HIVE": 1124 | if order["priceSymbol"] not in market_info: 1125 | token = Token(order["priceSymbol"]) 1126 | market_info[order["priceSymbol"]] = token.get_market_info() 1127 | hive_price = float(market_info[order["priceSymbol"]]["lastPrice"]) * float(order["price"]) 1128 | order["hive_price"] = hive_price 1129 | else: 1130 | order["hive_price"] = float(order["price"]) 1131 | if min_hive is not None and order["hive_price"] < float(min_hive): 1132 | continue 1133 | new_sell_book.append(order) 1134 | sorted_sell_book = sorted(new_sell_book, key=lambda account: float(account["hive_price"]), reverse=False) 1135 | if limit is not None: 1136 | sorted_sell_book = sorted_sell_book[:int(limit)] 1137 | name_str = "" 1138 | for g in nft["groupBy"]: 1139 | name_str += g + " - " 1140 | t = PrettyTable(["nftId", "account", name_str[:-3], "price", "priceSymbol", "fee", "est. HIVE"]) 1141 | t.align = "l" 1142 | t._max_width = {name_str[:-3] : 40} 1143 | market_info = {} 1144 | cheapest_list = [] 1145 | nft_ids = [] 1146 | for order in sorted_sell_book: 1147 | hive_price = round(float(order["hive_price"]), 3) 1148 | if grouping in nft.properties and value is None: 1149 | nftId = nft.get_id(int(order["nftId"])) 1150 | nft_name = nftId["properties"][grouping] 1151 | # print(nftId) 1152 | elif grouping in nft.properties and value is not None: 1153 | nft_name = value 1154 | elif grouping_value is not None: 1155 | nft_name = grouping_value 1156 | else: 1157 | nftId = nft.get_id(int(order["nftId"])) 1158 | nft_name = "" 1159 | for g in nft["groupBy"]: 1160 | if g in nftId["properties"]: 1161 | nft_name += nftId["properties"][g] + " - " 1162 | if len(nft_name) > 0: 1163 | nft_name = nft_name[:-3] 1164 | if cheapest_only: 1165 | if nft_name in cheapest_list: 1166 | continue 1167 | cheapest_list.append(nft_name) 1168 | nft_ids.append(order["nftId"]) 1169 | t.add_row([order["nftId"], order["account"], nft_name, order["price"], order["priceSymbol"], "%.2f %%" % (order["fee"]/100), hive_price]) 1170 | print(t.get_string()) 1171 | if interactive: 1172 | ret = None 1173 | while ret not in ["b", "c", "p", "a", "buy", "cancel", "change price", "about"]: 1174 | ret = input("Do the following: buy[b], cancel[c], change price[p] or abort[a]? ") 1175 | if ret in ["a", "abort"]: 1176 | return 1177 | account = input("Enter account name: ") 1178 | market = NftMarket(blockchain_instance=stm) 1179 | if not unlock_wallet(stm): 1180 | return 1181 | if ret in ["p", "change price"]: 1182 | newprice = input("Enter new price: ") 1183 | tx = market.change_price(symbol, account, list(nft_ids), newprice) 1184 | tx = json.dumps(tx, indent=4) 1185 | print(tx) 1186 | elif ret in ["c", "cancel"]: 1187 | tx = market.cancel(symbol, account, list(nft_ids)) 1188 | tx = json.dumps(tx, indent=4) 1189 | print(tx) 1190 | elif ret in ["b", "buy"]: 1191 | tx = market.buy(symbol, account, list(nft_ids), "nftmarket") 1192 | tx = json.dumps(tx, indent=4) 1193 | print(tx) 1194 | 1195 | 1196 | @cli.command() 1197 | @click.argument('symbol', nargs=1) 1198 | @click.option('--grouping', '-g', help='Can be set to a grouping parameter, or to parameter.value') 1199 | @click.option('--value', '-v', help='Set property value, can be used when grouping is set to a property parameter') 1200 | @click.option('--price-symbol', '-s', help='Limit to this price symbol') 1201 | def nftopen(symbol, grouping, value, price_symbol): 1202 | """Returns the open interest book for the given symbol 1203 | """ 1204 | stm = shared_blockchain_instance() 1205 | if stm.rpc is not None: 1206 | stm.rpc.rpcconnect() 1207 | if not stm.is_hive: 1208 | print("Please set a Hive node") 1209 | return 1210 | market = NftMarket(blockchain_instance=stm) 1211 | nft = Nft(symbol) 1212 | grouping_name = None 1213 | grouping_value = None 1214 | if value is not None and grouping is not None: 1215 | grouping_name = grouping 1216 | grouping_value = value 1217 | elif grouping is not None and len(grouping.split(".")) >= 2: 1218 | grouping_name = grouping.split(".")[0] 1219 | grouping_value = grouping.split(".")[1] 1220 | open_interest = market.get_open_interest(symbol, "sell", grouping_name, grouping_value, price_symbol) 1221 | 1222 | t = PrettyTable(["priceSymbol", "grouping", "count"]) 1223 | t.align = "l" 1224 | for order in open_interest: 1225 | t.add_row([order["priceSymbol"], order["grouping"], order["count"]]) 1226 | print(t.get_string()) 1227 | 1228 | @cli.command() 1229 | @click.argument('symbol', nargs=1, required=False) 1230 | @click.option('--account', '-a', help='Buy with this account (defaults to "default_account")') 1231 | def nfttrades(symbol, account): 1232 | """Returns the trades history 1233 | """ 1234 | stm = shared_blockchain_instance() 1235 | if stm.rpc is not None: 1236 | stm.rpc.rpcconnect() 1237 | if not stm.is_hive: 1238 | print("Please set a Hive node") 1239 | return 1240 | market = NftMarket(blockchain_instance=stm) 1241 | if symbol is not None: 1242 | 1243 | nft = Nft(symbol) 1244 | trades_history = market.get_trades_history(symbol, account) 1245 | new_trades_history = [] 1246 | market_info = {} 1247 | for order in trades_history: 1248 | if order["priceSymbol"] != "SWAP.HIVE": 1249 | if order["priceSymbol"] not in market_info: 1250 | token = Token(order["priceSymbol"]) 1251 | market_info[order["priceSymbol"]] = token.get_market_info() 1252 | hive_price = float(market_info[order["priceSymbol"]]["lastPrice"]) * float(order["price"]) 1253 | order["hive_price"] = hive_price 1254 | else: 1255 | order["hive_price"] = float(order["price"]) 1256 | new_trades_history.append(order) 1257 | 1258 | t = PrettyTable(["date", "account", "from", "Nfts", "price", "priceSymbol", "est. HIVE"]) 1259 | t.align = "l" 1260 | t._max_width = {"from": 16, "Nfts": 20} 1261 | row_sum = ["Sum: ", "", "", 0, "", "", 0] 1262 | for order in new_trades_history: 1263 | hive_price = round(float(order["hive_price"]), 3) 1264 | date = datetime.fromtimestamp(order["timestamp"]) 1265 | seller = [] 1266 | nfts = [] 1267 | for s in order["counterparties"]: 1268 | seller.append(s["account"]) 1269 | for n in s["nftIds"]: 1270 | nfts.append(n) 1271 | row_sum[3] += 1 1272 | row_sum[6] += hive_price 1273 | t.add_row([date, order["account"], ','.join(seller), ','.join(nfts), order["price"], order["priceSymbol"], hive_price]) 1274 | print(t.get_string()) 1275 | print("%d Nfts were sold for %.3f HIVE in the last 24 h" % (row_sum[3], row_sum[6])) 1276 | else: 1277 | nfts = Nfts() 1278 | symbol = nfts.get_symbol_list() 1279 | market_info = {} 1280 | t = PrettyTable(["Symbol", "Sold NFTs", "est. HIVE sum"]) 1281 | t.align = "l" 1282 | for s in symbol: 1283 | nft = Nft(s) 1284 | trades_history = market.get_trades_history(s, account) 1285 | new_trades_history = [] 1286 | hive_sum = 0 1287 | nft_sum = 0 1288 | for order in trades_history: 1289 | if order["priceSymbol"] != "SWAP.HIVE": 1290 | if order["priceSymbol"] not in market_info: 1291 | token = Token(order["priceSymbol"]) 1292 | market_info[order["priceSymbol"]] = token.get_market_info() 1293 | hive_price = float(market_info[order["priceSymbol"]]["lastPrice"]) * float(order["price"]) 1294 | order["hive_price"] = hive_price 1295 | else: 1296 | order["hive_price"] = float(order["price"]) 1297 | new_trades_history.append(order) 1298 | for order in new_trades_history: 1299 | hive_price = round(float(order["hive_price"]), 3) 1300 | hive_sum += hive_price 1301 | for c in order["counterparties"]: 1302 | for n in c["nftIds"]: 1303 | nft_sum += 1 1304 | t.add_row([s, nft_sum, round(hive_sum, 3)]) 1305 | print(t.get_string(sortby=("est. HIVE sum"), reversesort=True)) 1306 | 1307 | 1308 | @cli.command() 1309 | @click.argument('symbol', nargs=1) 1310 | @click.argument('nft_ids', nargs=-1) 1311 | @click.option('--account', '-a', help='Buy with this account (defaults to "default_account")') 1312 | @click.option('--market_account', '-m', help='Market account which will receive the fee (defaults to "nftmarket")', default="nftmarket") 1313 | @click.option('--yes', '-y', help='Answer yes to all questions', is_flag=True, default=False) 1314 | def nftbuy(symbol, nft_ids, account, market_account, yes): 1315 | """Buy nfts from the market 1316 | 1317 | """ 1318 | stm = shared_blockchain_instance() 1319 | if stm.rpc is not None: 1320 | stm.rpc.rpcconnect() 1321 | if not stm.is_hive: 1322 | print("Please set a Hive node") 1323 | return 1324 | if account is None: 1325 | account = stm.config["default_account"] 1326 | market = NftMarket(blockchain_instance=stm) 1327 | if not unlock_wallet(stm): 1328 | return 1329 | t = PrettyTable(["nftId", "previousAccount", "properties"]) 1330 | t.align = "l" 1331 | nft = Nft(symbol) 1332 | for _id in nft_ids: 1333 | obj = nft.get_id(int(_id)) 1334 | t.add_row([obj["_id"], obj["previousAccount"], obj["properties"]]) 1335 | print("Buy the following nfts (buy is only sucesfully when wallet balance is sufficient):") 1336 | print(t) 1337 | if not yes: 1338 | ret = input("continue [y/n]?") 1339 | if ret not in ["y", "yes"]: 1340 | return 1341 | tx = market.buy(symbol, account, list(nft_ids), market_account) 1342 | tx = json.dumps(tx, indent=4) 1343 | print(tx) 1344 | 1345 | 1346 | @cli.command() 1347 | @click.argument('symbol', nargs=1) 1348 | @click.argument('nft_ids', nargs=-1) 1349 | @click.argument('price', nargs=1) 1350 | @click.argument('price_symbol', nargs=1) 1351 | @click.option('--account', '-a', help='Buy with this account (uses the beem default account when not set)') 1352 | @click.option('--fee', '-f', help='Market fee 500 -> 5% (defaults is 500)', default=500) 1353 | @click.option('--yes', '-y', help='Answer yes to all questions', is_flag=True, default=False) 1354 | def nftsell(symbol, nft_ids, price, price_symbol, account, fee, yes): 1355 | """Create a sell order on the market 1356 | """ 1357 | stm = shared_blockchain_instance() 1358 | if stm.rpc is not None: 1359 | stm.rpc.rpcconnect() 1360 | if not stm.is_hive: 1361 | print("Please set a Hive node") 1362 | return 1363 | if account is None: 1364 | account = stm.config["default_account"] 1365 | market = NftMarket(blockchain_instance=stm) 1366 | if not unlock_wallet(stm): 1367 | return 1368 | t = PrettyTable(["nftId", "account", "properties"]) 1369 | t.align = "l" 1370 | nft = Nft(symbol) 1371 | for _id in nft_ids: 1372 | obj = nft.get_id(int(_id)) 1373 | t.add_row([obj["_id"], obj["account"], obj["properties"]]) 1374 | print("Create a sell order for %.3f %s with a %.2f %% fee on:" % (float(price), price_symbol, fee/100)) 1375 | print(t) 1376 | if not yes: 1377 | ret = input("continue [y/n]?") 1378 | if ret not in ["y", "yes"]: 1379 | return 1380 | tx = market.sell(symbol, account, list(nft_ids), price, price_symbol, fee) 1381 | tx = json.dumps(tx, indent=4) 1382 | print(tx) 1383 | 1384 | 1385 | @cli.command() 1386 | @click.argument('symbol', nargs=1) 1387 | @click.argument('nft_ids', nargs=-1) 1388 | @click.argument('newprice', nargs=1) 1389 | @click.option('--account', '-a', help='Buy with this account (defaults to "default_account")') 1390 | @click.option('--yes', '-y', help='Answer yes to all questions', is_flag=True, default=False) 1391 | def nftchangeprice(symbol, nft_ids, newprice, account, yes): 1392 | """Cancel a nft sell order 1393 | 1394 | """ 1395 | stm = shared_blockchain_instance() 1396 | if stm.rpc is not None: 1397 | stm.rpc.rpcconnect() 1398 | if not stm.is_hive: 1399 | print("Please set a Hive node") 1400 | return 1401 | if account is None: 1402 | account = stm.config["default_account"] 1403 | market = NftMarket(blockchain_instance=stm) 1404 | if not unlock_wallet(stm): 1405 | return 1406 | t = PrettyTable(["nftId", "previousAccount", "properties"]) 1407 | t.align = "l" 1408 | nft = Nft(symbol) 1409 | for _id in nft_ids: 1410 | obj = nft.get_id(int(_id)) 1411 | t.add_row([obj["_id"], obj["previousAccount"], obj["properties"]]) 1412 | print("Change price of following nfts to %s:" % newprice) 1413 | print(t) 1414 | if not yes: 1415 | ret = input("continue [y/n]?") 1416 | if ret not in ["y", "yes"]: 1417 | return 1418 | tx = market.change_price(symbol, account, list(nft_ids), newprice) 1419 | tx = json.dumps(tx, indent=4) 1420 | print(tx) 1421 | 1422 | 1423 | @cli.command() 1424 | @click.argument('symbol', nargs=1) 1425 | @click.argument('nft_ids', nargs=-1) 1426 | @click.option('--account', '-a', help='Buy with this account (defaults to "default_account")') 1427 | @click.option('--yes', '-y', help='Answer yes to all questions', is_flag=True, default=False) 1428 | def nftcancel(symbol, nft_ids, account, yes): 1429 | """Cancel a nft sell order 1430 | 1431 | """ 1432 | stm = shared_blockchain_instance() 1433 | if stm.rpc is not None: 1434 | stm.rpc.rpcconnect() 1435 | if not stm.is_hive: 1436 | print("Please set a Hive node") 1437 | return 1438 | if account is None: 1439 | account = stm.config["default_account"] 1440 | market = NftMarket(blockchain_instance=stm) 1441 | if not unlock_wallet(stm): 1442 | return 1443 | t = PrettyTable(["nftId", "previousAccount", "properties"]) 1444 | t.align = "l" 1445 | nft = Nft(symbol) 1446 | for _id in nft_ids: 1447 | obj = nft.get_id(int(_id)) 1448 | t.add_row([obj["_id"], obj["previousAccount"], obj["properties"]]) 1449 | print("Canceling selling of following nfts:") 1450 | print(t) 1451 | if not yes: 1452 | ret = input("continue [y/n]?") 1453 | if ret not in ["y", "yes"]: 1454 | return 1455 | tx = market.cancel(symbol, account, list(nft_ids)) 1456 | tx = json.dumps(tx, indent=4) 1457 | print(tx) 1458 | 1459 | 1460 | if __name__ == "__main__": 1461 | if getattr(sys, 'frozen', False): 1462 | os.environ['SSL_CERT_FILE'] = os.path.join(sys._MEIPASS, 'lib', 'cert.pem') 1463 | cli(sys.argv[1:]) 1464 | else: 1465 | cli() 1466 | -------------------------------------------------------------------------------- /hiveengine/collection.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | from __future__ import division 3 | from __future__ import print_function 4 | from __future__ import unicode_literals 5 | import sys 6 | from datetime import datetime, timedelta, date 7 | import time 8 | import io 9 | import json 10 | import requests 11 | from timeit import default_timer as timer 12 | import logging 13 | import decimal 14 | from hiveengine.api import Api 15 | from hiveengine.nft import Nft 16 | from hiveengine.nfts import Nfts 17 | from hiveengine.exceptions import (TokenDoesNotExists, TokenNotInWallet, InsufficientTokenAmount, TokenIssueNotPermitted, MaxSupplyReached, InvalidTokenAmount) 18 | from beem.instance import shared_blockchain_instance 19 | from beem.account import Account 20 | 21 | 22 | class Collection(dict): 23 | """ Access the hive-engine NFT collection 24 | 25 | :param str account: Name of the account 26 | :param Hive blockchain_instance: Hive 27 | instance 28 | 29 | Wallet example: 30 | 31 | .. code-block:: python 32 | 33 | from hiveengine.collection import Collection 34 | collection = Collection("test") 35 | print(collection) 36 | 37 | """ 38 | def __init__(self, account, api=None, blockchain_instance=None, steem_instance=None): 39 | if api is None: 40 | self.api = Api() 41 | else: 42 | self.api = api 43 | self.ssc_id = "ssc-mainnet-hive" 44 | self.blockchain = blockchain_instance or steem_instance or shared_blockchain_instance() 45 | check_account = Account(account, blockchain_instance=self.blockchain) 46 | self.account = check_account["name"] 47 | self.nfts = Nfts() 48 | self.refresh() 49 | 50 | def refresh(self): 51 | super(Collection, self).__init__(self.get_collection()) 52 | 53 | def set_id(self, ssc_id): 54 | """Sets the ssc id (default is ssc-mainnet-hive)""" 55 | self.ssc_id = ssc_id 56 | 57 | def get_collection(self): 58 | """Returns all token within the wallet as list""" 59 | collection = {} 60 | for symbol in self.nfts.get_symbol_list(): 61 | nft = Nft(symbol) 62 | tokenlist = nft.get_collection(self.account) 63 | if len(tokenlist) > 0: 64 | collection[symbol] = tokenlist 65 | return collection 66 | 67 | def change_account(self, account): 68 | """Changes the wallet account""" 69 | check_account = Account(account, blockchain_instance=self.blockchain) 70 | self.account = check_account["name"] 71 | self.refresh() 72 | 73 | def get_nft(self, nft_id, symbol): 74 | """Returns a token from the wallet. Is None when not available.""" 75 | for token in self[symbol]: 76 | if token["_id"].lower() == nft_id: 77 | return token 78 | return None 79 | 80 | def transfer(self, to, nfts, from_type="user", to_type="user"): 81 | """Transfer a token to another account. 82 | 83 | :param str to: Recipient 84 | :param list nfts: Amount to transfer 85 | :param str from_type: (optional) user / contract 86 | :param str to_type: (optional) user / contract 87 | 88 | 89 | Transfer example: 90 | 91 | .. code-block:: python 92 | 93 | from hiveengine.collection import Collection 94 | from beem import Hive 95 | active_wif = "5xxxx" 96 | hive = Hive(keys=[active_wif]) 97 | collection = Collection("test", blockchain_instance=hive) 98 | nfts = [{"symbol": "STAR", "ids": ["100"]}] 99 | collection.transfer("test2", nfts) 100 | """ 101 | assert from_type in ["user", "contract"] 102 | assert to_type in ["user", "contract"] 103 | assert len(nfts) > 0 104 | contract_payload = {"to":to, "nfts": nfts} 105 | if from_type == "contract": 106 | contract_payload["fromType"] = from_type 107 | if to_type == "contract": 108 | contract_payload["toType"] = to_type 109 | json_data = {"contractName": "nft","contractAction": "transfer", 110 | "contractPayload": contract_payload} 111 | assert self.blockchain.is_hive 112 | tx = self.blockchain.custom_json(self.ssc_id, json_data, required_auths=[self.account]) 113 | return tx 114 | 115 | def burn(self, nfts): 116 | """Burn a token 117 | 118 | :param list nfts: Amount to transfer 119 | 120 | Transfer example: 121 | 122 | .. code-block:: python 123 | 124 | from hiveengine.collection import Collection 125 | from beem import Hive 126 | active_wif = "5xxxx" 127 | hive = Hive(keys=[active_wif]) 128 | collection = Collection("test", blockchain_instance=hive) 129 | nfts = [{"symbol": "STAR", "ids": ["100"]}] 130 | collection.burn(nfts) 131 | """ 132 | assert len(nfts) > 0 133 | contract_payload = {"nfts": nfts} 134 | json_data = {"contractName": "nft","contractAction": "burn", 135 | "contractPayload": contract_payload} 136 | assert self.blockchain.is_hive 137 | tx = self.blockchain.custom_json(self.ssc_id, json_data, required_auths=[self.account]) 138 | return tx 139 | 140 | def delegate(self, to, nfts, from_type="user", to_type="user"): 141 | """Delegate a token to another account. 142 | 143 | :param str to: Recipient 144 | :param list nfts: Amount to transfer 145 | :param str from_type: (optional) user / contract 146 | :param str to_type: (optional) user / contract 147 | 148 | Transfer example: 149 | 150 | .. code-block:: python 151 | 152 | from hiveengine.collection import Collection 153 | from beem import Hive 154 | active_wif = "5xxxx" 155 | hive = Hive(keys=[active_wif]) 156 | collection = Collection("test", blockchain_instance=hive) 157 | nfts = [{"symbol": "STAR", "ids": ["100"]}] 158 | collection.delegate("test2", nfts) 159 | """ 160 | assert len(nfts) > 0 161 | assert from_type in ["user", "contract"] 162 | assert to_type in ["user", "contract"] 163 | contract_payload = {"to":to, "nfts": nfts} 164 | if from_type == "contract": 165 | contract_payload["fromType"] = from_type 166 | if to_type == "contract": 167 | contract_payload["toType"] = to_type 168 | json_data = {"contractName": "nft","contractAction": "delegate", 169 | "contractPayload": contract_payload} 170 | assert self.blockchain.is_hive 171 | tx = self.blockchain.custom_json(self.ssc_id, json_data, required_auths=[self.account]) 172 | return tx 173 | 174 | def undelegate(self, to, nfts, from_type="user"): 175 | """Undelegate a token to another account. 176 | 177 | :param str to: Recipient 178 | :param list nfts: Amount to transfer 179 | :param str from_type: (optional) user / contract 180 | 181 | Transfer example: 182 | 183 | .. code-block:: python 184 | 185 | from hiveengine.collection import Collection 186 | from beem import Hive 187 | active_wif = "5xxxx" 188 | hive = Hive(keys=[active_wif]) 189 | collection = Collection("test", blockchain_instance=hive) 190 | nfts = [{"symbol": "STAR", "ids": ["100"]}] 191 | collection.undelegate("test2", nfts) 192 | """ 193 | assert len(nfts) > 0 194 | assert from_type in ["user", "contract"] 195 | contract_payload = {"nfts": nfts} 196 | if from_type == "contract": 197 | contract_payload["fromType"] = from_type 198 | json_data = {"contractName": "nft","contractAction": "undelegate", 199 | "contractPayload": contract_payload} 200 | assert self.blockchain.is_hive 201 | tx = self.blockchain.custom_json(self.ssc_id, json_data, required_auths=[self.account]) 202 | return tx -------------------------------------------------------------------------------- /hiveengine/exceptions.py: -------------------------------------------------------------------------------- 1 | # This Python file uses the following encoding: utf-8 2 | from __future__ import absolute_import 3 | from __future__ import division 4 | from __future__ import print_function 5 | from __future__ import unicode_literals 6 | 7 | 8 | class TokenDoesNotExists(Exception): 9 | """ Token does not (yet) exists 10 | """ 11 | pass 12 | 13 | 14 | class NftDoesNotExists(Exception): 15 | """ Nft does not (yet) exists 16 | """ 17 | pass 18 | 19 | 20 | class TokenNotInWallet(Exception): 21 | """ The token is not in the account wallet 22 | """ 23 | pass 24 | 25 | 26 | class InsufficientTokenAmount(Exception): 27 | """ Not suffienct amount for transfer in the wallet 28 | """ 29 | pass 30 | 31 | 32 | class InvalidTokenAmount(Exception): 33 | """ Invalid token amount (not fitting precision or max supply) 34 | """ 35 | pass 36 | 37 | 38 | class TokenIssueNotPermitted(Exception): 39 | """ Only the token issuer is allowed to permit new tokens 40 | """ 41 | pass 42 | 43 | 44 | class MaxSupplyReached(Exception): 45 | """ Only the token issuer is allowed to permit new tokens 46 | """ 47 | pass 48 | -------------------------------------------------------------------------------- /hiveengine/market.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | from __future__ import division 3 | from __future__ import print_function 4 | from __future__ import unicode_literals 5 | import sys 6 | from datetime import datetime, timedelta, date 7 | import time 8 | import json 9 | from timeit import default_timer as timer 10 | import logging 11 | import decimal 12 | from hiveengine.api import Api 13 | from hiveengine.tokens import Tokens 14 | from hiveengine.tokenobject import Token 15 | from hiveengine.wallet import Wallet 16 | from hiveengine.exceptions import (TokenDoesNotExists, TokenNotInWallet, InsufficientTokenAmount, InvalidTokenAmount) 17 | from beem.instance import shared_blockchain_instance 18 | from beem.account import Account 19 | 20 | 21 | class Market(list): 22 | """ Access the hive-engine market 23 | 24 | :param Hive blockchain_instance: Hive 25 | instance 26 | """ 27 | def __init__(self, api=None, blockchain_instance=None, steem_instance=None): 28 | if api is None: 29 | self.api = Api() 30 | else: 31 | self.api = api 32 | self.blockchain = blockchain_instance or steem_instance or shared_blockchain_instance() 33 | self.tokens = Tokens(api=self.api) 34 | self.ssc_id = "ssc-mainnet-hive" 35 | self.refresh() 36 | 37 | def refresh(self): 38 | super(Market, self).__init__(self.get_metrics()) 39 | 40 | def set_id(self, ssc_id): 41 | """Sets the ssc id (default is ssc-mainnet-hive)""" 42 | self.ssc_id = ssc_id 43 | 44 | def get_metrics(self): 45 | """Returns all token within the wallet as list""" 46 | metrics = self.api.find("market", "metrics", query={}) 47 | return metrics 48 | 49 | def get_buy_book(self, symbol, account=None, limit=100, offset=0): 50 | """Returns the buy book for a given symbol. When account is set, 51 | the order book from the given account is shown. 52 | """ 53 | if self.tokens.get_token(symbol) is None: 54 | raise TokenDoesNotExists("%s does not exists" % symbol) 55 | if account is None: 56 | buy_book = self.api.find("market", "buyBook", query={"symbol": symbol.upper()}, limit=limit, offset=offset) 57 | else: 58 | buy_book = self.api.find("market", "buyBook", query={"symbol": symbol.upper(), "account": account}, limit=limit, offset=offset) 59 | return buy_book 60 | 61 | def get_sell_book(self, symbol, account=None, limit=100, offset=0): 62 | """Returns the sell book for a given symbol. When account is set, 63 | the order book from the given account is shown. 64 | """ 65 | if self.tokens.get_token(symbol) is None: 66 | raise TokenDoesNotExists("%s does not exists" % symbol) 67 | if account is None: 68 | sell_book = self.api.find("market", "sellBook", query={"symbol": symbol.upper()}, limit=limit, offset=offset) 69 | else: 70 | sell_book = self.api.find("market", "sellBook", query={"symbol": symbol.upper(), "account": account}, limit=limit, offset=offset) 71 | return sell_book 72 | 73 | def get_trades_history(self, symbol, account=None, limit=30, offset=0): 74 | """Returns the trade history for a given symbol. When account is set, 75 | the trade history from the given account is shown. 76 | """ 77 | if self.tokens.get_token(symbol) is None: 78 | raise TokenDoesNotExists("%s does not exists" % symbol) 79 | if account is None: 80 | trades_history = self.api.find("market", "tradesHistory", query={"symbol": symbol.upper()}, limit=limit, offset=offset) 81 | else: 82 | trades_history = self.api.find("market", "tradesHistory", query={"symbol": symbol.upper(), "account": account}, limit=limit, offset=offset) 83 | return trades_history 84 | 85 | def withdraw(self, account, amount): 86 | """Widthdraw SWAP.HIVE to account as HIVE. 87 | 88 | :param str account: account name 89 | :param float amount: Amount to withdraw 90 | 91 | Withdraw example: 92 | 93 | .. code-block:: python 94 | 95 | from hiveengine.market import Market 96 | from beem import Steem 97 | active_wif = "5xxxx" 98 | stm = Steem(keys=[active_wif]) 99 | market = Market(blockchain_instance=stm) 100 | market.withdraw("test", 1) 101 | """ 102 | wallet = Wallet(account, api=self.api, blockchain_instance=self.blockchain) 103 | token_in_wallet = wallet.get_token("SWAP.HIVE") 104 | if token_in_wallet is None: 105 | raise TokenNotInWallet("%s is not in wallet." % "SWAP.HIVE") 106 | if float(token_in_wallet["balance"]) < float(amount): 107 | raise InsufficientTokenAmount("Only %.3f in wallet" % float(token_in_wallet["balance"])) 108 | token = Token("SWAP.HIVE", api=self.api) 109 | quant_amount = token.quantize(amount) 110 | if quant_amount <= decimal.Decimal("0"): 111 | raise InvalidTokenAmount("Amount to transfer is below token precision of %d" % token["precision"]) 112 | contract_payload = {"quantity":str(quant_amount)} 113 | json_data = {"contractName":"hivepegged","contractAction":"withdraw", 114 | "contractPayload":contract_payload} 115 | assert self.blockchain.is_hive 116 | tx = self.blockchain.custom_json(self.ssc_id, json_data, required_auths=[account]) 117 | return tx 118 | 119 | def deposit(self, account, amount): 120 | """Deposit HIVE to market in exchange for SWAP.HIVE. 121 | 122 | :param str account: account name 123 | :param float amount: Amount to deposit 124 | 125 | Deposit example: 126 | 127 | .. code-block:: python 128 | 129 | from hiveengine.market import Market 130 | from beem import Steem 131 | active_wif = "5xxxx" 132 | stm = Steem(keys=[active_wif]) 133 | market = Market(blockchain_instance=stm) 134 | market.deposit("test", 1) 135 | """ 136 | acc = Account(account, blockchain_instance=self.blockchain) 137 | steem_balance = acc.get_balance("available", "HIVE") 138 | if float(steem_balance) < float(amount): 139 | raise InsufficientTokenAmount("Only %.3f in wallet" % float(steem_balance)) 140 | json_data = '{"id":"' + self.ssc_id + '","json":{"contractName":"hivepegged","contractAction":"buy","contractPayload":{}}}' 141 | tx = acc.transfer("honey-swap", amount, "HIVE", memo=json_data) 142 | return tx 143 | 144 | def buy(self, account, amount, symbol, price): 145 | """Buy token for given price. 146 | 147 | :param str account: account name 148 | :param float amount: Amount to withdraw 149 | :param str symbol: symbol 150 | :param float price: price 151 | 152 | Buy example: 153 | 154 | .. code-block:: python 155 | 156 | from hiveengine.market import Market 157 | from beem import Steem 158 | active_wif = "5xxxx" 159 | stm = Steem(keys=[active_wif]) 160 | market = Market(blockchain_instance=stm) 161 | market.buy("test", 1, "BEE", 0.95) 162 | """ 163 | wallet = Wallet(account, api=self.api, blockchain_instance=self.blockchain) 164 | token_in_wallet = wallet.get_token("SWAP.HIVE") 165 | if token_in_wallet is None: 166 | raise TokenNotInWallet("%s is not in wallet." % "SWAP.HIVE") 167 | if float(token_in_wallet["balance"]) < float(amount) * float(price): 168 | raise InsufficientTokenAmount("Only %.3f in wallet" % float(token_in_wallet["balance"])) 169 | 170 | token = Token(symbol, api=self.api) 171 | quant_amount = token.quantize(amount) 172 | if quant_amount <= decimal.Decimal("0"): 173 | raise InvalidTokenAmount("Amount to transfer is below token precision of %d" % token["precision"]) 174 | contract_payload = {"symbol": symbol.upper(), "quantity":str(quant_amount), "price": str(price)} 175 | json_data = {"contractName":"market","contractAction":"buy", 176 | "contractPayload":contract_payload} 177 | assert self.blockchain.is_hive 178 | tx = self.blockchain.custom_json(self.ssc_id, json_data, required_auths=[account]) 179 | return tx 180 | 181 | def sell(self, account, amount, symbol, price): 182 | """Sell token for given price. 183 | 184 | :param str account: account name 185 | :param float amount: Amount to withdraw 186 | :param str symbol: symbol 187 | :param float price: price 188 | 189 | Sell example: 190 | 191 | .. code-block:: python 192 | 193 | from hiveengine.market import Market 194 | from beem import Steem 195 | active_wif = "5xxxx" 196 | stm = Steem(keys=[active_wif]) 197 | market = Market(blockchain_instance=stm) 198 | market.sell("test", 1, "BEE", 0.95) 199 | """ 200 | wallet = Wallet(account, api=self.api, blockchain_instance=self.blockchain) 201 | token_in_wallet = wallet.get_token(symbol) 202 | if token_in_wallet is None: 203 | raise TokenNotInWallet("%s is not in wallet." % symbol) 204 | if float(token_in_wallet["balance"]) < float(amount): 205 | raise InsufficientTokenAmount("Only %.3f in wallet" % float(token_in_wallet["balance"])) 206 | 207 | token = Token(symbol, api=self.api) 208 | quant_amount = token.quantize(amount) 209 | if quant_amount <= decimal.Decimal("0"): 210 | raise InvalidTokenAmount("Amount to transfer is below token precision of %d" % token["precision"]) 211 | contract_payload = {"symbol": symbol.upper(), "quantity":str(quant_amount), "price": str(price)} 212 | json_data = {"contractName":"market","contractAction":"sell", 213 | "contractPayload":contract_payload} 214 | assert self.blockchain.is_hive 215 | tx = self.blockchain.custom_json(self.ssc_id, json_data, required_auths=[account]) 216 | return tx 217 | 218 | def cancel(self, account, order_type, order_id): 219 | """Cancel buy/sell order. 220 | 221 | :param str account: account name 222 | :param str order_type: sell or buy 223 | :param int order_id: order id 224 | 225 | Cancel example: 226 | 227 | .. code-block:: python 228 | 229 | from hiveengine.market import Market 230 | from beem import Steem 231 | active_wif = "5xxxx" 232 | stm = Steem(keys=[active_wif]) 233 | market = Market(blockchain_instance=stm) 234 | market.sell("test", "sell", 12) 235 | """ 236 | 237 | contract_payload = {"type": order_type, "id": order_id} 238 | json_data = {"contractName":"market","contractAction":"cancel", 239 | "contractPayload":contract_payload} 240 | assert self.blockchain.is_hive 241 | tx = self.blockchain.custom_json(self.ssc_id, json_data, required_auths=[account]) 242 | return tx 243 | -------------------------------------------------------------------------------- /hiveengine/nft.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | from __future__ import division 3 | from __future__ import print_function 4 | from __future__ import unicode_literals 5 | from hiveengine.api import Api 6 | from hiveengine.tokenobject import Token 7 | from beem.instance import shared_blockchain_instance 8 | from hiveengine.exceptions import (NftDoesNotExists) 9 | 10 | 11 | class Nft(dict): 12 | """ Access the hive-engine Nfts 13 | """ 14 | def __init__(self, symbol, api=None, blockchain_instance=None): 15 | if api is None: 16 | self.api = Api() 17 | else: 18 | self.api = api 19 | self.blockchain = blockchain_instance or shared_blockchain_instance() 20 | self.ssc_id = "ssc-mainnet-hive" 21 | if isinstance(symbol, dict): 22 | self.symbol = symbol["symbol"] 23 | super(Nft, self).__init__(symbol) 24 | else: 25 | self.symbol = symbol.upper() 26 | self.refresh() 27 | 28 | def refresh(self): 29 | info = self.get_info() 30 | if info is None: 31 | raise NftDoesNotExists("Nft %s does not exists!" % self.symbol) 32 | super(Nft, self).__init__(info) 33 | 34 | def get_info(self): 35 | """Returns information about the nft""" 36 | token = self.api.find_one("nft", "nfts", query={"symbol": self.symbol}) 37 | if len(token) > 0: 38 | return token[0] 39 | else: 40 | return token 41 | 42 | @property 43 | def properties(self): 44 | return list(self["properties"].keys()) 45 | 46 | @property 47 | def issuer(self): 48 | return self["issuer"] 49 | 50 | def get_property(self, property_name): 51 | """Returns all token properties""" 52 | return self.api.find_all("nft", "%sinstances" % self.symbol, query={"properties.name": property_name}) 53 | 54 | def get_collection(self, account): 55 | """ Get NFT collection""" 56 | tokens = self.api.find_all("nft", "%sinstances" % self.symbol, query={"account": account}) 57 | return tokens 58 | 59 | def get_id(self, _id): 60 | """ Get info about a token""" 61 | tokens = self.api.find_one("nft", "%sinstances" % self.symbol, query={"_id": _id}) 62 | if len(tokens) > 0: 63 | return tokens[0] 64 | return tokens 65 | 66 | def get_trade_history(self, query={}, limit=-1, offset=0): 67 | """Returns market information 68 | :param dict query: can be priceSymbol, timestamp 69 | """ 70 | if limit < 0 or limit > 1000: 71 | return self.api.find_all("nftmarket", "%stradesHistory" % self.symbol, query=query) 72 | else: 73 | return self.api.find("nftmarket", "%stradesHistory" % self.symbol, query=query, limit=limit, offset=offset) 74 | 75 | def get_open_interest(self, query={}, limit=-1, offset=0): 76 | """Returns open interests 77 | :param dict query: side, priceSymbol, grouping 78 | """ 79 | if limit < 0 or limit > 1000: 80 | return self.api.find_all("nftmarket", "%sopenInterest" % self.symbol, query=query) 81 | else: 82 | return self.api.find("nftmarket", "%sopenInterest" % self.symbol, query=query, limit=limit, offset=offset) 83 | 84 | def get_sell_book(self, query={}, limit=-1, offset=0): 85 | """Returns the sell book 86 | :param dict query: can be ownedBy, account, nftId, grouping, priceSymbol 87 | """ 88 | if limit < 0 or limit > 1000: 89 | return self.api.find_all("nftmarket", "%ssellBook" % self.symbol, query=query) 90 | else: 91 | return self.api.find("nftmarket", "%ssellBook" % self.symbol, query=query, limit=limit, offset=offset) 92 | 93 | def update_url(self, url): 94 | """Updates the NFT project website 95 | 96 | :param str url: new url 97 | 98 | example: 99 | 100 | .. code-block:: python 101 | 102 | from hiveengine.nft import Nft 103 | from beem import Hive 104 | active_wif = "5xxxx" 105 | hive = Hive(keys=[active_wif]) 106 | nft = Nft("STAR", blockchain_instance=hive) 107 | nft.update_url("https://new_url.com") 108 | """ 109 | contract_payload = {"symbol": self.symbol.upper(), "url": url} 110 | json_data = {"contractName":"nft","contractAction":"updateUrl", 111 | "contractPayload":contract_payload} 112 | assert self.blockchain.is_hive 113 | tx = self.blockchain.custom_json(self.ssc_id, json_data, required_posting_auths=[self["issuer"]]) 114 | return tx 115 | 116 | def update_metadata(self, medadata): 117 | """Updates the metadata of a token. 118 | 119 | :param dict medadata: new medadata 120 | 121 | example: 122 | 123 | .. code-block:: python 124 | 125 | from hiveengine.nft import Nft 126 | from beem import Hive 127 | active_wif = "5xxxx" 128 | hive = Hive(keys=[active_wif]) 129 | nft = Nft("STAR", blockchain_instance=hive) 130 | metadata = {"url": "https://mycoolnft.com", 131 | "icon": "https://mycoolnft.com/token.jpg", 132 | "desc": "This NFT will rock your world! It has features x, y, and z. So cool!"} 133 | nft.update_metadata(metadata) 134 | """ 135 | contract_payload = {"symbol": self.symbol.upper(), "metadata": metadata} 136 | json_data = {"contractName":"nft","contractAction":"updateMetadata", 137 | "contractPayload":contract_payload} 138 | assert self.blockchain.is_hive 139 | tx = self.blockchain.custom_json(self.ssc_id, json_data, required_posting_auths=[self["issuer"]]) 140 | return tx 141 | 142 | def update_name(self, name): 143 | """Updates the user friendly name of an NFT. 144 | 145 | :param str name: new name 146 | 147 | example: 148 | 149 | .. code-block:: python 150 | 151 | from hiveengine.nft import Nft 152 | from beem import Hive 153 | posting_wif = "5xxxx" 154 | hive = Hive(keys=[posting_wif]) 155 | nft = Nft("STAR", blockchain_instance=hive) 156 | nft.update_name("My Awesome NFT") 157 | """ 158 | contract_payload = {"symbol": self.symbol.upper(), "name": name} 159 | json_data = {"contractName":"nft","contractAction":"updateName", 160 | "contractPayload":contract_payload} 161 | assert self.blockchain.is_hive 162 | tx = self.blockchain.custom_json(self.ssc_id, json_data, required_posting_auths=[self["issuer"]]) 163 | return tx 164 | 165 | def update_org_name(self, org_name): 166 | """Updates the name of the company/organization that manages an NFT. 167 | 168 | :param str org_name: new org_name 169 | 170 | example: 171 | 172 | .. code-block:: python 173 | 174 | from hiveengine.nft import Nft 175 | from beem import Hive 176 | posting_wif = "5xxxx" 177 | hive = Hive(keys=[posting_wif]) 178 | nft = Nft("STAR", blockchain_instance=hive) 179 | nft.update_org_name("Nifty Company Inc") 180 | """ 181 | contract_payload = {"symbol": self.symbol.upper(), "orgName": org_name} 182 | json_data = {"contractName":"nft","contractAction":"updateOrgName", 183 | "contractPayload":contract_payload} 184 | assert self.blockchain.is_hive 185 | tx = self.blockchain.custom_json(self.ssc_id, json_data, required_posting_auths=[self["issuer"]]) 186 | return tx 187 | 188 | def update_product_name(self, product_name): 189 | """Updates the name of the company/organization that manages an NFT. 190 | 191 | :param str org_name: new org_name 192 | 193 | example: 194 | 195 | .. code-block:: python 196 | 197 | from hiveengine.nft import Nft 198 | from beem import Hive 199 | posting_wif = "5xxxx" 200 | hive = Hive(keys=[posting_wif]) 201 | nft = Nft("STAR", blockchain_instance=hive) 202 | nft.update_product_name("Acme Exploding NFTs") 203 | """ 204 | contract_payload = {"symbol": self.symbol.upper(), "productName": product_name} 205 | json_data = {"contractName":"nft","contractAction":"updateProductName", 206 | "contractPayload":contract_payload} 207 | assert self.blockchain.is_hive 208 | tx = self.blockchain.custom_json(self.ssc_id, json_data, required_posting_auths=[self["issuer"]]) 209 | return tx 210 | 211 | def add_authorized_issuing_accounts(self, accounts): 212 | """Adds Hive accounts to the list of accounts that are authorized to issue 213 | new tokens on behalf of the NFT owner. 214 | 215 | :param list accounts: A list of hive accounts to add to the authorized list 216 | 217 | example: 218 | 219 | .. code-block:: python 220 | 221 | from hiveengine.nft import Nft 222 | from beem import Hive 223 | active_wif = "5xxxx" 224 | hive = Hive(keys=[active_wif]) 225 | nft = Nft("TESTNFT", blockchain_instance=hive) 226 | nft.add_authorized_issuing_accounts(["satoshi","aggroed","cryptomancer"]) 227 | """ 228 | contract_payload = {"symbol": self.symbol.upper(), "accounts": accounts} 229 | json_data = {"contractName":"nft","contractAction":"addAuthorizedIssuingAccounts", 230 | "contractPayload":contract_payload} 231 | assert self.blockchain.is_hive 232 | tx = self.blockchain.custom_json(self.ssc_id, json_data, required_auths=[self["issuer"]]) 233 | return tx 234 | 235 | def add_authorized_issuing_contracts(self, contracts): 236 | """Adds smart contracts to the list of contracts that are authorized to issue 237 | new tokens on behalf of the NFT owner. 238 | 239 | :param list contracts: A list of smart contracts t to add to the authorized list 240 | 241 | example: 242 | 243 | .. code-block:: python 244 | 245 | from hiveengine.nft import Nft 246 | from beem import Hive 247 | active_wif = "5xxxx" 248 | hive = Hive(keys=[active_wif]) 249 | nft = Nft("TESTNFT", blockchain_instance=hive) 250 | nft.add_authorized_issuing_contracts(["mycontract","anothercontract","mygamecontract"]) 251 | """ 252 | contract_payload = {"symbol": self.symbol.upper(), "contracts": contracts} 253 | json_data = {"contractName":"nft","contractAction":"addAuthorizedIssuingContracts", 254 | "contractPayload":contract_payload} 255 | assert self.blockchain.is_hive 256 | tx = self.blockchain.custom_json(self.ssc_id, json_data, required_auths=[self["issuer"]]) 257 | return tx 258 | 259 | def remove_authorized_issuing_accounts(self, accounts): 260 | """Removes Hive accounts from the list of accounts that are authorized to issue 261 | new tokens on behalf of the NFT owner. 262 | 263 | :param list accounts: A list of hive accounts to remove from the authorized list 264 | 265 | example: 266 | 267 | .. code-block:: python 268 | 269 | from hiveengine.nft import Nft 270 | from beem import Hive 271 | active_wif = "5xxxx" 272 | hive = Hive(keys=[active_wif]) 273 | nft = Nft("TESTNFT", blockchain_instance=hive) 274 | nft.remove_authorized_issuing_accounts(["aggroed","cryptomancer"]) 275 | """ 276 | contract_payload = {"symbol": self.symbol.upper(), "accounts": accounts} 277 | json_data = {"contractName":"nft","contractAction":"removeAuthorizedIssuingAccounts", 278 | "contractPayload":contract_payload} 279 | assert self.blockchain.is_hive 280 | tx = self.blockchain.custom_json(self.ssc_id, json_data, required_auths=[self["issuer"]]) 281 | return tx 282 | 283 | def remove_authorized_issuing_contracts(self, contracts): 284 | """Remvoes smart contracts from the list of contracts that are authorized to issue 285 | new tokens on behalf of the NFT owner. 286 | 287 | :param list contracts: A list of smart contracts to remove from the authorized list 288 | 289 | example: 290 | 291 | .. code-block:: python 292 | 293 | from hiveengine.nft import Nft 294 | from beem import Hive 295 | active_wif = "5xxxx" 296 | hive = Hive(keys=[active_wif]) 297 | nft = Nft("TESTNFT", blockchain_instance=hive) 298 | nft.remove_authorized_issuing_contracts(["mycontract","mygamecontract"]) 299 | """ 300 | contract_payload = {"symbol": self.symbol.upper(), "contracts": contracts} 301 | json_data = {"contractName":"nft","contractAction":"removeAuthorizedIssuingContracts", 302 | "contractPayload":contract_payload} 303 | assert self.blockchain.is_hive 304 | tx = self.blockchain.custom_json(self.ssc_id, json_data, required_auths=[self["issuer"]]) 305 | return tx 306 | 307 | def transfer_ownership(self, to): 308 | """Transfers ownership of an NFT from the current owner to another Hive account. 309 | 310 | :param str to: Hive accounts to become the new owner 311 | 312 | example: 313 | 314 | .. code-block:: python 315 | 316 | from hiveengine.nft import Nft 317 | from beem import Hive 318 | active_wif = "5xxxx" 319 | hive = Hive(keys=[active_wif]) 320 | nft = Nft("TESTNFT", blockchain_instance=hive) 321 | nft.transfer_ownership("aggroed") 322 | """ 323 | contract_payload = {"symbol": self.symbol.upper(), "to": to} 324 | json_data = {"contractName":"nft","contractAction":"transferOwnership", 325 | "contractPayload":contract_payload} 326 | assert self.blockchain.is_hive 327 | tx = self.blockchain.custom_json(self.ssc_id, json_data, required_auths=[self["issuer"]]) 328 | return tx 329 | 330 | def add_property(self, name, prop_type, is_read_only=None, authorized_editing_accounts=None, 331 | authorized_editing_contracts=None): 332 | """Adds a new data property schema to an existing NFT definition 333 | 334 | :param str name: Name of the new property 335 | :param str prop_type: must be number, string or boolean 336 | :param bool is_read_only: 337 | :param list authorized_editing_accounts: 338 | :param list authorized_editing_contracts: 339 | 340 | example: 341 | 342 | .. code-block:: python 343 | 344 | from hiveengine.nft import Nft 345 | from beem import Hive 346 | active_wif = "5xxxx" 347 | hive = Hive(keys=[active_wif]) 348 | nft = Nft("TESTNFT", blockchain_instance=hive) 349 | nft.add_property("color", "string") 350 | """ 351 | contract_payload = {"symbol": self.symbol.upper(), "name": name, "type": prop_type} 352 | if is_read_only is not None: 353 | contract_payload["isReadOnly"] = is_read_only 354 | if authorized_editing_accounts is not None: 355 | contract_payload["authorizedEditingAccounts"] = authorized_editing_accounts 356 | if authorized_editing_contracts is not None: 357 | contract_payload["authorizedEditingContracts"] = authorized_editing_contracts 358 | json_data = {"contractName":"nft","contractAction":"addProperty", 359 | "contractPayload":contract_payload} 360 | assert self.blockchain.is_hive 361 | tx = self.blockchain.custom_json(self.ssc_id, json_data, required_auths=[self["issuer"]]) 362 | return tx 363 | 364 | def set_property_permissions(self, name, accounts=None, contracts=None): 365 | """Can be used after calling the addProperty action to change the lists of 366 | authorized editing accounts & contracts for a given data property. 367 | 368 | :param str name: Name of the new property 369 | :param list accounts: 370 | :param list contracts: 371 | 372 | example: 373 | 374 | .. code-block:: python 375 | 376 | from hiveengine.nft import Nft 377 | from beem import Hive 378 | active_wif = "5xxxx" 379 | hive = Hive(keys=[active_wif]) 380 | nft = Nft("TESTNFT", blockchain_instance=hive) 381 | nft.set_property_permissions("color", accounts=["cryptomancer","marc"]) 382 | """ 383 | contract_payload = {"symbol": self.symbol.upper(), "name": name} 384 | if accounts is not None: 385 | contract_payload["accounts"] = accounts 386 | if contracts is not None: 387 | contract_payload["contracts"] = contracts 388 | json_data = {"contractName":"nft","contractAction":"setPropertyPermissions", 389 | "contractPayload":contract_payload} 390 | assert self.blockchain.is_hive 391 | tx = self.blockchain.custom_json(self.ssc_id, json_data, required_auths=[self["issuer"]]) 392 | return tx 393 | 394 | def set_properties(self, nfts, from_type=None, authorized_account=None): 395 | """Edits one or more data properties on one or more instances of an NFT. 396 | 397 | :param list nfts: 398 | :param str from_type: 399 | :param str authorized_account: authorized hive account 400 | 401 | example: 402 | 403 | .. code-block:: python 404 | 405 | from hiveengine.nft import Nft 406 | from beem import Hive 407 | posting_wif = "5xxxx" 408 | hive = Hive(keys=[posting_wif]) 409 | nft = Nft("TESTNFT", blockchain_instance=hive) 410 | nft.set_properties([{ "id":"573", "properties": {"color": "red", "level": 2}}]) 411 | """ 412 | if authorized_account is None: 413 | authorized_account = self["issuer"] 414 | contract_payload = {"symbol": self.symbol.upper(), "nfts": nfts} 415 | if from_type is not None: 416 | contract_payload["fromType"] = from_type 417 | json_data = {"contractName":"nft","contractAction":"setPropertyPermissions", 418 | "contractPayload":contract_payload} 419 | assert self.blockchain.is_hive 420 | tx = self.blockchain.custom_json(self.ssc_id, json_data, required_posting_auths=[authorized_account]) 421 | return tx 422 | 423 | def set_group_by(self, properties): 424 | """Can be used after calling the addProperty action to change the lists of 425 | authorized editing accounts & contracts for a given data property. 426 | 427 | :param list properties: 428 | 429 | example: 430 | 431 | .. code-block:: python 432 | 433 | from hiveengine.nft import Nft 434 | from beem import Hive 435 | active_wif = "5xxxx" 436 | hive = Hive(keys=[active_wif]) 437 | nft = Nft("TESTNFT", blockchain_instance=hive) 438 | nft.set_group_by(["level", "isFood"]) 439 | """ 440 | contract_payload = {"symbol": self.symbol.upper(), "properties ": properties} 441 | json_data = {"contractName":"nft","contractAction":"setGroupBy", 442 | "contractPayload":contract_payload} 443 | assert self.blockchain.is_hive 444 | tx = self.blockchain.custom_json(self.ssc_id, json_data, required_auths=[self["issuer"]]) 445 | return tx 446 | 447 | def update_property_definition(self, name, new_name=None, prop_type=None, is_read_only=None): 448 | """ Updates the schema of a data property. 449 | This action can only be called if no tokens for this NFT have been issued yet. 450 | 451 | :param str name: Name of the new property 452 | :param str name: 453 | :param str new_name: 454 | :param str prop_type: 455 | :param bool is_read_only: 456 | 457 | example: 458 | 459 | .. code-block:: python 460 | 461 | from hiveengine.nft import Nft 462 | from beem import Hive 463 | active_wif = "5xxxx" 464 | hive = Hive(keys=[active_wif]) 465 | nft = Nft("TESTNFT", blockchain_instance=hive) 466 | nft.update_property_definition("color", new_name="Color") 467 | """ 468 | contract_payload = {"symbol": self.symbol.upper(), "name": name} 469 | if new_name is not None: 470 | contract_payload["newName"] = new_name 471 | if prop_type is not None: 472 | contract_payload["type"] = prop_type 473 | if is_read_only is not None: 474 | contract_payload["isReadOnly"] = is_read_only 475 | json_data = {"contractName":"nft","contractAction":"updatePropertyDefinition", 476 | "contractPayload":contract_payload} 477 | assert self.blockchain.is_hive 478 | tx = self.blockchain.custom_json(self.ssc_id, json_data, required_auths=[self["issuer"]]) 479 | return tx 480 | 481 | def issue(self, to, fee_symbol, from_type=None, to_type=None, lock_tokens=None, lock_nfts=None, 482 | properties=None, authorized_account=None): 483 | """Issues a new instance of an NFT to a Hive account or smart contract. 484 | 485 | :param str to: 486 | :param str fee_symbol: 487 | :param str from_type: 488 | :param str to_type: 489 | :param dict lock_tokens: 490 | :param list lock_nfts: 491 | :param dict properties: 492 | :param str authorized_account: authorized hive account 493 | 494 | example: 495 | 496 | .. code-block:: python 497 | 498 | from hiveengine.nft import Nft 499 | from beem import Hive 500 | active_wif = "5xxxx" 501 | hive = Hive(keys=[active_wif]) 502 | nft = Nft("TESTNFT", blockchain_instance=hive) 503 | nft.issue("aggroed", "PAL") 504 | """ 505 | if authorized_account is None: 506 | authorized_account = self["issuer"] 507 | contract_payload = {"symbol": self.symbol.upper(), "to": to, "feeSymbol": fee_symbol} 508 | if from_type is not None: 509 | contract_payload["fromType"] = from_type 510 | if to_type is not None: 511 | contract_payload["toType"] = to_type 512 | if lock_tokens is not None: 513 | contract_payload["lockTokens"] = lock_tokens 514 | if lock_nfts is not None: 515 | contract_payload["lockNfts"] = lock_nfts 516 | if properties is not None: 517 | contract_payload["properties"] = properties 518 | 519 | json_data = {"contractName":"nft","contractAction":"issue", 520 | "contractPayload":contract_payload} 521 | assert self.blockchain.is_hive 522 | tx = self.blockchain.custom_json(self.ssc_id, json_data, required_auths=[authorized_account]) 523 | return tx 524 | 525 | def issue_multiple(self, instances, authorized_account=None): 526 | """Issues multiple NFT instances at once. 527 | 528 | :param list instances: 529 | :param str authorized_account: authorized hive account 530 | 531 | example: 532 | 533 | .. code-block:: python 534 | 535 | from hiveengine.nft import Nft 536 | from beem import Hive 537 | active_wif = "5xxxx" 538 | hive = Hive(keys=[active_wif]) 539 | nft = Nft("TESTNFT", blockchain_instance=hive) 540 | nft.issue_multiple([{"fromType": "contract", "symbol": "TSTNFT", "to": "marc", "feeSymbol": "PAL"}]) 541 | """ 542 | if authorized_account is None: 543 | authorized_account = self["issuer"] 544 | contract_payload = {"instances": instances} 545 | json_data = {"contractName":"nft","contractAction":"issueMultiple", 546 | "contractPayload":contract_payload} 547 | assert self.blockchain.is_hive 548 | tx = self.blockchain.custom_json(self.ssc_id, json_data, required_auths=[authorized_account]) 549 | return tx 550 | 551 | def enable_delegation(self, undelegation_cooldown): 552 | """Enables the delegation feature for a NFT 553 | 554 | :param int undelegation_cooldown: Cooldown in days 555 | 556 | example: 557 | 558 | .. code-block:: python 559 | 560 | from hiveengine.nft import Nft 561 | from beem import Hive 562 | active_wif = "5xxxx" 563 | hive = Hive(keys=[active_wif]) 564 | nft = Nft("TESTNFT", blockchain_instance=hive) 565 | nft.enable_delegation(30) 566 | """ 567 | contract_payload = {"symbol": self.symbol.upper(), "undelegationCooldown": undelegation_cooldown} 568 | json_data = {"contractName":"nft","contractAction":"enableDelegation", 569 | "contractPayload":contract_payload} 570 | assert self.blockchain.is_hive 571 | tx = self.blockchain.custom_json(self.ssc_id, json_data, required_auths=[self["issuer"]]) 572 | return tx 573 | -------------------------------------------------------------------------------- /hiveengine/nftmarket.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | from __future__ import division 3 | from __future__ import print_function 4 | from __future__ import unicode_literals 5 | import sys 6 | from datetime import datetime, timedelta, date 7 | import time 8 | import json 9 | from timeit import default_timer as timer 10 | import logging 11 | import decimal 12 | from hiveengine.api import Api 13 | from hiveengine.nfts import Nfts 14 | from hiveengine.nft import Nft 15 | from hiveengine.wallet import Wallet 16 | from hiveengine.collection import Collection 17 | from hiveengine.exceptions import (NftDoesNotExists, TokenNotInWallet, InsufficientTokenAmount, InvalidTokenAmount) 18 | from beem.instance import shared_blockchain_instance 19 | from beem.account import Account 20 | 21 | 22 | class NftMarket(list): 23 | """ Access the hive-engine NFT market 24 | 25 | :param Hive blockchain_instance: Hive 26 | instance 27 | """ 28 | def __init__(self, api=None, blockchain_instance=None, steem_instance=None): 29 | if api is None: 30 | self.api = Api() 31 | else: 32 | self.api = api 33 | self.blockchain = blockchain_instance or steem_instance or shared_blockchain_instance() 34 | self.nfts = Nfts(api=self.api) 35 | self.ssc_id = "ssc-mainnet-hive" 36 | 37 | def set_id(self, ssc_id): 38 | """Sets the ssc id (default is ssc-mainnet-hive)""" 39 | self.ssc_id = ssc_id 40 | 41 | def get_sell_book(self, symbol, account=None, grouping_name=None, grouping_value=None, priceSymbol=None, nftId=None, limit=None): 42 | """Returns the sell book for a given symbol. When account is set, 43 | the order book from the given account is shown. 44 | """ 45 | nft = Nft(symbol, api=self.api) 46 | query = {} 47 | if account is not None: 48 | query["account"] = account 49 | if grouping_name is not None and grouping_value is not None: 50 | query["grouping." + grouping_name] = grouping_value 51 | if priceSymbol is not None: 52 | query["priceSymbol"] = priceSymbol.upper() 53 | if nftId is not None: 54 | query["nftId"] = nftId 55 | if limit is None: 56 | limit = -1 57 | sell_book = nft.get_sell_book(query=query, limit=limit) 58 | return sell_book 59 | 60 | def get_open_interest(self, symbol, side="sell", grouping_name=None, grouping_value=None, priceSymbol=None, limit=None): 61 | """Returns the sell book for a given symbol. When account is set, 62 | the order book from the given account is shown. 63 | """ 64 | nft = Nft(symbol, api=self.api) 65 | query = {} 66 | query["side"] = side 67 | if grouping_name is not None and grouping_value is not None: 68 | query["grouping." + grouping_name] = grouping_value 69 | if priceSymbol is not None: 70 | query["priceSymbol"] = priceSymbol.upper() 71 | if limit is None: 72 | limit = -1 73 | sell_book = nft.get_open_interest(query=query, limit=limit) 74 | return sell_book 75 | 76 | def get_trades_history(self, symbol, account=None, priceSymbol=None, timestamp=None): 77 | """Returns the trade history for a given symbol. When account is set, 78 | the trade history from the given account is shown. 79 | """ 80 | nft = Nft(symbol, api=self.api) 81 | query = {} 82 | if account is not None: 83 | query["account"] = account 84 | if priceSymbol is not None: 85 | query["priceSymbol"] = priceSymbol.upper() 86 | if timestamp is not None: 87 | query["timestamp"] = timestamp 88 | trades_history = nft.get_trade_history(query=query, limit=-1) 89 | new_trades_history = [] 90 | last_id = None 91 | for trade in trades_history[::-1]: 92 | if last_id is None: 93 | last_id = trade["_id"] 94 | elif last_id != trade["_id"] + 1: 95 | continue 96 | else: 97 | last_id = trade["_id"] 98 | new_trades_history.append(trade) 99 | 100 | return new_trades_history[::-1] 101 | 102 | def buy(self, symbol, account, nft_ids, market_account): 103 | """Buy nfts for given price. 104 | 105 | :param str symbol: symbol 106 | :param str account: account name 107 | :param list nft_ids: list if token ids 108 | :param str market_account: Account who receive the fee 109 | 110 | :param float price: price 111 | 112 | Buy example: 113 | 114 | .. code-block:: python 115 | 116 | from hiveengine.nftmarket import NftMarket 117 | from beem import Hive 118 | active_wif = "5xxxx" 119 | hive = Hive(keys=[active_wif]) 120 | market = NftMarket(blockchain_instance=hive) 121 | market.buy("STAR", "test", ["1"], "nftmarket") 122 | """ 123 | nft_list = [] 124 | if not isinstance(nft_ids, list): 125 | nft_list = [str(nft_ids)] 126 | else: 127 | for n in nft_ids: 128 | nft_list.append(str(n)) 129 | contract_payload = {"symbol": symbol.upper(), "nfts": nft_list, "marketAccount": market_account} 130 | json_data = {"contractName":"nftmarket","contractAction":"buy", 131 | "contractPayload":contract_payload} 132 | assert self.blockchain.is_hive 133 | tx = self.blockchain.custom_json(self.ssc_id, json_data, required_auths=[account]) 134 | return tx 135 | 136 | def sell(self, symbol, account, nft_ids, price, price_symbol, fee): 137 | """Sell token for given price. 138 | 139 | :param str symbol: symbol 140 | :param str account: account name 141 | :param list nft_ids: List of nft ids 142 | :param float price: price 143 | :param str price_symbol: price symbol 144 | :param int fee: fee percentage (500 -> 5%) 145 | 146 | Sell example: 147 | 148 | .. code-block:: python 149 | 150 | from hiveengine.nftmarket import NftMarket 151 | from beem import Hive 152 | active_wif = "5xxxx" 153 | hive = Hive(keys=[active_wif]) 154 | market = NftMarket(blockchain_instance=hive) 155 | market.sell("STAR", "test", ["1"], 100, "STARBITS", 500) 156 | 157 | """ 158 | nft_list = [] 159 | if not isinstance(nft_ids, list): 160 | nft_list = [str(nft_ids)] 161 | else: 162 | for n in nft_ids: 163 | nft_list.append(str(n)) 164 | contract_payload = {"symbol": symbol.upper(), "nfts": nft_list, "price": str(price), 165 | "priceSymbol": price_symbol.upper(), "fee": int(fee)} 166 | json_data = {"contractName":"nftmarket","contractAction":"sell", 167 | "contractPayload":contract_payload} 168 | assert self.blockchain.is_hive 169 | tx = self.blockchain.custom_json(self.ssc_id, json_data, required_auths=[account]) 170 | return tx 171 | 172 | def change_price(self, symbol, account, nft_ids, price): 173 | """Change a price for a listed nft id 174 | 175 | :param str symbol: nft symbol 176 | :param str account: account name 177 | :param list nft_ids: List of nfts 178 | :param float price: new price 179 | 180 | Sell example: 181 | 182 | .. code-block:: python 183 | 184 | from hiveengine.nftmarket import NftMarket 185 | from beem import Hive 186 | active_wif = "5xxxx" 187 | hive = Hive(keys=[active_wif]) 188 | market = NftMarket(blockchain_instance=hive) 189 | market.change_price("STAR", "test", ["1"], 30) 190 | 191 | """ 192 | 193 | nft_list = [] 194 | if not isinstance(nft_ids, list): 195 | nft_list = [str(nft_ids)] 196 | else: 197 | for n in nft_ids: 198 | nft_list.append(str(n)) 199 | contract_payload = {"symbol": symbol.upper(), "nfts": nft_list, "price": str(price)} 200 | json_data = {"contractName":"nftmarket","contractAction":"changePrice", 201 | "contractPayload":contract_payload} 202 | assert self.blockchain.is_hive 203 | tx = self.blockchain.custom_json(self.ssc_id, json_data, required_auths=[account]) 204 | return tx 205 | 206 | def cancel(self, symbol, account, nft_ids): 207 | """Cancel sell order. 208 | 209 | :param str symbol: symbol 210 | :param str account: account name 211 | :param list nft_ids: list of tokens ids 212 | 213 | Cancel example: 214 | 215 | .. code-block:: python 216 | 217 | from hiveengine.nftmarket import NftMarket 218 | from beem import Hive 219 | active_wif = "5xxxx" 220 | hive = Hive(keys=[active_wif]) 221 | market = NftMarket(blockchain_instance=hive) 222 | market.cancel("STAR", "test", ["1"]) 223 | 224 | """ 225 | nft_list = [] 226 | if not isinstance(nft_ids, list): 227 | nft_list = [str(nft_ids)] 228 | else: 229 | for n in nft_ids: 230 | nft_list.append(str(n)) 231 | contract_payload = {"symbol": symbol.upper(), "nfts": nft_list} 232 | json_data = {"contractName":"nftmarket","contractAction":"cancel", 233 | "contractPayload":contract_payload} 234 | assert self.blockchain.is_hive 235 | tx = self.blockchain.custom_json(self.ssc_id, json_data, required_auths=[account]) 236 | return tx 237 | -------------------------------------------------------------------------------- /hiveengine/nfts.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | from __future__ import division 3 | from __future__ import print_function 4 | from __future__ import unicode_literals 5 | from hiveengine.api import Api 6 | from hiveengine.nft import Nft 7 | 8 | 9 | class Nfts(list): 10 | """ Access the Hive-engine Nfts 11 | """ 12 | def __init__(self, api=None, **kwargs): 13 | if api is None: 14 | self.api = Api() 15 | else: 16 | self.api = api 17 | self.refresh() 18 | 19 | def refresh(self): 20 | super(Nfts, self).__init__(self.get_nft_list()) 21 | 22 | def get_nft_list(self): 23 | """Returns all available nft as list""" 24 | tokens = self.api.find_all("nft", "nfts", query={}) 25 | return tokens 26 | 27 | def get_nft_params(self): 28 | """Returns all available nft as list""" 29 | tokens = self.api.find_one("nft", "params", query={}) 30 | if isinstance(tokens, list) and len(tokens) > 0: 31 | tokens = tokens[0] 32 | return tokens 33 | 34 | def get_symbol_list(self): 35 | symbols = [] 36 | for nft in self: 37 | symbols.append(nft["symbol"]) 38 | return symbols 39 | 40 | def get_nft(self, nft): 41 | """Returns Token from given nft symbol. Is None 42 | when nft does not exists. 43 | """ 44 | for t in self: 45 | if t["symbol"].lower() == nft.lower(): 46 | return Nft(t, api=self.api) 47 | return None 48 | 49 | -------------------------------------------------------------------------------- /hiveengine/rpc.py: -------------------------------------------------------------------------------- 1 | """graphennewsrpc.""" 2 | from __future__ import absolute_import 3 | from __future__ import division 4 | from __future__ import print_function 5 | from __future__ import unicode_literals 6 | from builtins import next 7 | from builtins import str 8 | from builtins import object 9 | import sys 10 | import json 11 | import logging 12 | import re 13 | 14 | from .version import version as hiveengine_version 15 | if sys.version_info[0] < 3: 16 | from thread import interrupt_main 17 | else: 18 | from _thread import interrupt_main 19 | 20 | REQUEST_MODULE = None 21 | if not REQUEST_MODULE: 22 | try: 23 | import requests 24 | from requests.adapters import HTTPAdapter 25 | from requests.packages.urllib3.util.retry import Retry 26 | from requests.exceptions import ConnectionError 27 | REQUEST_MODULE = "requests" 28 | except ImportError: 29 | REQUEST_MODULE = None 30 | 31 | log = logging.getLogger(__name__) 32 | 33 | 34 | 35 | class RPCError(Exception): 36 | """RPCError Exception.""" 37 | 38 | pass 39 | 40 | 41 | class RPCErrorDoRetry(Exception): 42 | """RPCErrorDoRetry Exception.""" 43 | 44 | pass 45 | 46 | 47 | class UnauthorizedError(Exception): 48 | """UnauthorizedError Exception.""" 49 | 50 | pass 51 | 52 | 53 | class SessionInstance(object): 54 | """Singelton for the Session Instance""" 55 | instance = None 56 | 57 | 58 | def set_session_instance(instance): 59 | """Set session instance""" 60 | SessionInstance.instance = instance 61 | 62 | 63 | def shared_session_instance(): 64 | """Get session instance""" 65 | if REQUEST_MODULE is None: 66 | raise Exception() 67 | if not SessionInstance.instance: 68 | SessionInstance.instance = requests.Session() 69 | return SessionInstance.instance 70 | 71 | 72 | def get_endpoint_name(*args, **kwargs): 73 | # Sepcify the endpoint to talk to 74 | endpoint = "contracts" 75 | if ("endpoint" in kwargs) and len(kwargs["endpoint"]) > 0: 76 | endpoint = kwargs["endpoint"] 77 | return endpoint 78 | 79 | 80 | class RPC(object): 81 | """ 82 | This class allows to call API methods synchronously, without callbacks. 83 | 84 | It logs warnings and errors. 85 | 86 | Usage: 87 | 88 | .. code-block:: python 89 | 90 | from hiveengine.rpc import RPC 91 | rpc = RPC() 92 | print(rpc.getLatestBlockInfo(endpoint="blockchain")) 93 | 94 | """ 95 | 96 | def __init__(self, url=None, user=None, password=None, **kwargs): 97 | """Init.""" 98 | self._request_id = 0 99 | self.timeout = kwargs.get('timeout', 60) 100 | num_retries = kwargs.get("num_retries", -1) 101 | num_retries_call = kwargs.get("num_retries_call", 5) 102 | 103 | self.user = user 104 | self.password = password 105 | if url is None: 106 | self.url = 'https://api.hive-engine.com/rpc/' 107 | else: 108 | self.url = url 109 | self.session = shared_session_instance() 110 | self.headers = {'User-Agent': 'hiveengine v%s' % (hiveengine_version), 111 | 'content-type': 'application/json'} 112 | self.rpc_queue = [] 113 | 114 | def get_request_id(self): 115 | """Get request id.""" 116 | self._request_id += 1 117 | return self._request_id 118 | 119 | def request_send(self, endpoint, payload): 120 | if self.user is not None and self.password is not None: 121 | response = self.session.post(self.url + endpoint, 122 | data=payload, 123 | headers=self.headers, 124 | timeout=self.timeout, 125 | auth=(self.user, self.password)) 126 | else: 127 | response = self.session.post(self.url + endpoint, 128 | data=payload, 129 | headers=self.headers, 130 | timeout=self.timeout) 131 | if response.status_code == 401: 132 | raise UnauthorizedError 133 | return response.text 134 | 135 | def version_string_to_int(self, network_version): 136 | version_list = network_version.split('.') 137 | return int(int(version_list[0]) * 1e8 + int(version_list[1]) * 1e4 + int(version_list[2])) 138 | 139 | def _check_for_server_error(self, reply): 140 | """Checks for server error message in reply""" 141 | if re.search("Internal Server Error", reply) or re.search("500", reply): 142 | raise RPCErrorDoRetry("Internal Server Error") 143 | elif re.search("Not Implemented", reply) or re.search("501", reply): 144 | raise RPCError("Not Implemented") 145 | elif re.search("Bad Gateway", reply) or re.search("502", reply): 146 | raise RPCErrorDoRetry("Bad Gateway") 147 | elif re.search("Too Many Requests", reply) or re.search("429", reply): 148 | raise RPCErrorDoRetry("Too Many Requests") 149 | elif re.search("Service Temporarily Unavailable", reply) or re.search("Service Unavailable", reply) or re.search("503", reply): 150 | raise RPCErrorDoRetry("Service Temporarily Unavailable") 151 | elif re.search("Gateway Time-out", reply) or re.search("Gateway Timeout", reply) or re.search("504", reply): 152 | raise RPCErrorDoRetry("Gateway Time-out") 153 | elif re.search("HTTP Version not supported", reply) or re.search("505", reply): 154 | raise RPCError("HTTP Version not supported") 155 | elif re.search("Variant Also Negotiates", reply) or re.search("506", reply): 156 | raise RPCError("Variant Also Negotiates") 157 | elif re.search("Insufficient Storage", reply) or re.search("507", reply): 158 | raise RPCError("Insufficient Storage") 159 | elif re.search("Loop Detected", reply) or re.search("508", reply): 160 | raise RPCError("Loop Detected") 161 | elif re.search("Bandwidth Limit Exceeded", reply) or re.search("509", reply): 162 | raise RPCError("Bandwidth Limit Exceeded") 163 | elif re.search("Not Extended", reply) or re.search("510", reply): 164 | raise RPCError("Not Extended") 165 | elif re.search("Network Authentication Required", reply) or re.search("511", reply): 166 | raise RPCError("Network Authentication Required") 167 | else: 168 | raise RPCError("Client returned invalid format. Expected JSON!") 169 | 170 | def rpcexec(self, endpoint, payload): 171 | """ 172 | Execute a call by sending the payload. 173 | 174 | :param json payload: Payload data 175 | :raises ValueError: if the server does not respond in proper JSON format 176 | :raises RPCError: if the server returns an error 177 | """ 178 | log.debug(json.dumps(payload)) 179 | 180 | reply = self.request_send(endpoint, json.dumps(payload, ensure_ascii=False).encode('utf8')) 181 | 182 | ret = {} 183 | try: 184 | ret = json.loads(reply, strict=False) 185 | except ValueError: 186 | self._check_for_server_error(reply) 187 | 188 | log.debug(json.dumps(reply)) 189 | 190 | if isinstance(ret, dict) and 'error' in ret: 191 | if 'detail' in ret['error']: 192 | raise RPCError(ret['error']['detail']) 193 | else: 194 | raise RPCError(ret['error']['message']) 195 | else: 196 | if isinstance(ret, list): 197 | ret_list = [] 198 | for r in ret: 199 | if isinstance(r, dict) and 'error' in r: 200 | if 'detail' in r['error']: 201 | raise RPCError(r['error']['detail']) 202 | else: 203 | raise RPCError(r['error']['message']) 204 | elif isinstance(r, dict) and "result" in r: 205 | ret_list.append(r["result"]) 206 | else: 207 | ret_list.append(r) 208 | return ret_list 209 | elif isinstance(ret, dict) and "result" in ret: 210 | self.nodes.reset_error_cnt_call() 211 | return ret["result"] 212 | elif isinstance(ret, int): 213 | raise RPCError("Client returned invalid format. Expected JSON! Output: %s" % (str(ret))) 214 | else: 215 | return ret 216 | return ret 217 | 218 | # End of Deprecated methods 219 | #################################################################### 220 | def __getattr__(self, name): 221 | """Map all methods to RPC calls and pass through the arguments.""" 222 | def method(*args, **kwargs): 223 | endpoint = get_endpoint_name(*args, **kwargs) 224 | args = json.loads(json.dumps(args)) 225 | # let's be able to define the num_retries per query 226 | if len(args) > 0: 227 | args = args[0] 228 | query = {"method": name, 229 | "jsonrpc": "2.0", 230 | "params": args, 231 | "id": self.get_request_id()} 232 | self.rpc_queue.append(query) 233 | query = self.rpc_queue 234 | self.rpc_queue = [] 235 | r = self.rpcexec(endpoint, query) 236 | return r 237 | return method 238 | -------------------------------------------------------------------------------- /hiveengine/tokenobject.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | from __future__ import division 3 | from __future__ import print_function 4 | from __future__ import unicode_literals 5 | from hiveengine.api import Api 6 | from hiveengine.exceptions import TokenDoesNotExists 7 | import decimal 8 | 9 | 10 | class Token(dict): 11 | """ hive-engine token dict 12 | 13 | :param str token: Name of the token 14 | """ 15 | def __init__(self, symbol, api=None): 16 | if api is None: 17 | self.api = Api() 18 | else: 19 | self.api = api 20 | if isinstance(symbol, dict): 21 | self.symbol = symbol["symbol"] 22 | super(Token, self).__init__(symbol) 23 | else: 24 | self.symbol = symbol.upper() 25 | self.refresh() 26 | 27 | def refresh(self): 28 | info = self.get_info() 29 | if info is None: 30 | raise TokenDoesNotExists("Token %s does not exists!" % self.symbol) 31 | super(Token, self).__init__(info) 32 | 33 | def quantize(self, amount): 34 | """Round down a amount using the token precision and returns a Decimal object""" 35 | amount = decimal.Decimal(amount) 36 | places = decimal.Decimal(10) ** (-self["precision"]) 37 | return amount.quantize(places, rounding=decimal.ROUND_DOWN) 38 | 39 | def get_info(self): 40 | """Returns information about the token""" 41 | token = self.api.find_one("tokens", "tokens", query={"symbol": self.symbol}) 42 | if len(token) > 0: 43 | return token[0] 44 | else: 45 | return token 46 | 47 | def get_holder(self, limit=1000, offset=0): 48 | """Returns all token holders""" 49 | holder = self.api.find("tokens", "balances", query={"symbol": self.symbol}, limit=limit, offset=offset) 50 | return holder 51 | 52 | def get_market_info(self): 53 | """Returns market information""" 54 | metrics = self.api.find_one("market", "metrics", query={"symbol": self.symbol}) 55 | if len(metrics) > 0: 56 | return metrics[0] 57 | else: 58 | return metrics 59 | 60 | def get_buy_book(self, limit=100, offset=0): 61 | """Returns the buy book""" 62 | holder = self.api.find("market", "buyBook", query={"symbol": self.symbol}, limit=limit, offset=offset) 63 | return holder 64 | 65 | def get_sell_book(self, limit=100, offset=0): 66 | """Returns the sell book""" 67 | holder = self.api.find("market", "sellBook", query={"symbol": self.symbol}, limit=limit, offset=offset) 68 | return holder 69 | -------------------------------------------------------------------------------- /hiveengine/tokens.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | from __future__ import division 3 | from __future__ import print_function 4 | from __future__ import unicode_literals 5 | from hiveengine.api import Api 6 | from hiveengine.tokenobject import Token 7 | 8 | 9 | class Tokens(list): 10 | """ Access the steem-engine tokens 11 | """ 12 | def __init__(self, api=None, **kwargs): 13 | if api is None: 14 | self.api = Api() 15 | else: 16 | self.api = api 17 | self.refresh() 18 | 19 | def refresh(self): 20 | super(Tokens, self).__init__(self.get_token_list()) 21 | 22 | def get_token_list(self): 23 | """Returns all available token as list""" 24 | tokens = self.api.find("tokens", "tokens", query={}) 25 | return tokens 26 | 27 | def get_token(self, symbol): 28 | """Returns Token from given token symbol. Is None 29 | when token does not exists. 30 | """ 31 | for t in self: 32 | if t["symbol"].lower() == symbol.lower(): 33 | return Token(t, api=self.api) 34 | return None 35 | 36 | -------------------------------------------------------------------------------- /hiveengine/version.py: -------------------------------------------------------------------------------- 1 | """THIS FILE IS GENERATED FROM beem SETUP.PY.""" 2 | version = '0.2.2' 3 | -------------------------------------------------------------------------------- /hiveengine/wallet.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | from __future__ import division 3 | from __future__ import print_function 4 | from __future__ import unicode_literals 5 | import sys 6 | from datetime import datetime, timedelta, date 7 | import time 8 | import io 9 | import json 10 | import requests 11 | from timeit import default_timer as timer 12 | import logging 13 | import decimal 14 | from hiveengine.api import Api 15 | from hiveengine.tokenobject import Token 16 | from hiveengine.exceptions import (TokenDoesNotExists, TokenNotInWallet, InsufficientTokenAmount, TokenIssueNotPermitted, MaxSupplyReached, InvalidTokenAmount) 17 | from beem.instance import shared_blockchain_instance 18 | from beem.account import Account 19 | 20 | 21 | class Wallet(list): 22 | """ Access the hive-engine wallet 23 | 24 | :param str account: Name of the account 25 | :param Hive blockchain_instance: Hive 26 | instance 27 | 28 | Wallet example: 29 | 30 | .. code-block:: python 31 | 32 | from hiveengine.wallet import Wallet 33 | wallet = Wallet("test") 34 | print(wallet) 35 | 36 | """ 37 | def __init__(self, account, api=None, blockchain_instance=None, steem_instance=None): 38 | if api is None: 39 | self.api = Api() 40 | else: 41 | self.api = api 42 | self.ssc_id = "ssc-mainnet-hive" 43 | self.blockchain = blockchain_instance or steem_instance or shared_blockchain_instance() 44 | check_account = Account(account, blockchain_instance=self.blockchain) 45 | self.account = check_account["name"] 46 | self.refresh() 47 | 48 | def refresh(self): 49 | super(Wallet, self).__init__(self.get_balances()) 50 | 51 | def set_id(self, ssc_id): 52 | """Sets the ssc id (default is ssc-mainnet-hive)""" 53 | self.ssc_id = ssc_id 54 | 55 | def get_balances(self): 56 | """Returns all token within the wallet as list""" 57 | balances = self.api.find("tokens", "balances", query={"account": self.account}) 58 | return balances 59 | 60 | def change_account(self, account): 61 | """Changes the wallet account""" 62 | check_account = Account(account, blockchain_instance=self.blockchain) 63 | self.account = check_account["name"] 64 | self.refresh() 65 | 66 | def get_token(self, symbol): 67 | """Returns a token from the wallet. Is None when not available.""" 68 | for token in self: 69 | if token["symbol"].lower() == symbol.lower(): 70 | return token 71 | return None 72 | 73 | def transfer(self, to, amount, symbol, memo=""): 74 | """Transfer a token to another account. 75 | 76 | :param str to: Recipient 77 | :param float amount: Amount to transfer 78 | :param str symbol: Token to transfer 79 | :param str memo: (optional) Memo 80 | 81 | 82 | Transfer example: 83 | 84 | .. code-block:: python 85 | 86 | from hiveengine.wallet import Wallet 87 | from beem import Steem 88 | active_wif = "5xxxx" 89 | stm = Steem(keys=[active_wif]) 90 | wallet = Wallet("test", blockchain_instance=stm) 91 | wallet.transfer("test1", 1, "BEE", "test") 92 | """ 93 | token_in_wallet = self.get_token(symbol) 94 | if token_in_wallet is None: 95 | raise TokenNotInWallet("%s is not in wallet." % symbol) 96 | if float(token_in_wallet["balance"]) < float(amount): 97 | raise InsufficientTokenAmount("Only %.3f in wallet" % float(token_in_wallet["balance"])) 98 | token = Token(symbol, api=self.api) 99 | quant_amount = token.quantize(amount) 100 | if quant_amount <= decimal.Decimal("0"): 101 | raise InvalidTokenAmount("Amount to transfer is below token precision of %d" % token["precision"]) 102 | check_to = Account(to, blockchain_instance=self.blockchain) 103 | contract_payload = {"symbol":symbol.upper(),"to":to,"quantity":str(quant_amount),"memo":memo} 104 | json_data = {"contractName":"tokens","contractAction":"transfer", 105 | "contractPayload":contract_payload} 106 | assert self.blockchain.is_hive 107 | tx = self.blockchain.custom_json(self.ssc_id, json_data, required_auths=[self.account]) 108 | return tx 109 | 110 | def stake(self, amount, symbol, receiver=None): 111 | """Stake a token. 112 | 113 | :param float amount: Amount to stake 114 | :param str symbol: Token to stake 115 | 116 | Stake example: 117 | 118 | .. code-block:: python 119 | 120 | from hiveengine.wallet import Wallet 121 | from beem import Steem 122 | active_wif = "5xxxx" 123 | stm = Steem(keys=[active_wif]) 124 | wallet = Wallet("test", blockchain_instance=stm) 125 | wallet.stake(1, "BEE") 126 | """ 127 | token_in_wallet = self.get_token(symbol) 128 | if token_in_wallet is None: 129 | raise TokenNotInWallet("%s is not in wallet." % symbol) 130 | if float(token_in_wallet["balance"]) < float(amount): 131 | raise InsufficientTokenAmount("Only %.3f in wallet" % float(token_in_wallet["balance"])) 132 | token = Token(symbol, api=self.api) 133 | quant_amount = token.quantize(amount) 134 | if quant_amount <= decimal.Decimal("0"): 135 | raise InvalidTokenAmount("Amount to stake is below token precision of %d" % token["precision"]) 136 | if receiver is None: 137 | receiver = self.account 138 | else: 139 | _ = Account(receiver, blockchain_instance=self.blockchain) 140 | contract_payload = {"symbol":symbol.upper(),"to": receiver, "quantity":str(quant_amount)} 141 | json_data = {"contractName":"tokens","contractAction":"stake", 142 | "contractPayload":contract_payload} 143 | assert self.blockchain.is_hive 144 | tx = self.blockchain.custom_json(self.ssc_id, json_data, required_auths=[self.account]) 145 | return tx 146 | 147 | def unstake(self, amount, symbol): 148 | """Unstake a token. 149 | 150 | :param float amount: Amount to unstake 151 | :param str symbol: Token to unstake 152 | 153 | Unstake example: 154 | 155 | .. code-block:: python 156 | 157 | from hiveengine.wallet import Wallet 158 | from beem import Steem 159 | active_wif = "5xxxx" 160 | stm = Steem(keys=[active_wif]) 161 | wallet = Wallet("test", blockchain_instance=stm) 162 | wallet.unstake(1, "BEE") 163 | """ 164 | token_in_wallet = self.get_token(symbol) 165 | if token_in_wallet is None: 166 | raise TokenNotInWallet("%s is not in wallet." % symbol) 167 | if "stake" not in token_in_wallet: 168 | raise InsufficientTokenAmount("Token cannot be unstaked") 169 | if float(token_in_wallet["stake"]) < float(amount): 170 | raise InsufficientTokenAmount("Only %.3f are staked in the wallet" % float(token_in_wallet["stake"])) 171 | token = Token(symbol, api=self.api) 172 | quant_amount = token.quantize(amount) 173 | if quant_amount <= decimal.Decimal("0"): 174 | raise InvalidTokenAmount("Amount to stake is below token precision of %d" % token["precision"]) 175 | contract_payload = {"symbol":symbol.upper(),"quantity":str(quant_amount)} 176 | json_data = {"contractName":"tokens","contractAction":"unstake", 177 | "contractPayload":contract_payload} 178 | assert self.blockchain.is_hive 179 | tx = self.blockchain.custom_json(self.ssc_id, json_data, required_auths=[self.account]) 180 | return tx 181 | 182 | def cancel_unstake(self, trx_id): 183 | """Cancel unstaking a token. 184 | 185 | :param str trx_id: transaction id in which the tokan was unstaked 186 | 187 | Cancel unstake example: 188 | 189 | .. code-block:: python 190 | 191 | from hiveengine.wallet import Wallet 192 | from beem import Steem 193 | active_wif = "5xxxx" 194 | stm = Steem(keys=[active_wif]) 195 | wallet = Wallet("test", blockchain_instance=stm) 196 | wallet.stake("cf39ecb8b846f1efffb8db526fada21a5fcf41c3") 197 | """ 198 | contract_payload = {"txID":trx_id} 199 | json_data = {"contractName":"tokens","contractAction":"cancelUnstake", 200 | "contractPayload":contract_payload} 201 | assert self.blockchain.is_hive 202 | tx = self.blockchain.custom_json(self.ssc_id, json_data, required_auths=[self.account]) 203 | return tx 204 | 205 | def issue(self, to, amount, symbol): 206 | """Issues a specific token amount. 207 | 208 | :param str to: Recipient 209 | :param float amount: Amount to issue 210 | :param str symbol: Token to issue 211 | 212 | 213 | Issue example: 214 | 215 | .. code-block:: python 216 | 217 | from hiveengine.wallet import Wallet 218 | from beem import Steem 219 | active_wif = "5xxxx" 220 | stm = Steem(keys=[active_wif]) 221 | wallet = Wallet("test", blockchain_instance=stm) 222 | wallet.issue(1, "my_token") 223 | """ 224 | token = Token(symbol, api=self.api) 225 | if token["issuer"] != self.account: 226 | raise TokenIssueNotPermitted("%s is not the issuer of token %s" % (self.account, symbol)) 227 | 228 | if token["maxSupply"] == token["supply"]: 229 | raise MaxSupplyReached("%s has reached is maximum supply of %d" % (symbol, token["maxSupply"])) 230 | quant_amount = token.quantize(amount) 231 | if quant_amount <= decimal.Decimal("0"): 232 | raise InvalidTokenAmount("Amount to issue is below token precision of %d" % token["precision"]) 233 | check_to = Account(to, blockchain_instance=self.blockchain) 234 | contract_payload = {"symbol":symbol.upper(),"to":to,"quantity":str(quant_amount)} 235 | json_data = {"contractName":"tokens","contractAction":"issue", 236 | "contractPayload":contract_payload} 237 | assert self.blockchain.is_hive 238 | tx = self.blockchain.custom_json(self.ssc_id, json_data, required_auths=[self.account]) 239 | return tx 240 | 241 | def get_history(self, symbol, limit=1000, offset=0): 242 | """Returns the transfer history of a token""" 243 | return self.api.get_history(self.account, symbol, limit, offset) 244 | 245 | def get_buy_book(self, symbol=None, limit=100, offset=0): 246 | """Returns the buy book for the wallet account. When symbol is set, 247 | the order book from the given token is shown. 248 | """ 249 | if symbol is None: 250 | buy_book = self.api.find("market", "buyBook", query={"account": self.account}, limit=limit, offset=offset) 251 | else: 252 | buy_book = self.api.find("market", "buyBook", query={"symbol": symbol, "account": self.account}, limit=limit, offset=offset) 253 | return buy_book 254 | 255 | def get_sell_book(self, symbol=None, limit=100, offset=0): 256 | """Returns the sell book for the wallet account. When symbol is set, 257 | the order book from the given token is shown. 258 | """ 259 | if symbol is None: 260 | sell_book = self.api.find("market", "sellBook", query={"account": self.account}, limit=limit, offset=offset) 261 | else: 262 | sell_book = self.api.find("market", "sellBook", query={"symbol": symbol, "account": self.account}, limit=limit, offset=offset) 263 | return sell_book 264 | -------------------------------------------------------------------------------- /requirements-test.txt: -------------------------------------------------------------------------------- 1 | pip 2 | setuptools 3 | wheel 4 | future==0.16.0 5 | requests>=2.20.0 6 | pytz==2018.5 7 | mock==2.0.0 8 | appdirs==1.4.3 9 | Click==7.0 10 | prettytable 11 | pycodestyle==2.4.0 12 | pyflakes==2.0.0 13 | six==1.11.0 14 | pytest 15 | pytest-mock 16 | pytest-cov 17 | coverage 18 | parameterized 19 | tox 20 | codacy-coverage 21 | virtualenv 22 | codecov 23 | beem 24 | 25 | 26 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """Packaging logic for beem.""" 3 | import codecs 4 | import io 5 | import os 6 | import sys 7 | 8 | from setuptools import setup 9 | 10 | # Work around mbcs bug in distutils. 11 | # http://bugs.python.org/issue10945 12 | 13 | try: 14 | codecs.lookup('mbcs') 15 | except LookupError: 16 | ascii = codecs.lookup('ascii') 17 | codecs.register(lambda name, enc=ascii: {True: enc}.get(name == 'mbcs')) 18 | 19 | VERSION = '0.2.3' 20 | 21 | tests_require = ['mock >= 2.0.0', 'pytest', 'pytest-mock', 'parameterized'] 22 | 23 | requires = [ 24 | "beem", 25 | "requests", 26 | "six", 27 | ] 28 | 29 | 30 | def write_version_py(filename): 31 | """Write version.""" 32 | cnt = """\"""THIS FILE IS GENERATED FROM beem SETUP.PY.\""" 33 | version = '%(version)s' 34 | """ 35 | with open(filename, 'w') as a: 36 | a.write(cnt % {'version': VERSION}) 37 | 38 | 39 | def get_long_description(): 40 | """Generate a long description from the README file.""" 41 | descr = [] 42 | for fname in ('README.md',): 43 | with io.open(fname, encoding='utf-8') as f: 44 | descr.append(f.read()) 45 | return '\n\n'.join(descr) 46 | 47 | 48 | if __name__ == '__main__': 49 | 50 | # Rewrite the version file everytime 51 | write_version_py('hiveengine/version.py') 52 | 53 | setup( 54 | name='hiveengine', 55 | version=VERSION, 56 | description='command line tool and python library for sending hive engine tokens', 57 | long_description=get_long_description(), 58 | author='Holger Nahrstaedt', 59 | author_email='nahrstaedt@gmail.com', 60 | maintainer='Holger Nahrstaedt', 61 | maintainer_email='nahrstaedt@gmail.com', 62 | keywords=['hive', 'tools', 'nft'], 63 | packages=[ 64 | "hiveengine", 65 | ], 66 | classifiers=[ 67 | 'License :: OSI Approved :: MIT License', 68 | 'Operating System :: OS Independent', 69 | 'Programming Language :: Python', 70 | 'Programming Language :: Python :: 3', 71 | 'Programming Language :: Python :: 3.5', 72 | 'Programming Language :: Python :: 3.6', 73 | 'Programming Language :: Python :: 3.7', 74 | 'Programming Language :: Python :: 3.8', 75 | 'Development Status :: 4 - Beta', 76 | 'Intended Audience :: Developers', 77 | ], 78 | install_requires=requires, 79 | entry_points={ 80 | 'console_scripts': [ 81 | 'hiveengine=hiveengine.cli:cli', 82 | ], 83 | }, 84 | include_package_data=True, 85 | ) 86 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | """Unit tests.""" 2 | -------------------------------------------------------------------------------- /tests/test_api.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | from __future__ import division 3 | from __future__ import print_function 4 | from __future__ import unicode_literals 5 | from builtins import range 6 | from builtins import super 7 | import unittest 8 | from hiveengine.api import Api 9 | 10 | 11 | class Testcases(unittest.TestCase): 12 | def test_api(self): 13 | api = Api() 14 | result = api.get_latest_block_info() 15 | self.assertTrue(len(result) > 0) 16 | 17 | result = api.get_block_info(1910) 18 | self.assertTrue(len(result) > 0) 19 | 20 | result = api.get_transaction_info("78aea60cdc4477cdf9437d8224e34c6033499169") 21 | self.assertTrue(len(result) > 0) 22 | 23 | result = api.get_contract("tokens") 24 | self.assertTrue(len(result) > 0) 25 | 26 | result = api.find("tokens", "tokens") 27 | self.assertTrue(len(result) > 0) 28 | 29 | result = api.find_one("tokens", "tokens") 30 | self.assertTrue(len(result) > 0) 31 | 32 | # result = api.get_history("holger80", "FOODIE") 33 | # self.assertTrue(len(result) > 0) 34 | -------------------------------------------------------------------------------- /tests/test_rpc.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | from __future__ import division 3 | from __future__ import print_function 4 | from __future__ import unicode_literals 5 | from builtins import range 6 | from builtins import super 7 | import unittest 8 | from hiveengine.rpc import RPC 9 | 10 | 11 | class Testcases(unittest.TestCase): 12 | def test_rpc_blockchain(self): 13 | rpc = RPC() 14 | result = rpc.getLatestBlockInfo(endpoint="blockchain") 15 | self.assertTrue(len(result) > 0) 16 | 17 | def test_rpc_contract(self): 18 | rpc = RPC() 19 | result = rpc.getContract({"name": "token"}, endpoint="contracts") 20 | self.assertTrue(len(result) > 0) 21 | -------------------------------------------------------------------------------- /tests/test_tokenobject.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | from __future__ import division 3 | from __future__ import print_function 4 | from __future__ import unicode_literals 5 | import unittest 6 | from hiveengine.tokenobject import Token 7 | 8 | 9 | class Testcases(unittest.TestCase): 10 | def test_token(self): 11 | eng = Token("BEE") 12 | self.assertTrue(eng is not None) 13 | self.assertTrue(eng["symbol"] == "BEE") 14 | -------------------------------------------------------------------------------- /tests/test_tokens.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | from __future__ import division 3 | from __future__ import print_function 4 | from __future__ import unicode_literals 5 | import unittest 6 | from hiveengine.tokens import Tokens 7 | 8 | 9 | class Testcases(unittest.TestCase): 10 | def test_tokens(self): 11 | tokens = Tokens() 12 | self.assertTrue(tokens is not None) 13 | self.assertTrue(len(tokens) > 0) 14 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py{27,34,35,36,37} 3 | skip_missing_interpreters = true 4 | 5 | [testenv] 6 | deps = 7 | -rrequirements-test.txt 8 | commands = 9 | coverage run --parallel-mode -m pytest {posargs} 10 | coverage combine 11 | coverage report -m 12 | coverage xml 13 | 14 | [testenv:flake8] 15 | deps= 16 | flake8 17 | # flake8-docstrings>=0.2.7 18 | # flake8-import-order>=0.9 19 | # pep8-naming 20 | # flake8-colors 21 | commands= 22 | flake8 steemengine setup.py 23 | 24 | [testenv:pylint] 25 | deps= 26 | pyflakes 27 | pylint 28 | commands= 29 | pylint steemengine 30 | 31 | -------------------------------------------------------------------------------- /util/appveyor/build.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | :: To build extensions for 64 bit Python 3, we need to configure environment 3 | :: variables to use the MSVC 2010 C++ compilers from GRMSDKX_EN_DVD.iso of: 4 | :: MS Windows SDK for Windows 7 and .NET Framework 4 5 | :: 6 | :: More details at: 7 | :: https://packaging.python.org/appveyor/ 8 | 9 | IF "%DISTUTILS_USE_SDK%"=="1" ( 10 | ECHO Configuring environment to build with MSVC on a 64bit architecture 11 | ECHO Using Windows SDK 7.1 12 | "C:\Program Files\Microsoft SDKs\Windows\v7.1\Setup\WindowsSdkVer.exe" -q -version:v7.1 13 | CALL "C:\Program Files\Microsoft SDKs\Windows\v7.1\Bin\SetEnv.cmd" /x64 /release 14 | SET MSSdk=1 15 | REM Need the following to allow tox to see the SDK compiler 16 | SET TOX_TESTENV_PASSENV=DISTUTILS_USE_SDK MSSdk INCLUDE LIB 17 | ) ELSE ( 18 | ECHO Using default MSVC build environment 19 | ) 20 | 21 | CALL %* -------------------------------------------------------------------------------- /util/package-linux.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | COMM_TAG=$(git describe --tags $(git rev-list --tags --max-count=1)) 3 | COMM_COUNT=$(git rev-list --count HEAD) 4 | BUILD="steemengine-${COMM_TAG}-${COMM_COUNT}_linux.tar.gz" 5 | 6 | 7 | rm -rf dist build locale 8 | pip install 9 | python setup.py clean 10 | python setup.py build_ext 11 | # python setup.py build_locales 12 | pip install pyinstaller 13 | pyinstaller steemengine-onedir.spec 14 | 15 | cd dist 16 | 17 | tar -zcvf ${BUILD} steemengine 18 | if [ -n "$UPLOAD_LINUX" ] 19 | then 20 | curl --upload-file ${BUILD} https://transfer.sh/ 21 | # Required for a newline between the outputs 22 | echo -e "\n" 23 | md5sum ${BUILD} 24 | echo -e "\n" 25 | sha256sum ${BUILD} 26 | fi 27 | -------------------------------------------------------------------------------- /util/package-osx.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | VERSION=$(python -c 'import beem; print(beem.__version__)') 3 | COMM_TAG=$(git describe --tags $(git rev-list --tags --max-count=1)) 4 | COMM_COUNT=$(git rev-list --count HEAD) 5 | BUILD="steemengine-${COMM_TAG}-${COMM_COUNT}_osx.dmg" 6 | 7 | rm -rf dist build locale 8 | pip install 9 | python setup.py clean 10 | python setup.py build_ext 11 | # python setup.py build_locales 12 | pip install pyinstaller 13 | pyinstaller steemengine-onedir.spec 14 | 15 | cd dist 16 | ditto -rsrc --arch x86_64 'steemengine.app' 'steemengine.tmp' 17 | rm -r 'steemengine.app' 18 | mv 'steemengine.tmp' 'steemengine.app' 19 | hdiutil create -volname "steemengine $VERSION" -srcfolder 'steemengine.app' -ov -format UDBZ "$BUILD" 20 | if [ -n "$UPLOAD_OSX" ] 21 | then 22 | curl --upload-file "$BUILD" https://transfer.sh/ 23 | # Required for a newline between the outputs 24 | echo -e "\n" 25 | md5 -r "$BUILD" 26 | echo -e "\n" 27 | shasum -a 256 "$BUILD" 28 | fi 29 | -------------------------------------------------------------------------------- /util/travis_osx_install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | brew update 3 | brew install ccache 4 | brew outdated openssl || brew upgrade openssl 5 | brew install openssl@1.1 6 | 7 | export CFLAGS="-I$(brew --prefix openssl)/include $CFLAGS" 8 | export LDFLAGS="-L$(brew --prefix openssl)/lib $LDFLAGS" 9 | 10 | # install pyenv 11 | git clone --depth 1 https://github.com/pyenv/pyenv ~/.pyenv 12 | PYENV_ROOT="$HOME/.pyenv" 13 | PATH="$PYENV_ROOT/bin:$PATH" 14 | eval "$(pyenv init -)" 15 | 16 | case "${TOXENV}" in 17 | py27) 18 | curl -O https://bootstrap.pypa.io/get-pip.py 19 | python get-pip.py --user 20 | ;; 21 | py33) 22 | pyenv install 3.3.6 23 | pyenv global 3.3.6 24 | ;; 25 | py34) 26 | pyenv install 3.4.6 27 | pyenv global 3.4.6 28 | ;; 29 | py35) 30 | pyenv install 3.5.3 31 | pyenv global 3.5.3 32 | ;; 33 | py36) 34 | pyenv install 3.6.1 35 | pyenv global 3.6.1 36 | ;; 37 | pypy*) 38 | pyenv install "$PYPY_VERSION" 39 | pyenv global "$PYPY_VERSION" 40 | ;; 41 | pypy3) 42 | pyenv install pypy3-2.4.0 43 | pyenv global pypy3-2.4.0 44 | ;; 45 | docs) 46 | brew install enchant 47 | curl -O https://bootstrap.pypa.io/get-pip.py 48 | python get-pip.py --user 49 | ;; 50 | esac 51 | pyenv rehash 52 | python -m pip install --user virtualenv 53 | python -m virtualenv ~/.venv 54 | source ~/.venv/bin/activate 55 | # This coverage pin must be kept in sync with tox.ini 56 | pip install --upgrade pip 57 | pip install --upgrade wheel 58 | pip install tox 59 | pip install delocate 60 | --------------------------------------------------------------------------------