├── .env ├── .flake8 ├── .github ├── ISSUE_TEMPLATE.md └── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── .gitignore ├── .travis.yml ├── AUTHORS ├── CHANGELOG.md ├── LICENSE.txt ├── Makefile ├── README.md ├── aecli ├── aeternity ├── __init__.py ├── __main__.py ├── aens.py ├── channel.py ├── compiler.py ├── contract.py ├── contract_native.py ├── defaults.py ├── exceptions.py ├── hashing.py ├── hdwallet.py ├── identifiers.py ├── node.py ├── openapi.py ├── oracles.py ├── signing.py ├── transactions.py └── utils.py ├── command-line-tool.md ├── docker-compose.yml ├── docker ├── accounts.json ├── aeternity.yaml ├── keys │ └── node │ │ ├── peer_key │ │ ├── peer_key.pub │ │ ├── sign_key │ │ └── sign_key.pub └── wait-for-it.sh ├── docs ├── Makefile ├── OLD │ ├── README.md │ ├── ga.md │ └── keystore_format_change.md ├── README.md ├── assets │ └── images │ │ └── faucet.png ├── command_line_client.md ├── conf.py ├── faq │ └── index.rst ├── howto │ ├── accounts_signatures.rst │ ├── amounts.rst │ ├── cli_accounts.rst │ ├── delegate_signatures.rst │ ├── index.rst │ └── validate_contract_bytecode.rst ├── index.rst ├── intro │ ├── contributing.rst │ ├── index.rst │ ├── install.rst │ ├── tutorial01-spend.rst │ ├── tutorial02-contracts-call.rst │ ├── tutorial02-contracts-deploy.rst │ ├── tutorial03-aens.rst │ ├── tutorial04-ga.rst │ ├── tutorial05-cli.rst │ ├── tutorial06-hdwallet.rst │ └── tutorial07-offline.rst ├── library.md ├── make.bat ├── mkdir ├── ref │ ├── client_and_config.rst │ ├── constants.rst │ ├── index.rst │ ├── txbuilder.rst │ ├── txobject.rst │ └── txsigner.rst ├── snippets │ ├── accounts.py │ └── index.rst ├── topics │ ├── index.rst │ ├── install.rst │ └── keystore_format_change.rst ├── utilities.md └── utils.md ├── examples ├── aens.md └── index.md ├── poetry.lock ├── pyproject.toml ├── requirements.txt └── tests ├── __init__.py ├── conftest.py ├── test_aens.py ├── test_api.py ├── test_channels.py ├── test_cli.py ├── test_compiler.py ├── test_contract_lima.py ├── test_contract_native.py ├── test_hashing.py ├── test_hd_wallet.py ├── test_node.py ├── test_node_delegate_sig.py ├── test_openapi.py ├── test_oracle.py ├── test_signing.py ├── test_sophia_transformation.py ├── test_transactions.py ├── test_tutorial01-spend.py ├── test_tutorial05-contracts.py ├── test_tutorial06-hdwallet.py ├── test_tutorial07-offline.py ├── test_utils.py └── testdata ├── CryptoHamster.aes ├── identity.aes ├── identity.aes.aci.json ├── identity.aes.bin ├── keystore.invalid.json ├── keystore.json ├── simplestorage.aes └── simplestorage.aes.bin /.env: -------------------------------------------------------------------------------- 1 | # this is used by docker-compose.yml to for the node image tag 2 | NODE_TAG=v5.0.1 3 | COMPILER_TAG=v4.1.0 4 | -------------------------------------------------------------------------------- /.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | max-line-length = 160 3 | exclude = tests/*,private/*,dist/*,build/*,docs/* 4 | 5 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | **Note: for support questions, please use the [forum](https://forum.aeternity.com)**. This repository's issues are reserved for feature requests and bug reports. 2 | 3 | * **I'm submitting a ...** 4 | - [ ] bug report 5 | - [ ] feature request 6 | - [ ] support request => Please do not submit support request here, see note at the top of this template. 7 | 8 | 9 | * **Do you want to request a *feature* or report a *bug*?** 10 | 11 | * **What is the current behavior?** 12 | 13 | * **If the current behavior is a bug, please provide the steps to reproduce and if possible a minimal demo of the problem** 14 | 15 | * **What is the expected behavior?** 16 | 17 | * **What is the motivation / use case for changing the behavior?** 18 | 19 | * **Please tell us about your environment:** 20 | 21 | - Node Version: v0.0.0 22 | - Protocol Version: 1 23 | - Compiler version: v0.0.0 24 | - VM Version: aevm | fate 25 | - SDK Version: v0.0.0 26 | - Python version: v3.7.0 27 | 28 | 29 | * **Other information** (e.g. detailed explanation, stacktraces, related issues, suggestions how to fix, links for us to have context, eg. forum, telegram, etc) 30 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug, triage 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Please tell us about your environment:** 27 | 28 | - Node Version: v0.0.0 29 | - Protocol Version: 1 30 | - Compiler version: v0.0.0 31 | - VM Version: aevm | fate 32 | - SDK Version: v0.0.0 33 | - Python version: v3.7.0 34 | 35 | **Other information** (e.g. detailed explanation, stack traces, related issues, suggestions how to fix, links for us to have context, eg. forum, telegram, etc) 36 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: feature, triage 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | venv 2 | .idea 3 | **/*.pyc 4 | .pytest_cache 5 | .vscode 6 | aepp_sdk.egg-info 7 | dist 8 | build 9 | coverage.xml 10 | docker-compose.override.yml 11 | test-results.xml 12 | .coverage 13 | private 14 | .DS_Store 15 | .envrc 16 | docs/_build 17 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | branches: 2 | only: 3 | - master 4 | - develop 5 | 6 | dist: xenial 7 | os: linux 8 | addons: 9 | apt: 10 | packages: 11 | - build-essential 12 | - curl 13 | - libltdl7 14 | - git 15 | - make 16 | 17 | services: 18 | - docker 19 | 20 | language: python 21 | python: 22 | - "3.7" 23 | - "3.8" 24 | 25 | env: 26 | global: 27 | - TEST_NODE=http://localhost:3013 28 | - TEST_URL=http://localhost:3013 29 | - TEST_DEBUG_URL=http://localhost:3113 30 | - TEST_NETWORK_ID=ae_devnet 31 | - COMPILER_URL=http://localhost:3080 32 | - FORCE_COMPATIBILITY=false 33 | 34 | cache: 35 | timeout: 604800 # 7 days 36 | directories: 37 | - $HOME/.cache/pip 38 | 39 | before_cache: 40 | - rm -f $HOME/.cache/pip/log/debug.log 41 | 42 | before_install: 43 | - docker-compose up -d node compiler 44 | 45 | install: 46 | - pip install -r requirements.txt 47 | - pip install codecov 48 | 49 | script: 50 | - make lint 51 | - make test 52 | - codecov 53 | 54 | 55 | jobs: 56 | include: 57 | - stage: Test with latest node and compiler 58 | if: type = "cron" 59 | env: 60 | - NODE_TAG=master 61 | - COMPILER_TAG=latest 62 | - FORCE_COMPATIBILITY=true 63 | script: 64 | - make lint 65 | - make test 66 | - codecov 67 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Tom Wallroth 2 | Andrea Giacobino -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | ISC License 2 | 3 | Copyright (c) 2018, aeternity developers 4 | 5 | Permission to use, copy, modify, and/or distribute this software for any 6 | purpose with or without fee is hereby granted, provided that the above 7 | copyright notice and this permission notice appear in all copies. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 10 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 11 | AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 12 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 13 | LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE 14 | OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 15 | PERFORMANCE OF THIS SOFTWARE. 16 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # build output folder 2 | DIST_FOLDER = dist 3 | BUILD_FOLDER = build 4 | # extra test options 5 | TEST_OPTS = '' 6 | # Documentation 7 | SPHINXOPTS = 8 | SPHINXBUILD = sphinx-build 9 | SOURCEDIR = docs 10 | BUILDDIR = docs/_build 11 | 12 | .PHONY: list 13 | list: 14 | @echo clean build lint test publish-test publish 15 | 16 | default: build 17 | 18 | build: build-dist 19 | 20 | build-dist: 21 | @echo build 22 | poetry build 23 | @echo done 24 | 25 | install: build 26 | @echo build and install the SDK 27 | python -m pip install dist/$(shell ls -tr dist | grep whl | tail -1) 28 | @echo installation completed 29 | 30 | test: test-all 31 | 32 | test-all: 33 | @echo run pytest 34 | pytest -v --junitxml test-results.xml tests --cov=aeternity --cov-config .coveragerc --cov-report xml:coverage.xml $(TEST_OPTS) 35 | @echo done 36 | 37 | lint: lint-all 38 | 39 | lint-all: 40 | @echo lint aeternity 41 | flake8 aeternity 42 | @echo done 43 | 44 | clean: 45 | @echo remove '$(DIST_FOLDER)','$(BUILD_FOLDER)' folders 46 | @rm -rf $(DIST_FOLDER) $(BUILD_FOLDER) 47 | @echo done 48 | 49 | publish: 50 | @echo publish on pypi.org 51 | poetry publish --build 52 | @echo done 53 | 54 | publish-test: 55 | @echo publish on test.pypi.org 56 | poetry config repositories.testpypi https://test.pypi.org/legacy/ 57 | poetry publish --build --repository testpypi 58 | @echo done 59 | 60 | deps: 61 | @echo generating requirements.txt 62 | poetry export --dev -f requirements.txt > requirements.txt 63 | @echo done 64 | 65 | changelog: 66 | @echo build changelog 67 | git-changelog -t keepachangelog -s angular . -o CHANGELOG.md 68 | @echo done 69 | 70 | docs-html: 71 | @echo build html documentation 72 | $(SPHINXBUILD) -M html "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 73 | @echo done 74 | 75 | docs-view: docs-html 76 | python -c "import webbrowser; webbrowser.open('docs/_build/html/index.html')" 77 | 78 | docs-lint: 79 | @echo lint documentation 80 | $(SPHINXBUILD) -M linkcheck "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 81 | @echo done 82 | 83 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # aepp-sdk-python 2 | 3 | [![Commitizen friendly](https://img.shields.io/badge/commitizen-friendly-brightgreen.svg)](http://commitizen.github.io/cz-cli/) 4 | [![Build Status](https://travis-ci.com/aeternity/aepp-sdk-python.svg?branch=develop)](http://travis-ci.com/aeternity/aepp-sdk-python?branch=develop) 5 | [![PyPI version](https://badge.fury.io/py/aepp-sdk.svg)](https://badge.fury.io/py/aepp-sdk) 6 | [![Language grade: Python](https://img.shields.io/lgtm/grade/python/g/aeternity/aepp-sdk-python.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/aeternity/aepp-sdk-python/context:python) 7 | [![Documentation Status](https://readthedocs.org/projects/aepp-sdk-python/badge/?version=latest)](https://aepp-sdk-python.readthedocs.io/en/latest/?badge=latest) 8 | 9 | Welcome to the [Aeternity](https://aeternity.com) SDK for Python 10 | 11 | For support visit the [forum](https://forum.aeternity.com), for bug reports or feature requests visit open an [issue](https://github.com/aeternity/aepp-sdk-python/issues). 12 | 13 | 14 | ### Documentation 15 | - [Stable](https://aepp-sdk-python.readthedocs.io/en/stable/) for the `master` branch (current release). 16 | - [Latest](https://aepp-sdk-python.readthedocs.io/en/latest/) for the `develop` branch 17 | 18 | ### Relevant repositories 19 | - [Aeternity node](https://github.com/aeternity/aeternity) 20 | - [Protocol documentation](https://github.com/aeternity/protocol) 21 | 22 | ### Useful links 23 | - [Mainnet API Gateway](https://mainnet.aeternity.io/v2/status) 24 | - [Mainnet frontend and middleware](https://mainnet.aeternal.io) (Aeternal) 25 | - [Testnet API Gateway](https://mainnet.aeternity.io/v2/status) 26 | - [Testnet frontend and middleware](https://testnet.aeternal.io) (Aeternal) 27 | - [Testnet faucet](https://testnet.faucet.aepps.com) 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /aecli: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from aeternity import __main__ 4 | 5 | __main__.run() 6 | -------------------------------------------------------------------------------- /aeternity/__init__.py: -------------------------------------------------------------------------------- 1 | import pkg_resources 2 | import logging 3 | 4 | __node_compatibility__ = (">=5.0.0", "<=6.0.0") 5 | __compiler_compatibility__ = (">=4.1.0", "<5.0.0") 6 | 7 | # initialize logging 8 | logging.basicConfig(level=logging.ERROR) 9 | 10 | 11 | def _version(): 12 | try: 13 | return pkg_resources.get_distribution('aepp-sdk').version 14 | except pkg_resources.DistributionNotFound: 15 | return 'snapshot' 16 | -------------------------------------------------------------------------------- /aeternity/compiler.py: -------------------------------------------------------------------------------- 1 | from aeternity import exceptions, __compiler_compatibility__ 2 | from aeternity import utils, hashing, openapi, identifiers 3 | 4 | from munch import Munch 5 | import semver 6 | from deprecated import deprecated 7 | 8 | 9 | class CompilerError(exceptions.AException): 10 | """ 11 | Throw when the compiler apis return an error 12 | """ 13 | pass 14 | 15 | 16 | class CompilerClient(object): 17 | """ 18 | The compiler client to interact with the aeternity http compiler 19 | """ 20 | 21 | def __init__(self, compiler_url='http://localhost:3080', **kwargs): 22 | self.compiler_url = compiler_url 23 | self.compiler_cli = openapi.OpenAPICli(compiler_url, compatibility_version_range=__compiler_compatibility__) 24 | # default backend set to FATE 25 | self.compiler_options = { 26 | "backend": kwargs.get("backend", identifiers.COMPILER_OPTIONS_BACKEND_FATE) 27 | } 28 | 29 | def set_option(self, name, value): 30 | self.compiler_options[name] = value 31 | 32 | def get_version(self): 33 | if self.version is None: 34 | self.version = self.compiler_cli.version().version 35 | return self.version 36 | 37 | def compile(self, source_code, compiler_options={}): 38 | compiler_options = compiler_options if len(compiler_options) > 0 else self.compiler_options 39 | body = dict( 40 | code=source_code, 41 | options=compiler_options 42 | ) 43 | return self.compiler_cli.compile_contract(body=body) 44 | 45 | def aci(self, source_code, compiler_options={}): 46 | compiler_options = compiler_options if len(compiler_options) > 0 else self.compiler_options 47 | body = dict( 48 | code=source_code, 49 | options=compiler_options 50 | ) 51 | return self.compiler_cli.generate_aci(body=body) 52 | 53 | def encode_calldata(self, contract_src, function_name, *arguments, compiler_options={}): 54 | """ 55 | Encode calldata to be used to deploy or call a contract 56 | :param contract_src: the source code of the contract 57 | :param function_name: the name of the function to encode the calldata for 58 | :param arguments: the list of arguments of the funcition 59 | :compiler_options: to override the global compiler options 60 | """ 61 | compiler_options = compiler_options if len(compiler_options) > 0 else self.compiler_options 62 | body = dict( 63 | source=contract_src, 64 | function=function_name, 65 | arguments=[str(v) for v in arguments], 66 | options=compiler_options 67 | ) 68 | return self.compiler_cli.encode_calldata(body=body) 69 | 70 | @deprecated(reason="This method has been deprecated in favour of decode_call_result") 71 | def decode_data(self, sophia_type, encoded_data): 72 | body = { 73 | "data": encoded_data, 74 | "sophia-type": sophia_type 75 | } 76 | return self.compiler_cli.decode_data(body=body) 77 | 78 | def decode_call_result(self, source, function, call_value, call_result="ok", compiler_options={}): 79 | body = { 80 | "source": source, 81 | "function": function, 82 | "call-result": call_result, 83 | "call-value": call_value, 84 | "options": compiler_options 85 | } 86 | return self.compiler_cli.decode_call_result(body=body) 87 | 88 | def decode_calldata_with_bytecode(self, bytecode, encoded_calldata, compiler_options={}): 89 | compiler_options = compiler_options if len(compiler_options) > 0 else self.compiler_options 90 | body = { 91 | "calldata": encoded_calldata, 92 | "bytecode": bytecode, 93 | "backend": compiler_options.get("backend", self.compiler_options.get("backend", identifiers.COMPILER_OPTIONS_BACKEND_FATE)) 94 | } 95 | return self.compiler_cli.decode_calldata_bytecode(body=body) 96 | 97 | def decode_calldata_with_sourcecode(self, sourcecode, function, encoded_calldata, compiler_options={}): 98 | compiler_options = compiler_options if len(compiler_options) > 0 else self.compiler_options 99 | body = { 100 | "source": sourcecode, 101 | "function": function, 102 | "calldata": encoded_calldata, 103 | "options": compiler_options 104 | } 105 | return self.compiler_cli.decode_calldata_source(body=body) 106 | 107 | def validate_bytecode(self, sourcecode, bytecode, compiler_options={}): 108 | if semver.match(self.compiler_cli.version().version, ">=4.1.0"): 109 | compiler_options = compiler_options if len(compiler_options) > 0 else self.compiler_options 110 | body = { 111 | "source": sourcecode, 112 | "bytecode": bytecode, 113 | "options": compiler_options 114 | } 115 | result = self.compiler_cli.validate_byte_code(body=body) 116 | if result != {}: 117 | raise CompilerError(result["message"]) 118 | return result 119 | 120 | @staticmethod 121 | def decode_bytecode(compiled): 122 | """ 123 | Decode an encoded contract to it's components 124 | :param compiled: the encoded bytecode to decode as got from the 'compile' function 125 | :return: a named tuple with a decoded contract 126 | """ 127 | if isinstance(compiled, str): 128 | if not utils.prefix_match(identifiers.BYTECODE, compiled): 129 | raise ValueError(f"Invalid input, expecting {identifiers.BYTECODE}_ prefix") 130 | # unpack the transaction 131 | raw_contract = hashing.decode_rlp(compiled) 132 | elif isinstance(compiled, bytes): 133 | # unpack the transaction 134 | raw_contract = hashing.decode_rlp(compiled) 135 | else: 136 | raise ValueError(f"Invalid input type") 137 | 138 | if not isinstance(raw_contract, list) or len(raw_contract) < 6: 139 | raise ValueError(f"Invalid contract structure") 140 | 141 | # print(raw_contract) 142 | tag = hashing._int_decode(raw_contract[0]) 143 | vsn = hashing._int_decode(raw_contract[1]) 144 | if tag != identifiers.OBJECT_TAG_SOPHIA_BYTE_CODE: 145 | raise ValueError(f"Invalid input, expecting object type {identifiers.OBJECT_TAG_SOPHIA_BYTE_CODE}, got {tag}") 146 | # this is the hash 147 | contract_data = dict( 148 | raw=raw_contract, 149 | tag=tag, 150 | vsn=vsn, 151 | src_hash=raw_contract[2], 152 | type_info=[], 153 | bytecode=raw_contract[4], 154 | compiler_version=hashing._binary_decode(raw_contract[5], str), 155 | # payable=raw_contract[6] 156 | ) 157 | # print(type_info) 158 | for t in raw_contract[3]: 159 | contract_data["type_info"].append(dict( 160 | fun_hash=t[0], 161 | fun_name=hashing._binary_decode(t[1], str), 162 | arg_type=t[2], 163 | out_type=t[3], 164 | )) 165 | return Munch.fromDict(contract_data) 166 | -------------------------------------------------------------------------------- /aeternity/defaults.py: -------------------------------------------------------------------------------- 1 | from aeternity import identifiers 2 | 3 | # fee calculation 4 | BASE_GAS = 15000 5 | GAS_PER_BYTE = 20 6 | GAS_PRICE = 1000000000 7 | # default relative ttl in number of blocks for executing transaction on the chain 8 | MAX_TX_TTL = 256 9 | TX_TTL = 0 10 | FEE = 0 11 | # contracts 12 | CONTRACT_GAS = 10000 13 | CONTRACT_GAS_PRICE = 1000000000 14 | CONTRACT_DEPOSIT = 0 15 | CONTRACT_AMOUNT = 0 16 | # https://github.com/aeternity/protocol/blob/master/oracles/oracles.md#technical-aspects-of-oracle-operations 17 | ORACLE_VM_VERSION = identifiers.NO_VM 18 | ORACLE_TTL_TYPE = identifiers.ORACLE_TTL_TYPE_DELTA 19 | ORACLE_QUERY_FEE = 0 20 | ORACLE_TTL_VALUE = 500 21 | ORACLE_QUERY_TTL_VALUE = 10 22 | ORACLE_RESPONSE_TTL_VALUE = 10 23 | # Chain 24 | KEY_BLOCK_INTERVAL = 3 # average time between key-blocks in minutes 25 | KEY_BLOCK_CONFIRMATION_NUM = 3 # number of key blocks to wait for to consider a key-block confirmed 26 | # network id 27 | NETWORK_ID = identifiers.NETWORK_ID_MAINNET 28 | # TUNING 29 | POLL_TX_MAX_RETRIES = 8 # used in exponential backoff when checking a transaction 30 | POLL_TX_RETRIES_INTERVAL = 2 # in seconds 31 | POLL_BLOCK_MAX_RETRIES = 20 # number of retries 32 | POLL_BLOCK_RETRIES_INTERVAL = 30 # in seconds 33 | # channels 34 | CHANNEL_ENDPOINT = 'channel' 35 | CHANNEL_URL = 'ws://127.0.0.1:3014' 36 | # Generalized accounts 37 | GA_AUTH_FUNCTION = "authorize" 38 | GA_MAX_AUTH_FUN_GAS = 50000 39 | GA_ACCOUNTS_NONCE = 0 # for tx in ga transactions the nonce must be 0 40 | # Aens 41 | # max number of block into the future that the name is going to be available 42 | # https://github.com/aeternity/protocol/blob/master/AENS.md#aens-entry 43 | NAME_MAX_TTL = 50000 # in blocks 44 | NAME_MAX_CLIENT_TTL = 84600 # in seconds 45 | NAME_FEE = 0 46 | # see https://github.com/aeternity/aeternity/blob/72e440b8731422e335f879a31ecbbee7ac23a1cf/apps/aecore/src/aec_governance.erl#L67 47 | NAME_FEE_MULTIPLIER = 100000000000000 48 | NAME_FEE_BID_INCREMENT = 0.05 # the increment is in percentage 49 | # see https://github.com/aeternity/aeternity/blob/72e440b8731422e335f879a31ecbbee7ac23a1cf/apps/aecore/src/aec_governance.erl#L272 50 | NAME_BID_TIMEOUT_BLOCKS = 480 # ~1 day 51 | NAME_BID_MAX_LENGTH = 12 # this is the max length for a domain to be part of a bid 52 | # ref: https://github.com/aeternity/aeternity/blob/72e440b8731422e335f879a31ecbbee7ac23a1cf/apps/aecore/src/aec_governance.erl#L290 53 | # bid ranges: 54 | NAME_BID_RANGES = { 55 | 31: 3 * NAME_FEE_MULTIPLIER, 56 | 30: 5 * NAME_FEE_MULTIPLIER, 57 | 29: 8 * NAME_FEE_MULTIPLIER, 58 | 28: 13 * NAME_FEE_MULTIPLIER, 59 | 27: 21 * NAME_FEE_MULTIPLIER, 60 | 26: 34 * NAME_FEE_MULTIPLIER, 61 | 25: 55 * NAME_FEE_MULTIPLIER, 62 | 24: 89 * NAME_FEE_MULTIPLIER, 63 | 23: 144 * NAME_FEE_MULTIPLIER, 64 | 22: 233 * NAME_FEE_MULTIPLIER, 65 | 21: 377 * NAME_FEE_MULTIPLIER, 66 | 20: 610 * NAME_FEE_MULTIPLIER, 67 | 19: 987 * NAME_FEE_MULTIPLIER, 68 | 18: 1597 * NAME_FEE_MULTIPLIER, 69 | 17: 2584 * NAME_FEE_MULTIPLIER, 70 | 16: 4181 * NAME_FEE_MULTIPLIER, 71 | 15: 6765 * NAME_FEE_MULTIPLIER, 72 | 14: 10946 * NAME_FEE_MULTIPLIER, 73 | 13: 17711 * NAME_FEE_MULTIPLIER, 74 | 12: 28657 * NAME_FEE_MULTIPLIER, 75 | 11: 46368 * NAME_FEE_MULTIPLIER, 76 | 10: 75025 * NAME_FEE_MULTIPLIER, 77 | 9: 121393 * NAME_FEE_MULTIPLIER, 78 | 8: 196418 * NAME_FEE_MULTIPLIER, 79 | 7: 317811 * NAME_FEE_MULTIPLIER, 80 | 6: 514229 * NAME_FEE_MULTIPLIER, 81 | 5: 832040 * NAME_FEE_MULTIPLIER, 82 | 4: 1346269 * NAME_FEE_MULTIPLIER, 83 | 3: 2178309 * NAME_FEE_MULTIPLIER, 84 | 2: 3524578 * NAME_FEE_MULTIPLIER, 85 | 1: 5702887 * NAME_FEE_MULTIPLIER, 86 | } 87 | 88 | # ref: https://github.com/aeternity/aeternity/blob/72e440b8731422e335f879a31ecbbee7ac23a1cf/apps/aecore/src/aec_governance.erl#L273 89 | # name bid timeouts 90 | NAME_BID_TIMEOUTS = { 91 | 13: 0, 92 | 8: 1 * NAME_BID_TIMEOUT_BLOCKS, # 480 blocks 93 | 4: 31 * NAME_BID_TIMEOUT_BLOCKS, # 14880 blocks 94 | 1: 62 * NAME_BID_TIMEOUT_BLOCKS, # 29760 blocks 95 | } 96 | 97 | # dry run 98 | DRY_RUN_ADDRESS = "ak_11111111111111111111111111111111273Yts" 99 | DRY_RUN_AMOUNT = 100000000000000000000000000000000000 100 | -------------------------------------------------------------------------------- /aeternity/exceptions.py: -------------------------------------------------------------------------------- 1 | class AException(Exception): 2 | def __init__(self, *args, payload=None): 3 | super().__init__(*args) 4 | self.payload = payload 5 | 6 | def __str__(self): 7 | return super().__str__() + '\npayload\n' + str(self.payload) 8 | 9 | 10 | class AENSException(AException): 11 | pass 12 | 13 | 14 | class MissingPreclaim(AENSException): 15 | pass 16 | 17 | 18 | class PreclaimFailed(AENSException): 19 | pass 20 | 21 | 22 | class NameTooEarlyClaim(Exception): 23 | pass 24 | 25 | 26 | class NameCommitmentIdMismatch(Exception): 27 | """ Raised when a commitment id cannot be verified """ 28 | pass 29 | 30 | 31 | class ClaimFailed(AENSException): 32 | pass 33 | 34 | 35 | class NameNotAvailable(AENSException): 36 | pass 37 | 38 | 39 | class NameUpdateError(Exception): 40 | pass 41 | 42 | 43 | class InsufficientFundsException(AException): 44 | pass 45 | 46 | 47 | class TransactionNotFoundException(AException): 48 | pass 49 | 50 | 51 | class TransactionHashMismatch(AException): 52 | """Raised when the computed transaction hash differs from the one retrieved by the chain""" 53 | pass 54 | 55 | 56 | class TransactionWaitTimeoutExpired(AException): 57 | """Raised when a transaction hasn't been found after waiting for an amount of time""" 58 | 59 | def __init__(self, tx_hash, reason): 60 | AENSException.__init__(self) 61 | self.tx_hash = tx_hash 62 | self.reason = reason 63 | 64 | 65 | class BlockWaitTimeoutExpired(Exception): 66 | """Raised when a block height hasn't been reached after waiting for an amount of time""" 67 | pass 68 | 69 | 70 | class UnsupportedNodeVersion(Exception): 71 | """Raised when the node target runs an unsupported version""" 72 | 73 | 74 | class ConfigException(Exception): 75 | """Raised in case of configuration errors""" 76 | pass 77 | 78 | 79 | class UnsupportedTransactionType(Exception): 80 | """Raised for unknow transaction tag""" 81 | pass 82 | 83 | 84 | class TransactionFeeTooLow(Exception): 85 | """Raised for transaction fee with too low value""" 86 | pass 87 | -------------------------------------------------------------------------------- /aeternity/hdwallet.py: -------------------------------------------------------------------------------- 1 | import hmac 2 | import hashlib 3 | import json 4 | from nacl.signing import SigningKey 5 | from mnemonic import Mnemonic 6 | from aeternity.signing import Account, keystore_seal, keystore_open, save_keystore_to_file, save_keystore_to_folder 7 | from aeternity.identifiers import SECRET_TYPE_BIP39 8 | 9 | 10 | class HDWallet(): 11 | 12 | HARDENED_OFFSET = 0x80000000 13 | AETERNITY_DERIVATION_PATH = "m/44'/457'/%d'/0'/%d'" 14 | MNEMONIC = Mnemonic(language="english") 15 | 16 | def __init__(self, mnemonic=None): 17 | if mnemonic is None: 18 | mnemonic = self.generate_mnemonic() 19 | if not self.MNEMONIC.check(mnemonic): 20 | raise ValueError("Invalid Mnemonic") 21 | self.mnemonic = mnemonic 22 | self.master_key = self._master_key_from_mnemonic(mnemonic) 23 | self.master_account = HDWallet._get_account(self.master_key["secret_key"]) 24 | self.account_index = 0 25 | 26 | @staticmethod 27 | def generate_mnemonic(): 28 | """ 29 | Generate mnemonic 30 | """ 31 | return HDWallet.MNEMONIC.generate() 32 | 33 | def derive_child(self, account_index=None, address_index=0): 34 | """ 35 | Derives the account using the master key 36 | Args: 37 | account_index(int): Account index to use for derivation path (optional) 38 | address_index(int): Address index to use for derivation path (default: 0) 39 | Returns: 40 | derivation path and generated account 41 | """ 42 | if account_index is None: 43 | account_index = self.account_index 44 | self.account_index += 1 45 | derivation_path = self.AETERNITY_DERIVATION_PATH % (account_index, address_index) 46 | derived_keys = HDWallet._from_path(derivation_path, self.master_key) 47 | return derivation_path, HDWallet._get_account(derived_keys[-1]["secret_key"]) 48 | 49 | def get_master_key(self): 50 | """ 51 | Returns the master key 52 | """ 53 | return self.master_key 54 | 55 | def get_master_account(self): 56 | """ 57 | Returns the account generated using the master_key 58 | """ 59 | return self.master_account 60 | 61 | def mnemonic_to_keystore(self, password): 62 | return keystore_seal(self.mnemonic, password, secret_type=SECRET_TYPE_BIP39) 63 | 64 | def save_mnemonic_to_keystore_file(self, path, password): 65 | """ 66 | Utility method for save_to_keystore 67 | """ 68 | return save_keystore_to_file(path, self.mnemonic_to_keystore(password)) 69 | 70 | def save_mnemonic_to_keystore(self, path, password): 71 | """ 72 | Save an account in a Keystore/JSON format 73 | :param path: the folder where to store the keystore file 74 | :param password: the password for the keystore 75 | :return: the filename that has been used for the keystore 76 | """ 77 | return save_keystore_to_folder(path, self.mnemonic_to_keystore(password), self.master_account.get_address()) 78 | 79 | @staticmethod 80 | def from_keystore(path, password=""): 81 | with open(path) as fp: 82 | keystore = json.load(fp) 83 | if keystore.get("crypto", {}).get("secret_type") == SECRET_TYPE_BIP39: 84 | mnemonic = keystore_open(keystore, password).decode() 85 | return HDWallet(mnemonic) 86 | else: 87 | raise TypeError(f"Invalid keystore. Expected keystore of type {SECRET_TYPE_BIP39}") 88 | 89 | @staticmethod 90 | def _get_account(secret_key): 91 | """ 92 | Generate Account from secret key 93 | 94 | Args: 95 | secret_key(bytes): secret key in bytes 96 | Returns: 97 | Account instance with generated signing key and verifying key 98 | """ 99 | sign_key = SigningKey(secret_key) 100 | return Account(sign_key, sign_key.verify_key) 101 | 102 | @staticmethod 103 | def _master_key_from_mnemonic(mnemonic): 104 | """ 105 | Generate master key from a given mnemonic 106 | Args: 107 | mnemonic (str): a BIP39 compliant mnemonic string 108 | Returns: 109 | dict containing 'secret_key' and 'chain_code' 110 | """ 111 | return HDWallet._master_key_from_seed(Mnemonic.to_seed(mnemonic)) 112 | 113 | @staticmethod 114 | def _master_key_from_seed(seed): 115 | """ 116 | Generates a master key from a provided seed. 117 | 118 | Args: 119 | seed (str or bytes): a string of bytes or a hex string 120 | Returns: 121 | dict containing 'secret_key' and 'chain_code' 122 | """ 123 | seed = bytes.fromhex(seed) if type(seed) is str else seed 124 | I_hmac = hmac.new(b'ed25519 seed', seed, hashlib.sha512).digest() 125 | return {"secret_key": I_hmac[:32], "chain_code": I_hmac[32:]} 126 | 127 | @staticmethod 128 | def _parse_path(path): 129 | if isinstance(path, str): 130 | p = path.rstrip("/").split("/") 131 | elif isinstance(path, bytes): 132 | p = path.decode('utf-8').rstrip("/").split("/") 133 | else: 134 | p = list(path) 135 | 136 | return p 137 | 138 | @staticmethod 139 | def _derive_child_key(parent_key, i): 140 | if i & HDWallet.HARDENED_OFFSET: 141 | hmac_data = b'\x00' + parent_key["secret_key"] + i.to_bytes(length=4, byteorder='big') 142 | I_hmac = hmac.new(parent_key["chain_code"], hmac_data, hashlib.sha512).digest() 143 | return {"secret_key": I_hmac[:32], "chain_code": I_hmac[32:]} 144 | 145 | @staticmethod 146 | def _from_path(path, root_key, is_master=True): 147 | p = HDWallet._parse_path(path) 148 | 149 | if p[0] == "m": 150 | if is_master: 151 | p = p[1:] 152 | else: 153 | raise ValueError("root_key must be a master key if 'm' is the first element of the path.") 154 | 155 | keys = [root_key] 156 | for i in p: 157 | if isinstance(i, str): 158 | index = int(i[:-1]) | HDWallet.HARDENED_OFFSET 159 | else: 160 | index = i 161 | k = keys[-1] 162 | keys.append(HDWallet._derive_child_key(k, index)) 163 | 164 | return keys 165 | -------------------------------------------------------------------------------- /aeternity/oracles.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from aeternity import hashing, defaults 3 | from aeternity.identifiers import ORACLE_ID 4 | 5 | logger = logging.getLogger(__name__) 6 | 7 | 8 | class OracleQuery(): 9 | """ 10 | Create and execute a oracle query 11 | """ 12 | 13 | def __init__(self, client, oracle_id, id=None): 14 | self.oracle_id = oracle_id 15 | self.id = id 16 | self.client = client 17 | 18 | def execute(self, sender, query, 19 | query_fee=defaults.ORACLE_QUERY_FEE, 20 | query_ttl_type=defaults.ORACLE_TTL_TYPE, 21 | query_ttl_value=defaults.ORACLE_QUERY_TTL_VALUE, 22 | response_ttl_type=defaults.ORACLE_TTL_TYPE, 23 | response_ttl_value=defaults.ORACLE_RESPONSE_TTL_VALUE, 24 | fee=defaults.FEE, 25 | tx_ttl=defaults.TX_TTL): 26 | """ 27 | Execute a query to the oracle 28 | """ 29 | if self.oracle_id is None: 30 | raise ValueError("Oracle id must be provided before executing a query") 31 | # get the transaction builder 32 | txb = self.client.tx_builder 33 | # get the account nonce and ttl 34 | nonce, ttl = self.client._get_nonce_ttl(sender.get_address(), tx_ttl) 35 | # create spend_tx 36 | tx = txb.tx_oracle_query(self.oracle_id, sender.get_address(), query, 37 | query_fee, query_ttl_type, query_ttl_value, 38 | response_ttl_type, response_ttl_value, 39 | fee, ttl, nonce 40 | ) 41 | # sign the transaction 42 | tx_signed = self.client.sign_transaction(sender, tx) 43 | # post the transaction to the chain 44 | self.client.broadcast_transaction(tx_signed) 45 | # save the query id 46 | self.id = hashing.oracle_query_id(sender.get_address(), nonce, self.oracle_id) 47 | # return the transaction 48 | return tx_signed 49 | 50 | def get_response_object(self): 51 | # TODO: workaround for dashes in the parameter names 52 | return self.client.get_oracle_query_by_pubkey_and_query_id(**{"pubkey": self.oracle_id, "query-id": self.id}) 53 | 54 | 55 | class Oracle(): 56 | """ 57 | 58 | """ 59 | 60 | def __init__(self, client, oracle_id=None): 61 | self.client = client 62 | self.id = oracle_id 63 | self.query_id = None 64 | 65 | def register(self, account, query_format, response_format, 66 | query_fee=defaults.ORACLE_QUERY_FEE, 67 | ttl_type=defaults.ORACLE_TTL_TYPE, 68 | ttl_value=defaults.ORACLE_TTL_VALUE, 69 | vm_version=defaults.ORACLE_VM_VERSION, 70 | fee=defaults.FEE, 71 | tx_ttl=defaults.TX_TTL): 72 | """ 73 | Execute a registration of an oracle 74 | """ 75 | # get the transaction builder 76 | txb = self.client.tx_builder 77 | # get the account nonce and ttl 78 | nonce, ttl = self.client._get_nonce_ttl(account.get_address(), tx_ttl) 79 | # create spend_tx 80 | tx = txb.tx_oracle_register( 81 | account.get_address(), 82 | query_format, response_format, 83 | query_fee, ttl_type, ttl_value, 84 | vm_version, fee, ttl, nonce 85 | ) 86 | # sign the transaction 87 | tx_signed = self.client.sign_transaction(account, tx) 88 | # post the transaction to the chain 89 | self.client.broadcast_transaction(tx_signed) 90 | # register the oracle id 91 | # the oracle id is the account that register the oracle 92 | # with the prefix substituted by with ok_ 93 | self.id = f"{ORACLE_ID}_{account.get_address()[3:]}" 94 | # return the transaction 95 | return tx_signed 96 | 97 | def respond(self, account, query_id, response, 98 | response_ttl_type=defaults.ORACLE_TTL_TYPE, 99 | response_ttl_value=defaults.ORACLE_RESPONSE_TTL_VALUE, 100 | fee=defaults.FEE, 101 | tx_ttl=defaults.TX_TTL): 102 | """ 103 | Post a response to an oracle query 104 | """ 105 | if self.id is None: 106 | raise ValueError("Oracle id must be provided before respond to a query") 107 | # get the transaction builder 108 | txb = self.client.tx_builder 109 | # get the account nonce and ttl 110 | nonce, ttl = self.client._get_nonce_ttl(account.get_address(), tx_ttl) 111 | # create spend_tx 112 | tx = txb.tx_oracle_respond(self.id, query_id, response, 113 | response_ttl_type, response_ttl_value, 114 | fee, ttl, nonce 115 | ) 116 | # sign the transaction 117 | tx_signed = self.client.sign_transaction(account, tx) 118 | # post the transaction to the chain 119 | self.client.broadcast_transaction(tx_signed) 120 | # return the transaction 121 | return tx_signed 122 | 123 | def extend(self, account, query_id, response, 124 | ttl_type=defaults.ORACLE_TTL_TYPE, 125 | ttl_value=defaults.ORACLE_TTL_VALUE, 126 | fee=defaults.FEE, 127 | tx_ttl=defaults.TX_TTL): 128 | """ 129 | Extend the ttl of an oracle 130 | """ 131 | if self.id is None: 132 | raise ValueError("Oracle id must be provided before extending") 133 | # get the transaction builder 134 | txb = self.client.tx_builder 135 | # get the account nonce and ttl 136 | nonce, ttl = self.client._get_nonce_ttl(account.get_address(), tx_ttl) 137 | # create spend_tx 138 | tx = txb.tx_oracle_extend(self.id, ttl_type, ttl_value, fee, ttl, nonce) 139 | # sign the transaction 140 | tx_signed = self.client.sign_transaction(account, tx) 141 | # post the transaction to the chain 142 | self.client.broadcast_transaction(tx_signed) 143 | # return the transaction 144 | return tx_signed 145 | -------------------------------------------------------------------------------- /aeternity/utils.py: -------------------------------------------------------------------------------- 1 | import validators 2 | 3 | from decimal import Decimal 4 | from aeternity import hashing 5 | 6 | 7 | def is_valid_hash(hash_str: str, prefix: str = None) -> bool: 8 | """ 9 | Validate an aeternity hash, optionally restrict to a specific prefix. 10 | The validation will check if the hash parameter is of the form prefix_hash 11 | and that the hash is valid according to the decode function. 12 | :param hash_str: the hash to validate 13 | :param prefix: the prefix to restrict the validation to 14 | :return: true if it is valid false otherwise 15 | """ 16 | try: 17 | if hash_str is None: 18 | return False 19 | # decode the hash 20 | hashing.decode(hash_str) 21 | # if prefix is not set then is valid 22 | if prefix is None: 23 | return True 24 | # let's check the prefix 25 | if not isinstance(prefix, list): 26 | prefix = [prefix] 27 | # if a match is not found then raise ValueError 28 | match = False 29 | for p in prefix: 30 | match = match or prefix_match(p, hash_str) 31 | if not match: 32 | raise ValueError('Invalid prefix') 33 | # a match was found 34 | return True 35 | except ValueError: 36 | return False 37 | 38 | 39 | def prefix_match(prefix, obj): 40 | """ 41 | Check if an hash prefix matches: 42 | example: prefix_match(hash, "ak") will match "ak_123" but not "ak123" 43 | """ 44 | if obj is None: 45 | return False 46 | return obj.startswith(f"{prefix}_") 47 | 48 | 49 | def is_valid_aens_name(domain_name): 50 | """ 51 | Test if the provided name is valid for the aens system 52 | """ 53 | if domain_name is None or not validators.domain(domain_name.lower()) or ( 54 | not domain_name.endswith(('.chain')) and not domain_name.endswith(('.test'))): 55 | return False 56 | return True 57 | 58 | 59 | def format_amount(value: int, precision: int = -18, unit_label: str = "AE") -> str: 60 | """ 61 | Format a number as ERC20 token (1e18) and adding the unit 62 | 63 | For example, if you want to format the amount 1000000 in microAE 64 | you can use format_amount(1000000, -6, 'microAE') 65 | 66 | If the value is None or <= 0 the function returns 0 67 | 68 | :param value: the value to format 69 | :param precision: the precision to use, default -18 70 | :param unit_label: the label to use to format the value, default AE 71 | :return: a string with the value formatted using precision and unit_label 72 | """ 73 | if value is None or value <= 0: 74 | return f"0{unit_label}" 75 | value = Decimal(value).scaleb(precision) 76 | # remove trailing 0 77 | value = str(value).rstrip('0').rstrip('.') 78 | return f"{value}{unit_label}" 79 | 80 | 81 | def amount_to_aettos(value) -> int: 82 | """ 83 | Transform a value describing an amount in AE to aettos. 84 | Supported amount format are 85 | - floats: ex 1.2 3e12 86 | - string: ex 12AE 12ae 12Ae 87 | - int: will return the same number 88 | 89 | :param value: the value to transform 90 | :return: the amount ins aettos 91 | :raises TypeError: if the input value is not recognized or if the amount is lt 0 or amount is gt 1e28 92 | 93 | for additional reference check https://docs.python.org/3/library/decimal.html#decimal.Decimal.quantize 94 | 95 | """ 96 | amount = None 97 | if isinstance(value, int): 98 | amount = value 99 | elif isinstance(value, str): 100 | clean_val = value.strip().lower() 101 | if clean_val.isdigit(): 102 | amount = int(clean_val) 103 | else: 104 | clean_val = clean_val[:-2] if clean_val.endswith("ae") else clean_val 105 | try: 106 | amount = Decimal(clean_val).scaleb(18).quantize(Decimal('1')) 107 | except Exception as e: 108 | raise TypeError(f"Invalid value for amount: {value}, {e}") 109 | elif isinstance(value, float): 110 | amount = Decimal(f'{value}').scaleb(18).quantize(Decimal('1')) 111 | else: 112 | raise TypeError(f"Invalid value for amount: {value}") 113 | # validate the number 114 | if amount < 0: 115 | raise TypeError("Amount values must be greater then 0") 116 | return int(amount) 117 | 118 | 119 | def _amounts_to_aettos(*values): 120 | """ 121 | Shortcut function to convert multiple values in one call 122 | """ 123 | return [amount_to_aettos(x) for x in values] 124 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | node: 4 | image: aeternity/aeternity:${NODE_TAG} 5 | hostname: node 6 | ports: ["3013:3013", "3113:3113"] 7 | environment: 8 | AETERNITY_CONFIG: /home/aeterinty/aeternity.yaml 9 | command: bin/aeternity console -noinput -aecore expected_mine_rate ${EPOCH_MINE_RATE:-5000} 10 | volumes: 11 | - ${PWD}/docker/aeternity.yaml:/home/aeterinty/aeternity.yaml 12 | - ${PWD}/docker/keys/node:/home/aeterinty/node/keys 13 | - ${PWD}/docker/accounts.json:/home/aeternity/node/data/aecore/.genesis/accounts_test.json 14 | - node_db:/home/aeterinty/node/data/mnesia 15 | - node_keys:/home/aeterinty/node/keys 16 | 17 | 18 | compiler: 19 | image: aeternity/aesophia_http:${COMPILER_TAG} 20 | hostname: compiler 21 | ports: ["3080:3080"] 22 | 23 | volumes: 24 | node_db: 25 | node_keys: 26 | -------------------------------------------------------------------------------- /docker/accounts.json: -------------------------------------------------------------------------------- 1 | { 2 | "ak_2a1j2Mk9YSmC1gioUq4PWRm3bsv887MbuRVwyv4KaUGoR1eiKi": 1000000000000000000000000000000000000000 3 | } -------------------------------------------------------------------------------- /docker/aeternity.yaml: -------------------------------------------------------------------------------- 1 | peers: [] 2 | 3 | http: 4 | external: 5 | port: 3013 6 | internal: 7 | port: 3113 8 | listen_address: 0.0.0.0 9 | debug_endpoints: true 10 | 11 | websocket: 12 | channel: 13 | listen_address: 0.0.0.0 14 | port: 3014 15 | 16 | keys: 17 | peer_password: "top secret" 18 | dir: ./keys 19 | 20 | chain: 21 | persist: true 22 | hard_forks: 23 | "1": 0 24 | "2": 2 25 | "3": 4 26 | "4": 6 27 | 28 | mempool: 29 | nonce_offset: 1000 30 | 31 | mining: 32 | autostart: true 33 | beneficiary: "ak_2iBPH7HUz3cSDVEUWiHg76MZJ6tZooVNBmmxcgVK6VV8KAE688" 34 | expected_mine_rate: 4000 35 | micro_block_cycle: 1000 36 | cuckoo: 37 | miner: 38 | executable: mean15-generic 39 | extra_args: "" 40 | edge_bits: 15 41 | 42 | fork_management: 43 | network_id: "ae_devnet" 44 | -------------------------------------------------------------------------------- /docker/keys/node/peer_key: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aeternity/aepp-sdk-python/58ac2d12e0062896a473c254781ee397e3da318e/docker/keys/node/peer_key -------------------------------------------------------------------------------- /docker/keys/node/peer_key.pub: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aeternity/aepp-sdk-python/58ac2d12e0062896a473c254781ee397e3da318e/docker/keys/node/peer_key.pub -------------------------------------------------------------------------------- /docker/keys/node/sign_key: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aeternity/aepp-sdk-python/58ac2d12e0062896a473c254781ee397e3da318e/docker/keys/node/sign_key -------------------------------------------------------------------------------- /docker/keys/node/sign_key.pub: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aeternity/aepp-sdk-python/58ac2d12e0062896a473c254781ee397e3da318e/docker/keys/node/sign_key.pub -------------------------------------------------------------------------------- /docker/wait-for-it.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Use this script to test if a given TCP host/port are available 3 | 4 | cmdname=$(basename $0) 5 | 6 | echoerr() { if [[ $QUIET -ne 1 ]]; then echo "$@" 1>&2; fi } 7 | 8 | usage() 9 | { 10 | cat << USAGE >&2 11 | Usage: 12 | $cmdname host:port [-s] [-t timeout] [-- command args] 13 | -h HOST | --host=HOST Host or IP under test 14 | -p PORT | --port=PORT TCP port under test 15 | Alternatively, you specify the host and port as host:port 16 | -s | --strict Only execute subcommand if the test succeeds 17 | -q | --quiet Don't output any status messages 18 | -t TIMEOUT | --timeout=TIMEOUT 19 | Timeout in seconds, zero for no timeout 20 | -- COMMAND ARGS Execute command with args after the test finishes 21 | USAGE 22 | exit 1 23 | } 24 | 25 | wait_for() 26 | { 27 | if [[ $TIMEOUT -gt 0 ]]; then 28 | echoerr "$cmdname: waiting $TIMEOUT seconds for $HOST:$PORT" 29 | else 30 | echoerr "$cmdname: waiting for $HOST:$PORT without a timeout" 31 | fi 32 | start_ts=$(date +%s) 33 | while : 34 | do 35 | if [[ $ISBUSY -eq 1 ]]; then 36 | nc -z $HOST $PORT 37 | result=$? 38 | else 39 | (echo > /dev/tcp/$HOST/$PORT) >/dev/null 2>&1 40 | result=$? 41 | fi 42 | if [[ $result -eq 0 ]]; then 43 | end_ts=$(date +%s) 44 | echoerr "$cmdname: $HOST:$PORT is available after $((end_ts - start_ts)) seconds" 45 | break 46 | fi 47 | sleep 1 48 | done 49 | return $result 50 | } 51 | 52 | wait_for_wrapper() 53 | { 54 | # In order to support SIGINT during timeout: http://unix.stackexchange.com/a/57692 55 | if [[ $QUIET -eq 1 ]]; then 56 | timeout $BUSYTIMEFLAG $TIMEOUT $0 --quiet --child --host=$HOST --port=$PORT --timeout=$TIMEOUT & 57 | else 58 | timeout $BUSYTIMEFLAG $TIMEOUT $0 --child --host=$HOST --port=$PORT --timeout=$TIMEOUT & 59 | fi 60 | PID=$! 61 | trap "kill -INT -$PID" INT 62 | wait $PID 63 | RESULT=$? 64 | if [[ $RESULT -ne 0 ]]; then 65 | echoerr "$cmdname: timeout occurred after waiting $TIMEOUT seconds for $HOST:$PORT" 66 | fi 67 | return $RESULT 68 | } 69 | 70 | # process arguments 71 | while [[ $# -gt 0 ]] 72 | do 73 | case "$1" in 74 | *:* ) 75 | hostport=(${1//:/ }) 76 | HOST=${hostport[0]} 77 | PORT=${hostport[1]} 78 | shift 1 79 | ;; 80 | --child) 81 | CHILD=1 82 | shift 1 83 | ;; 84 | -q | --quiet) 85 | QUIET=1 86 | shift 1 87 | ;; 88 | -s | --strict) 89 | STRICT=1 90 | shift 1 91 | ;; 92 | -h) 93 | HOST="$2" 94 | if [[ $HOST == "" ]]; then break; fi 95 | shift 2 96 | ;; 97 | --host=*) 98 | HOST="${1#*=}" 99 | shift 1 100 | ;; 101 | -p) 102 | PORT="$2" 103 | if [[ $PORT == "" ]]; then break; fi 104 | shift 2 105 | ;; 106 | --port=*) 107 | PORT="${1#*=}" 108 | shift 1 109 | ;; 110 | -t) 111 | TIMEOUT="$2" 112 | if [[ $TIMEOUT == "" ]]; then break; fi 113 | shift 2 114 | ;; 115 | --timeout=*) 116 | TIMEOUT="${1#*=}" 117 | shift 1 118 | ;; 119 | --) 120 | shift 121 | CLI=("$@") 122 | break 123 | ;; 124 | --help) 125 | usage 126 | ;; 127 | *) 128 | echoerr "Unknown argument: $1" 129 | usage 130 | ;; 131 | esac 132 | done 133 | 134 | if [[ "$HOST" == "" || "$PORT" == "" ]]; then 135 | echoerr "Error: you need to provide a host and port to test." 136 | usage 137 | fi 138 | 139 | TIMEOUT=${TIMEOUT:-15} 140 | STRICT=${STRICT:-0} 141 | CHILD=${CHILD:-0} 142 | QUIET=${QUIET:-0} 143 | 144 | # check to see if timeout is from busybox? 145 | # check to see if timeout is from busybox? 146 | TIMEOUT_PATH=$(realpath $(which timeout)) 147 | if [[ $TIMEOUT_PATH =~ "busybox" ]]; then 148 | ISBUSY=1 149 | BUSYTIMEFLAG="-t" 150 | else 151 | ISBUSY=0 152 | BUSYTIMEFLAG="" 153 | fi 154 | 155 | if [[ $CHILD -gt 0 ]]; then 156 | wait_for 157 | RESULT=$? 158 | exit $RESULT 159 | else 160 | if [[ $TIMEOUT -gt 0 ]]; then 161 | wait_for_wrapper 162 | RESULT=$? 163 | else 164 | wait_for 165 | RESULT=$? 166 | fi 167 | fi 168 | 169 | if [[ $CLI != "" ]]; then 170 | if [[ $RESULT -ne 0 && $STRICT -eq 1 ]]; then 171 | echoerr "$cmdname: strict mode, refusing to execute subprocess" 172 | exit $RESULT 173 | fi 174 | exec "${CLI[@]}" 175 | else 176 | exit $RESULT 177 | fi 178 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | SOURCEDIR = . 8 | BUILDDIR = build 9 | 10 | # Put it first so that "make" without argument is like "make help". 11 | help: 12 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 13 | 14 | .PHONY: help Makefile 15 | 16 | # Catch-all target: route all unknown targets to Sphinx using the new 17 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 18 | %: Makefile 19 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 20 | -------------------------------------------------------------------------------- /docs/OLD/README.md: -------------------------------------------------------------------------------- 1 | # Python SDK documentation 2 | 3 | Work in progress 4 | -------------------------------------------------------------------------------- /docs/OLD/ga.md: -------------------------------------------------------------------------------- 1 | # Generalized Accounts 2 | 3 | -------------------------------------------------------------------------------- /docs/OLD/keystore_format_change.md: -------------------------------------------------------------------------------- 1 | # Keystore format change 2 | 3 | The release 0.25.0.1 of the Python SDK change the format of the keystore used to store 4 | a secret key in an encrypted format. 5 | 6 | The previous format, suported till version 0.25.0.1b1 is not supported anymore and secret keys 7 | encrypted with the legacy format will have to be updated manually. 8 | 9 | ## ⚠️ BEFORE YOU START 10 | 11 | The following procedure will print your secret key unencrypted on the terminal. 12 | 13 | **Make sure you perform this procedure in a secret setting.** 14 | 15 | ## Steps 16 | 17 | The follwing steps are required to upgade a keystore (named `my_account.json`). 18 | 19 | 1. Install the `aepp-sdk` v0.25.0.1b1 20 | 21 | ``` 22 | $ pip install aepp-sdk==0.25.0.1b1 23 | ``` 24 | 25 | Verify that you are using the correct version 26 | 27 | ``` 28 | $ aecli --version 29 | aecli, version 0.25.0.1b1 30 | ``` 31 | 32 | 2. Print your secret key to the terminal 33 | 34 | ``` 35 | aecli account address --secret-key my_account.json 36 | Enter the account password []: 37 | !Warning! this will print your secret key on the screen, are you sure? [y/N]: y 38 | 39 | Address ___________________________________________ ak_2UeaQn7Ei7HoMvDTiq2jyDuE8ymEQMPZExzC64qWTxpUnanYsE 40 | Signing key _______________________________________ 507f598f2fac1a4ab57edae53650cbf7ffae9eeeea1a297cc7c3b6172052e55ec27954c4ba901cf9b3760dc12b2c313d60fcc674ba2d04746ed813a91499a2ed 41 | 42 | ``` 43 | 44 | 3. Install the `aepp-sdk` v0.25.0.1 45 | 46 | ``` 47 | $ pip install aepp-sdk==0.25.0.1 48 | ``` 49 | 50 | Verify that you are using the correct version 51 | 52 | ``` 53 | $ aecli --version 54 | aecli, version 0.25.0.1 55 | ``` 56 | 57 | 4. Save the secret key to the new format 58 | 59 | ``` 60 | aecli account save my_account_new.json 507f598f2fac1a4ab57edae53650cbf7ffae9eeeea1a297cc7c3b6172052e55ec27954c4ba901cf9b3760dc12b2c313d60fcc674ba2d04746ed813a91499a2ed 61 | Enter the account password []: 62 | 63 | Address ___________________________________________ ak_2UeaQn7Ei7HoMvDTiq2jyDuE8ymEQMPZExzC64qWTxpUnanYsE 64 | Path ______________________________________________ /Users/andrea/tmp/aeternity/my_account_new.json 65 | 66 | ``` 67 | 68 | 5. Verify that the new keystore has the correct format 69 | 70 | the content of the keystore file should appear as the following: 71 | 72 | ``` 73 | $ cat my_account_new.json 74 | {"public_key": "ak_2UeaQn7Ei7HoMvDTiq2jyDuE8ymEQMPZExzC64qWTxpUnanYsE", "crypto": {"secret_type": "ed25519", "symmetric_alg": "xsalsa20-poly1305", "ciphertext": "f431af7e6f00da7f9acc8187900b97d42526eb135f4db0da80a7809f36a00e37f3a313f7c611784f381e58620bb2c23ef2686c3e61af28381f3a2dc6b0fcc168d46fd8d3c2bd473311140b7ee5acaa2d", "cipher_params": {"nonce": "1ea25d885a68adf13998e0fad17b22e7ade78f5cf1670eb1"}, "kdf": "argon2id", "kdf_params": {"memlimit_kib": 262144, "opslimit": 3, "salt": "c3dd4a4ac8347b3ad706756b96919387", "parallelism": 1}}, "id": "44c3d693-a890-4ac1-936b-0a65c8293388", "name": "", "version": 1} 75 | ``` 76 | 77 | 6. Cleanup! 78 | 79 | Once the operation is completed, cleanup the terminaly history that contains 80 | your secret key 81 | 82 | ```bash 83 | history -c 84 | ``` 85 | 86 | And remove the old account file 87 | 88 | ``` 89 | rm my_account.json 90 | ``` 91 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Python SDK documentation 2 | 3 | The updated documentation can be found [here](https://aepp-sdk-python.readthedocs.io/). 4 | -------------------------------------------------------------------------------- /docs/assets/images/faucet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aeternity/aepp-sdk-python/58ac2d12e0062896a473c254781ee397e3da318e/docs/assets/images/faucet.png -------------------------------------------------------------------------------- /docs/command_line_client.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## CLI Usage 4 | 5 | See below for programmatic usage 6 | 7 | You can launch the command line tool using 8 | 9 | ``` 10 | ./aecli 11 | ``` 12 | 13 | Available commands: 14 | 15 | ``` 16 | Usage: aecli [OPTIONS] COMMAND [ARGS]... 17 | 18 | Welcome to the aecli client. 19 | 20 | The client is to interact with an node node. 21 | 22 | Options: 23 | --version Show the version and exit. 24 | -u, --url URL Node node url 25 | -d, --debug-url URL 26 | --force Ignore node version compatibility check 27 | --wait Wait for transactions to be included 28 | --json Print output in JSON format 29 | --version Show the version and exit. 30 | --help Show this message and exit. 31 | 32 | Commands: 33 | account Handle account operations 34 | chain Interact with the blockchain 35 | config Print the client configuration 36 | inspect Get information on transactions, blocks,... 37 | name Handle name lifecycle 38 | oracle Interact with oracles 39 | tx Handle transactions creation 40 | ``` 41 | 42 | ## Environment variables 43 | 44 | Use the environment variables 45 | 46 | - `NODE_URL` 47 | - `NODE_URL_DEBUG` 48 | 49 | ### Example usage 50 | 51 | The following is a walkthrough to execute an offline spend transaction on the *testnet* network 52 | 53 | 1. Set the environment variables 54 | 55 | ``` 56 | export NODE_URL=https://sdk-testnet.aepps.com 57 | ``` 58 | 59 | ❗ When not set the command line client will connect to mainnet 60 | 61 | 2. Retrieve the top block 62 | 63 | ``` 64 | $ aecli chain top 65 | 66 | Beneficiary _______________________________________ ak_2iBPH7HUz3cSDVEUWiHg76MZJ6tZooVNBmmxcgVK6VV8KAE688 67 | Hash ______________________________________________ kh_WTqMQQRsvmbtP5yrKPxd4p2PPKiA51AuPyCkVJk7d7HVtkhS6 68 | Height ____________________________________________ 46049 69 | Info ______________________________________________ cb_Xfbg4g== 70 | Miner _____________________________________________ ak_24yQXT3g2jNryZbY2veHYcgQn3PspfTnkTHbXMwNYDQd9NZAs5 71 | Nonce _____________________________________________ 17848795956567990671 72 | Prev hash _________________________________________ kh_B5Q3F7Gxbmg2z3prkay2uYohvhv4xXUKQzKKkbTdm7Z3GuQxU 73 | Prev key hash _____________________________________ kh_B5Q3F7Gxbmg2z3prkay2uYohvhv4xXUKQzKKkbTdm7Z3GuQxU 74 | State hash ________________________________________ bs_bkP3QdFKCWNetDHfwL3rJG2hEgRzZRAQN6jh33SKwUd17tBjp 75 | Target ____________________________________________ 538409724 76 | Time ______________________________________________ 2019-03-03T23:38:49.720000+00:00 77 | Version ___________________________________________ 2 78 | 79 | ``` 80 | 81 | 3. Create a new account 82 | 83 | ``` 84 | aecli account create Bob.json 85 | Enter the account password []: 86 | 87 | Address ___________________________________________ ak_BobY97QUVR4iDLg4k3RKmy6shZYx9FR75nLaN33GsVmSnhWxn 88 | Path ______________________________________________ /.../Bob.json 89 | 90 | 91 | ``` 92 | 93 | ❗ Make sure that you use a long and difficult-to-guess password for an account that you plan to use on mainnet 94 | 95 | 4. Go to [testnet.faucet.aepps.com](https://testnet.faucet.aepps.com) and top up your account 96 | 97 | ![](docs/assets/images/faucet.png) 98 | 99 | 5. Inspect the transaction reported by the faucet app 100 | 101 | ``` 102 | aecli inspect th_2CV4a7xxDYj5ysaDjXNoCSLxnkowGM5bbyAvtdoPvHZwTSYykX 103 | 104 | Block hash ________________________________________ mh_2vjFffExUZPVGo3q6CHRSzxVUhzLcUnQQUWpijFtSvKfoHwQWe 105 | Block height ______________________________________ 12472 106 | Hash ______________________________________________ th_2CV4a7xxDYj5ysaDjXNoCSLxnkowGM5bbyAvtdoPvHZwTSYykX 107 | 108 | Signature #1 ____________________________________ sg_WtPeyKWN4zmcnZZXpAxCT8EvjF3qSjiUidc9cdxQooxe1JCLADTVbKDFm9S5bNwv3yq57PQKTG4XuUP4eTzD5jymPHpNu 109 | 110 | 111 | Amount __________________________________________ 5AE 112 | Fee _____________________________________________ 0.00001686AE 113 | Nonce ___________________________________________ 146 114 | Payload _________________________________________ Faucet Tx 115 | Recipient id ____________________________________ ak_2ioQbdSViNKjknaLUWphdRjpbTNVpMHpXf9X5ZkoVrhrCZGuyW 116 | Sender id _______________________________________ ak_2iBPH7HUz3cSDVEUWiHg76MZJ6tZooVNBmmxcgVK6VV8KAE688 117 | Ttl _____________________________________________ 12522 118 | Type ____________________________________________ SpendTx 119 | Version _________________________________________ 1 120 | 121 | 122 | ``` 123 | 124 | 6. Create another account 125 | 126 | ``` 127 | aecli account create Alice.json 128 | Enter the account password []: 129 | 130 | Address ___________________________________________ ak_9j8akv2PE2Mnt5khFeDvS9BGc3TBBrJkfcgaJHgBXcLLagX8M 131 | Path ______________________________________________ /.../Alice.json 132 | 133 | ``` 134 | 135 | 136 | 7. Transfer some tokens to an account to the other 137 | 138 | ``` 139 | aecli account spend Bob.json ak_9j8akv2PE2Mnt5khFeDvS9BGc3TBBrJkfcgaJHgBXcLLagX8M 1000000000000000000 140 | Enter the account password []: 141 | 142 | 143 | Tag _____________________________________________ 12 144 | Vsn _____________________________________________ 1 145 | Sender id _______________________________________ ak_BobY97QUVR4iDLg4k3RKmy6shZYx9FR75nLaN33GsVmSnhWxn 146 | Recipient id ____________________________________ ak_9j8akv2PE2Mnt5khFeDvS9BGc3TBBrJkfcgaJHgBXcLLagX8M 147 | Amount __________________________________________ 1AE 148 | Fee _____________________________________________ 0.00001686AE 149 | Ttl _____________________________________________ 0 150 | Nonce ___________________________________________ 4 151 | Payload _________________________________________ 152 | 153 | Metadata 154 | Tx ________________________________________________ tx_+KMLAfhCuEAKN05UwTV0fSgO5woziVNnAMBcDrh46XlNFTZTJQlI05fz/8pVSyrb1guCLcw8n7++O887k/JEu6/XHcCSHOMMuFv4WQwBoQEYh8aMDs7saMDBvys+lbKds3Omnzm4crYNbs9xGolBm6EBE9B4l/BeyxMO//3ANxwyT+ZHL52j9nAZosRe/YFuK4eIDeC2s6dkAACGD1WGT5gAAASAN24JGA== 155 | Hash ______________________________________________ th_2gAL72dtnaeDcZoZA9MbfSL1JrWzNErMJuikmTRvBY8zhkGh91 156 | Signature _________________________________________ sg_2LX9hnJRiYGSspzpS34QeN3PLT9bGSkFRbad9LXvLj5QUFoV5eHRf9SueDgLiiquCGbeFEBPBe7xMJidf8NMSuF16dngr 157 | Network id ________________________________________ ae_uat 158 | 159 | ``` 160 | 161 | 8. Verify the balance of the new account 162 | ``` 163 | aecli inspect ak_9j8akv2PE2Mnt5khFeDvS9BGc3TBBrJkfcgaJHgBXcLLagX8M 164 | 165 | Balance ___________________________________________ 1AE 166 | Id ________________________________________________ ak_9j8akv2PE2Mnt5khFeDvS9BGc3TBBrJkfcgaJHgBXcLLagX8M 167 | Nonce _____________________________________________ 0 168 | 169 | ``` 170 | 171 | 172 | 173 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Configuration file for the Sphinx documentation builder. 4 | # 5 | # This file does only contain a selection of the most common options. For a 6 | # full list see the documentation: 7 | # http://www.sphinx-doc.org/en/master/config 8 | 9 | # -- Path setup -------------------------------------------------------------- 10 | 11 | # If extensions (or modules to document with autodoc) are in another directory, 12 | # add these directories to sys.path here. If the directory is relative to the 13 | # documentation root, use os.path.abspath to make it absolute, like shown here. 14 | # 15 | import os 16 | import sys 17 | sys.path.append(os.path.join(os.path.dirname(__name__), '..')) 18 | import aeternity 19 | 20 | 21 | import sphinx_rtd_theme 22 | 23 | # -- Project information ----------------------------------------------------- 24 | 25 | project = 'aepp-sdk' 26 | copyright = '2019, Andrea Giacobino, Shubhendu Shekhar' 27 | author = 'Andrea Giacobino, Shubhendu Shekhar' 28 | 29 | # The short X.Y version 30 | version = aeternity._version() 31 | # The full version, including alpha/beta/rc tags 32 | release = '' 33 | 34 | 35 | # -- General configuration --------------------------------------------------- 36 | 37 | # If your documentation needs a minimal Sphinx version, state it here. 38 | # 39 | # needs_sphinx = '1.0' 40 | 41 | # Add any Sphinx extension module names here, as strings. They can be 42 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 43 | # ones. 44 | extensions = [ 45 | 'sphinx.ext.autodoc', 46 | 'sphinx.ext.doctest', 47 | 'sphinx.ext.todo', 48 | 'sphinx.ext.coverage', 49 | 'sphinx.ext.mathjax', 50 | 'sphinx.ext.ifconfig', 51 | 'sphinx.ext.viewcode', 52 | 'sphinx.ext.githubpages', 53 | 'sphinx_rtd_theme', 54 | ] 55 | 56 | # Add any paths that contain templates here, relative to this directory. 57 | templates_path = ['_templates'] 58 | 59 | # The suffix(es) of source filenames. 60 | # You can specify multiple suffix as a list of string: 61 | # 62 | # source_suffix = ['.rst', '.md'] 63 | source_suffix = '.rst' 64 | 65 | # The master toctree document. 66 | master_doc = 'index' 67 | 68 | # The language for content autogenerated by Sphinx. Refer to documentation 69 | # for a list of supported languages. 70 | # 71 | # This is also used if you do content translation via gettext catalogs. 72 | # Usually you set "language" from the command line for these cases. 73 | language = None 74 | 75 | # List of patterns, relative to source directory, that match files and 76 | # directories to ignore when looking for source files. 77 | # This pattern also affects html_static_path and html_extra_path. 78 | exclude_patterns = [] 79 | 80 | # The name of the Pygments (syntax highlighting) style to use. 81 | pygments_style = None 82 | 83 | 84 | # -- Options for HTML output ------------------------------------------------- 85 | 86 | # The theme to use for HTML and HTML Help pages. See the documentation for 87 | # a list of builtin themes. 88 | # 89 | # html_theme = 'alabaster' 90 | 91 | html_theme = "sphinx_rtd_theme" 92 | 93 | # Theme options are theme-specific and customize the look and feel of a theme 94 | # further. For a list of options available for each theme, see the 95 | # documentation. 96 | # 97 | # html_theme_options = {} 98 | 99 | # Add any paths that contain custom static files (such as style sheets) here, 100 | # relative to this directory. They are copied after the builtin static files, 101 | # so a file named "default.css" will overwrite the builtin "default.css". 102 | html_static_path = ['_static'] 103 | 104 | # Custom sidebar templates, must be a dictionary that maps document names 105 | # to template names. 106 | # 107 | # The default sidebars (for documents that don't match any pattern) are 108 | # defined by theme itself. Builtin themes are using these templates by 109 | # default: ``['localtoc.html', 'relations.html', 'sourcelink.html', 110 | # 'searchbox.html']``. 111 | # 112 | # html_sidebars = {} 113 | 114 | 115 | # -- Options for HTMLHelp output --------------------------------------------- 116 | 117 | # Output file base name for HTML help builder. 118 | htmlhelp_basename = 'aepp-sdkdoc' 119 | 120 | 121 | # -- Options for LaTeX output ------------------------------------------------ 122 | 123 | latex_elements = { 124 | # The paper size ('letterpaper' or 'a4paper'). 125 | # 126 | # 'papersize': 'letterpaper', 127 | 128 | # The font size ('10pt', '11pt' or '12pt'). 129 | # 130 | # 'pointsize': '10pt', 131 | 132 | # Additional stuff for the LaTeX preamble. 133 | # 134 | # 'preamble': '', 135 | 136 | # Latex figure (float) alignment 137 | # 138 | # 'figure_align': 'htbp', 139 | } 140 | 141 | # Grouping the document tree into LaTeX files. List of tuples 142 | # (source start file, target name, title, 143 | # author, documentclass [howto, manual, or own class]). 144 | latex_documents = [ 145 | (master_doc, 'aepp-sdk.tex', 'aepp-sdk Documentation', 146 | 'Andrea Giacobino, Shubhendu Shekhar', 'manual'), 147 | ] 148 | 149 | 150 | # -- Options for manual page output ------------------------------------------ 151 | 152 | # One entry per manual page. List of tuples 153 | # (source start file, name, description, authors, manual section). 154 | man_pages = [ 155 | (master_doc, 'aepp-sdk', 'aepp-sdk Documentation', 156 | [author], 1) 157 | ] 158 | 159 | 160 | # -- Options for Texinfo output ---------------------------------------------- 161 | 162 | # Grouping the document tree into Texinfo files. List of tuples 163 | # (source start file, target name, title, author, 164 | # dir menu entry, description, category) 165 | texinfo_documents = [ 166 | (master_doc, 'aepp-sdk', 'aepp-sdk Documentation', 167 | author, 'aepp-sdk', 'One line description of project.', 168 | 'Miscellaneous'), 169 | ] 170 | 171 | 172 | # -- Options for Epub output ------------------------------------------------- 173 | 174 | # Bibliographic Dublin Core info. 175 | epub_title = project 176 | 177 | # The unique identifier of the text. This can be a ISBN number 178 | # or the project homepage. 179 | # 180 | # epub_identifier = '' 181 | 182 | # A unique identification for the text. 183 | # 184 | # epub_uid = '' 185 | 186 | # A list of files that should not be packed into the epub file. 187 | epub_exclude_files = ['search.html'] 188 | 189 | 190 | # -- Extension configuration ------------------------------------------------- 191 | 192 | # -- Options for todo extension ---------------------------------------------- 193 | 194 | # If true, `todo` and `todoList` produce output, else they produce nothing. 195 | todo_include_todos = True 196 | -------------------------------------------------------------------------------- /docs/faq/index.rst: -------------------------------------------------------------------------------- 1 | FAQ 2 | =========================== 3 | 4 | 5 | TODO 6 | -------------------------------------------------------------------------------- /docs/howto/accounts_signatures.rst: -------------------------------------------------------------------------------- 1 | ======================= 2 | Accounts and Signatures 3 | ======================= 4 | 5 | The Aeternity blockchain uses the account/balance model, 6 | and here are listed all the possibility regarding handling 7 | accounts and signatures 8 | 9 | 10 | Create an account (non HD) 11 | ========================== 12 | 13 | Generate a new account 14 | 15 | :: 16 | 17 | # import the Account class from signing 18 | from aeternity.signing import Account 19 | 20 | account = Account.generate() 21 | print(account) # will print the public key/address of the account 22 | 23 | Save account on file 24 | ==================== 25 | 26 | :: 27 | 28 | # import the Account class from signing 29 | from aeternity.signing import Account 30 | 31 | # generate a new account 32 | account = Account.generate() 33 | 34 | # save to file 35 | account.save_to_keystore_file("keystore_file_path", "My Password") 36 | 37 | Load account 38 | ======================= 39 | 40 | :: 41 | 42 | # import the Account class from signing 43 | from aeternity.signing import Account 44 | 45 | # load from file 46 | account = Account.from_keystore("keystore_file_path", "My Password") 47 | 48 | # from secret key string hex encoded 49 | account = Account.from_secret_key_string("mysecretkeystring...") 50 | 51 | 52 | To synchronize an Account with data from 53 | the node, such as the account type (Basic or GA) 54 | 55 | :: 56 | 57 | # import the Account class from signing 58 | from aeternity.node import NodeClient, Config 59 | 60 | # initialize the node client 61 | node_cli = NodeClient() 62 | 63 | # synchronize with the node api 64 | account = node_cli.get_account(keystore_path="", password="", height="") 65 | 66 | 67 | 68 | 69 | 70 | Print the secret key of an account 71 | ================================== 72 | 73 | .. warning:: 74 | The secret key is printed in "plain text", by printing it on the terminal or saving 75 | it without encryption you expose your account 76 | 77 | 78 | the command to print the public and secret key of an account saved in a keystore file is: 79 | 80 | :: 81 | 82 | $ aecli account address --secret-key KEYSTORE_FILENAME.json 83 | 84 | Example: 85 | 86 | :: 87 | 88 | ➜ aecli account address BOB.json --secret-key 89 | Enter the account password: 90 | !Warning! this will print your secret key on the screen, are you sure? [y/N]: y 91 | 92 | Address ___________________________________________ ak_2P44NhGFT7fP1TFpZ62FwabWP9djg5sDwFNDVVpiGgd3p4yA7X 93 | Secretkey _________________________________________ c226bf650f30740287f77a715............f49ddff758971112fb5cfb0e66975a8f 94 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /docs/howto/amounts.rst: -------------------------------------------------------------------------------- 1 | ======= 2 | Amounts 3 | ======= 4 | 5 | The Aeternity blockchain tokens are an ERC-20 compatible token, 6 | where the unit is the Aetto (1e-18). 7 | 8 | The ``utils`` submodule provides functions to manipulate the 9 | amounts. 10 | 11 | 12 | Format amount to a human readable format 13 | ======================================== 14 | 15 | The following snippets show how to format 16 | an amount from ``aetto`` to a human readable format. 17 | 18 | :: 19 | 20 | # import the utils submodule 21 | from aeternity import utils 22 | 23 | amount = 1000000000000000000 24 | human_value = utils.format_amount(amount) 25 | print(human_value) # prints 1AE 26 | 27 | 28 | Parse amounts 29 | ============= 30 | 31 | This snippets shows how to parse amounts 32 | expressed in a human friendly, float or scientific notation. 33 | 34 | :: 35 | 36 | # import the utils submodule 37 | from aeternity import utils 38 | 39 | amount = utils.amount_to_aettos(1.2) 40 | print(amount) # prints 1200000000000000000 41 | 42 | amount = utils.amount_to_aettos("10AE") 43 | print(amount) # prints 10000000000000000000 44 | 45 | amount = utils.amount_to_aettos(1e1) 46 | print(amount) # prints 10000000000000000000 47 | 48 | amount = utils.amount_to_aettos(1) 49 | print(amount) # prints 1 50 | 51 | 52 | .. Important:: 53 | when using amount_to_aettos function, the maximum value as imput is 1e9, 54 | that will result in an aetto value of 1000000000000000000000000000 (1e27). 55 | Bigger values wil return in an error. 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /docs/howto/cli_accounts.rst: -------------------------------------------------------------------------------- 1 | ========================== 2 | Account management via CLI 3 | ========================== 4 | 5 | The Python SDK comes with a command line client (CLI) that can be used 6 | among other things to generate, store, inspect and load accounts 7 | 8 | 9 | Generate a new account 10 | ====================== 11 | 12 | The command to generate a new (non HD) account is: 13 | 14 | :: 15 | 16 | $ aecli account create KEYSTORE_FILENAME.json 17 | 18 | 19 | the command will ask for a password and will store the account in the file 20 | named ``KEYSTORE_FILENAME.json``; here an example: 21 | 22 | :: 23 | 24 | ➜ aecli account create BOB.json 25 | Enter the account password []: 26 | 27 | Address ___________________________________________ ak_2P44NhGFT7fP1TFpZ62FwabWP9djg5sDwFNDVVpiGgd3p4yA7X 28 | Path ______________________________________________ /howto/BOB.json 29 | 30 | 31 | Print the secret key of an account 32 | ================================== 33 | 34 | .. warning:: 35 | The secret key is printed in "clear text", by printing it on the terminal or saving 36 | it unencrypted you expose your account 37 | 38 | 39 | the command to print the public and secret key of an account saved in a keystore file is: 40 | 41 | :: 42 | 43 | $ aecli account address --secret-key KEYSTORE_FILENAME.json 44 | 45 | Example: 46 | 47 | :: 48 | 49 | ➜ aecli account address BOB.json --secret-key 50 | Enter the account password: 51 | !Warning! this will print your secret key on the screen, are you sure? [y/N]: y 52 | 53 | Address ___________________________________________ ak_2P44NhGFT7fP1TFpZ62FwabWP9djg5sDwFNDVVpiGgd3p4yA7X 54 | Secretkey _________________________________________ c226bf650f30740287f77a715............f49ddff758971112fb5cfb0e66975a8f 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /docs/howto/delegate_signatures.rst: -------------------------------------------------------------------------------- 1 | ================================= 2 | Transaction delegation signatures 3 | ================================= 4 | 5 | The `Sophia`_ language for smart contracts allow to delegate 6 | the transaction execution to a contract by providing 7 | delegation signatures. 8 | 9 | .. _Sophia: https://github.com/aeternity/protocol/blob/aeternity-node-v5.4.1/contracts/sophia.md 10 | 11 | Delegate signatures for AENS 12 | ============================ 13 | 14 | The following code snippet shows how to generate 15 | signatures for name transactions delegation to a contract 16 | 17 | :: 18 | 19 | # import the required libraries 20 | from aeternity.node import NodeClient, Config 21 | from aeternity.signing import Account 22 | 23 | # initialize the node client 24 | node_cli = NodeClient(Config(external_url="https://mainnet.aeternal.io")) 25 | 26 | # get an account 27 | account = Account.from_keystore("/path/to/keystore", "keystore password") 28 | 29 | # an example name 30 | name = "example.chain" 31 | 32 | # name preclaim signature delegation 33 | sig = node_cli.delegate_name_preclaim_signature(account, contract_id) 34 | 35 | # name claim signature delegation 36 | sig = node_cli.delegate_name_claim_signature(account, contract_id, name) 37 | 38 | # name revoke signature delegation 39 | sig = node_cli.delegate_name_revoke_signature(account, contract_id, name) 40 | 41 | # name revoke signature delegation 42 | sig = node_cli.delegate_name_transfer_signature(account, contract_id, name) 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /docs/howto/index.rst: -------------------------------------------------------------------------------- 1 | =============== 2 | "How-to" guides 3 | =============== 4 | 5 | Here you'll find short answers to "How do I....?" types of questions. These 6 | how-to guides don't cover topics in depth. However, these guides will help 7 | you quickly accomplish common tasks. 8 | 9 | .. toctree:: 10 | :maxdepth: 1 11 | 12 | cli_accounts 13 | validate_contract_bytecode 14 | amounts 15 | delegate_signatures 16 | 17 | .. seealso:: 18 | 19 | Many writers in the `Aeternity forum`_ write this sort of 20 | how-to material. 21 | 22 | .. _Aeternity forum: https://forum.aeternity.com 23 | -------------------------------------------------------------------------------- /docs/howto/validate_contract_bytecode.rst: -------------------------------------------------------------------------------- 1 | ========================== 2 | Validate contract bytecode 3 | ========================== 4 | 5 | When deploying a contract on the aeternity blockchain 6 | you have to compile the source into bytecode and 7 | then create a transaction that includes this bytecode. 8 | But when the contract is deployed, aeternity node initializes 9 | the contract using the provided `init` method and then removes 10 | it from the bytecode before storing it on the chain. 11 | Therefore, given the contract source and it's address on 12 | the network you cannot verify the source. 13 | 14 | To overcome this behaviour and validate the bytecode, 15 | you can refer to the below example: 16 | 17 | .. note:: 18 | This method is only available if you are using compiler version >= 4.1.0. 19 | 20 | 21 | Deploy the contract (if it is not already) 22 | ========================================== 23 | 24 | .. literalinclude:: ../../tests/test_compiler.py 25 | :lines: 125-132 26 | :dedent: 4 27 | 28 | Validate the contract 29 | ===================== 30 | 31 | .. literalinclude:: ../../tests/test_compiler.py 32 | :lines: 134-136 33 | :dedent: 4 -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. aepp-sdk documentation master file, created by 2 | sphinx-quickstart on Mon Nov 11 23:04:48 2019. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Aeternty Python SDK Documentation (aepp-sdk) 7 | ============================================ 8 | 9 | .. rubric:: Everything you need to know about the Aeternity Python SDK. 10 | 11 | .. toctree:: 12 | :hidden: 13 | 14 | intro/index 15 | howto/index 16 | topics/index 17 | ref/index 18 | faq/index 19 | snippets/index 20 | 21 | 22 | How the documentation is organized 23 | ================================== 24 | 25 | A high-level overview of how it's organized will help you know 26 | where to look for certain things: 27 | 28 | * :doc:`Tutorials ` take you by the hand through a series of 29 | steps to create a python aepp. Start here if you're new to Aeternity 30 | application development. Also look at the ":ref:`index-first-steps`" below. 31 | 32 | * :doc:`Topic guides ` discuss key topics and concepts at a 33 | fairly high level and provide useful background information and explanation. 34 | 35 | * :doc:`Reference guides ` contain technical reference for APIs and 36 | other aspects of Aeternity SDK machinery. They describe how it works and how to 37 | use it but assume that you have a basic understanding of key concepts. 38 | 39 | * :doc:`How-to guides ` are recipes. They guide you through the 40 | steps involved in addressing key problems and use-cases. They are more 41 | advanced than tutorials and assume some knowledge of how the Aeternity SDK works. 42 | 43 | * :doc:`Code snippets ` are various pieces of codes to copy/paste. 44 | They may be useful to find pieces of codes to get stuff done. 45 | 46 | .. _index-first-steps: 47 | 48 | First steps 49 | =========== 50 | 51 | Are you new to the Aeternity SDK? This is the place to start! 52 | 53 | * **From scratch:** 54 | 55 | * :doc:`Installation ` 56 | 57 | * **Tutorial:** 58 | 59 | * :doc:`Spend transactions ` 60 | * :doc:`Deploy a contract ` 61 | * :doc:`Call a contract ` 62 | * :doc:`Claiming a name ` 63 | * :doc:`Using Generalized accounts ` 64 | * :doc:`The CLI ` 65 | * :doc:`Generating and using a HD Wallet ` 66 | 67 | 68 | * **How to:** 69 | 70 | * Coming soon... 71 | 72 | .. * :doc:`Generate a list of accounts on the fly ` 73 | * :doc:`Use the json output from the CLI with jq ` 74 | * :doc:`Pretty print amounts ` 75 | 76 | * **Reference:** 77 | 78 | * :doc:`The NodeClient and Config ` 79 | * :doc:`The TxObject ` 80 | 81 | 82 | Getting help 83 | ============ 84 | 85 | Having trouble? We'd like to help! 86 | 87 | * Try the :doc:`FAQ ` -- it's got answers to many common questions. 88 | 89 | * Looking for specific information? Try the :ref:`genindex` or :ref:`modindex` 90 | 91 | * Search for information in the `Aeternity forum`_, or `post a question`_. 92 | 93 | * Report bugs with the Python SDK in our `issue tracker`_. 94 | 95 | .. _Aeternity forum: https://forum.aeternity.com 96 | .. _post a question: https://forum.aeternity.com 97 | .. _issue tracker: https://github.com/aeternity/aepp-sdk-python/issues 98 | 99 | 100 | Indices and tables 101 | ================== 102 | 103 | * :ref:`genindex` 104 | * :ref:`modindex` 105 | * :ref:`search` 106 | 107 | -------------------------------------------------------------------------------- /docs/intro/contributing.rst: -------------------------------------------------------------------------------- 1 | ============ 2 | Contributing 3 | ============ 4 | 5 | Contributing to the Aeternity Python SDK 6 | 7 | Pull Requests 8 | ============= 9 | 10 | * `Squash your commits`_ to ensure each resulting commit is stable and can be tested. 11 | * Include issue numbers in the PR title, e.g. "GH-128. Resolves issue #123". 12 | * Add unit tests for self-contained modules. 13 | * Add integration tests as needed. 14 | * Document new code. 15 | 16 | Git Commit Messages 17 | =================== 18 | 19 | * Use the present tense ("Add feature" not "Added feature") 20 | * Use the imperative mood ("Move cursor to..." not "Moves cursor to...") 21 | * Limit the first line to 72 characters or less 22 | * Reference issues and pull requests liberally after the first line 23 | 24 | Learn more about `writing a good commit message`_. 25 | 26 | .. _Squash your commits: :https://stackoverflow.com/questions/5189560/squash-my-last-x-commits-together-using-git 27 | .. _writing a good commit message: https://chris.beams.io/posts/git-commit/ 28 | -------------------------------------------------------------------------------- /docs/intro/index.rst: -------------------------------------------------------------------------------- 1 | =============== 2 | Getting started 3 | =============== 4 | 5 | Are you new to Aeternity blockchain? Well, you came to the right 6 | place: read this material to quickly get up and running. 7 | 8 | .. toctree:: 9 | :maxdepth: 1 10 | 11 | install 12 | tutorial01-spend 13 | tutorial02-contracts-deploy 14 | tutorial02-contracts-call 15 | tutorial03-aens 16 | tutorial04-ga 17 | tutorial05-cli 18 | tutorial06-hdwallet 19 | tutorial07-offline 20 | contributing 21 | 22 | .. seealso:: 23 | If you are new to Aeternity blockchain, you may want to start by checking 24 | the website_ and the forum_. 25 | 26 | .. _website: https://aeternity.com 27 | .. _forum: https://forum.aeternity.com 28 | 29 | .. seealso:: 30 | 31 | If you're new to Python_, you might want to start by getting an idea of what 32 | the language is like. 33 | 34 | If you're new to programming entirely, you might want to start with this 35 | `list of Python resources for non-programmers`_ 36 | 37 | If you already know a few other languages and want to get up to speed with 38 | Python quickly, we recommend `Dive Into Python`_. If that's not quite your 39 | style, there are many other `books about Python`_. 40 | 41 | .. _python: https://python.org/ 42 | .. _list of Python resources for non-programmers: https://wiki.python.org/moin/BeginnersGuide/NonProgrammers 43 | .. _Dive Into Python: https://diveinto.org/python3/table-of-contents.html 44 | .. _books about Python: https://wiki.python.org/moin/PythonBooks 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /docs/intro/install.rst: -------------------------------------------------------------------------------- 1 | =================== 2 | Quick install guide 3 | =================== 4 | 5 | Before you can use the Aeternity SDK, you'll need to get it installed. 6 | This guide will guide you to a minimal installation that'll work 7 | while you walk through the introduction. For more installation options 8 | check the :doc:`installation guide `. 9 | 10 | 11 | Install Python 12 | ============== 13 | 14 | Get the latest version of Python at https://www.python.org/downloads/ or with 15 | your operating system's package manager. 16 | 17 | You can verify that Python is installed by typing ``python`` from your shell; 18 | you should see something like:: 19 | 20 | Python 3.7.y 21 | [GCC 4.x] on linux 22 | Type "help", "copyright", "credits" or "license" for more information. 23 | >>> 24 | 25 | .. hint:: 26 | The mimimum required python version is 3.7 27 | 28 | 29 | Install the SDK 30 | ==================== 31 | 32 | The Aeternity Python SDK package name is ``aepp-sdk`` and it is available 33 | via the `pypi.org`_ repository. 34 | 35 | .. _pypi.org: https://pypi.org/project/aepp-sdk/ 36 | 37 | To install or upgrade run the command 38 | 39 | :: 40 | 41 | pip install -U aepp-sdk 42 | 43 | 44 | Verifying 45 | ========= 46 | 47 | To verify that the Aeternity SDK can be seen by Python, type ``python`` from your shell. 48 | Then at the Python prompt, try to import aeternity: 49 | 50 | .. parsed-literal:: 51 | 52 | >>> import aeternity 53 | >>> print(aeternity._version()) 54 | |version| 55 | 56 | 57 | To verify that the CLI is available in your ``PATH`` run the command 58 | 59 | .. parsed-literal:: 60 | 61 | $ aecli --version 62 | aecli, version |version| 63 | 64 | 65 | 66 | 67 | 68 | That's it! 69 | ========== 70 | 71 | That's it -- you can now :doc:`move onto the tutorial `. 72 | -------------------------------------------------------------------------------- /docs/intro/tutorial01-spend.rst: -------------------------------------------------------------------------------- 1 | ========================== 2 | Transfer funds 3 | ========================== 4 | 5 | 6 | Let's learn by example. 7 | 8 | Throughout this tutorial, we'll walk you through the creation of a basic 9 | script to transfer funds from an account to another. 10 | 11 | It'll consist of three parts: 12 | 13 | * Generate 2 accounts via command line client 14 | * Get some funds for the first account 15 | * Transfer the funds from an account to the other 16 | 17 | We'll assume you have :doc:`the SDK installed ` already. You can 18 | tell the SDK is installed and which version by running the following command 19 | in a shell prompt (indicated by the $ prefix): 20 | 21 | :: 22 | 23 | $ aecli --version 24 | 25 | First step will be to generate two new accounts using the command line client: 26 | 27 | First import the required libraries 28 | 29 | .. literalinclude:: ../../tests/test_tutorial01-spend.py 30 | :lines: 9-14 31 | 32 | Then instantiate the node client and generate 2 accounts 33 | 34 | .. literalinclude:: ../../tests/test_tutorial01-spend.py 35 | :lines: 16-39 36 | :dedent: 4 37 | 38 | 39 | Now copy the Alice address and paste it into the `Aeternity Faucet`_ to top up the account; once the Alice account has some positive balance we can execute the spend transaction: 40 | 41 | 42 | .. _Aeternity Faucet: https://testnet.faucet.aepps.com 43 | 44 | 45 | .. literalinclude:: ../../tests/test_tutorial01-spend.py 46 | :lines: 45-51 47 | :dedent: 4 48 | 49 | And finally verify the new balance: 50 | 51 | .. literalinclude:: ../../tests/test_tutorial01-spend.py 52 | :lines: 56-63 53 | :dedent: 4 54 | 55 | Thats it! You have successfully executed your transaction in the Aeternity Blockchain testnet network. For the mainnet network the procedure is the same except you will have to get some tokens via an exchange or via other means. 56 | -------------------------------------------------------------------------------- /docs/intro/tutorial02-contracts-call.rst: -------------------------------------------------------------------------------- 1 | =========================== 2 | Interacting with a contract 3 | =========================== 4 | 5 | This guide describes how you can leverage aepp-sdk interact 6 | with a deployed aeternity smart contract. 7 | 8 | .. seealso:: Sophia: An Æternity Blockchain Language 9 | 10 | The Sophia is a language in the ML family. 11 | It is strongly typed and has restricted mutable state. 12 | Check out more about sophia `here `_. 13 | 14 | 15 | Prerequisites 16 | ============= 17 | 18 | 1. An account with some initial AE 19 | 2. An address/contract_id of a deployed contract 20 | 3. An aeternity node 21 | 4. A sophia aehttp compiler 22 | 23 | .. admonition:: Assumptions 24 | 25 | We're going to assume that you have working knowledge of the SDK and 26 | know how to create an Account instance, a NodeClient, and a CompilerClient. 27 | 28 | 29 | Sample Sophia Contract 30 | ====================== 31 | 32 | Below is the sample sophia contract that we'll use in this guide. 33 | 34 | .. literalinclude:: ../../tests/testdata/CryptoHamster.aes 35 | 36 | Importing required classes and methods 37 | ====================================== 38 | 39 | We need to import the following classes to use contracts. 40 | 41 | .. literalinclude:: ../../tests/test_tutorial05-contracts.py 42 | :lines: 11-14 43 | 44 | 45 | Initializing NodeClient and Compiler 46 | ==================================== 47 | 48 | Below are the steps required to initialize the `NodeClient` and `Compiler`. 49 | As you can see below, during the initialization of `NodeClient` we're also providing the `internal_url`. 50 | 51 | `internal_url` provides the debug endpoint to `dry_run` a contract method which 52 | can also be used to do static calls on deployed contracts and this is what exactly we're going to use this for. 53 | 54 | You can also not provide the `internal_url` but then you'll have to disable the use of `dry-run` endpoint. 55 | We'll see how to do that when we initialize our contract. 56 | 57 | .. literalinclude:: ../../tests/test_tutorial05-contracts.py 58 | :lines: 18-28 59 | :dedent: 4 60 | 61 | Generate an Account 62 | ===================== 63 | 64 | You'll need an account (using the `Account` class) for stateful contract calls. 65 | 66 | .. literalinclude:: ../../tests/test_tutorial05-contracts.py 67 | :lines: 32-33 68 | :dedent: 4 69 | 70 | Read the Contract from file and initialize 71 | ========================================== 72 | 73 | You can read the contract from the stored `.aes` file and use it to initialize the contract instance. 74 | If you have not provided the `internal_endpoint` or simple do not want to use the `dry-run` functionality 75 | you can disable it by passing `use-dry-run=False` to the `ContractNative` constructor. 76 | 77 | .. warning:: 78 | If you DO NOT provide the `internal_url` during NodeClient initialization 79 | and also DID NOT disable the `dry-run` then the contract method calls for 80 | `un-stateful` methods WILL FAIL. 81 | 82 | .. literalinclude:: ../../tests/test_tutorial05-contracts.py 83 | :lines: 37-52 84 | :dedent: 4 85 | 86 | Now pass the address of the deployed contract 87 | 88 | .. warning:: 89 | If the contract is not found at the provided address or the on-chain bytecode 90 | does not match, for the given network, the method will fail. 91 | 92 | .. literalinclude:: ../../tests/test_tutorial05-contracts.py 93 | :lines: 62-63 94 | :dedent: 4 95 | 96 | 97 | Call the contract methods 98 | ========================= 99 | 100 | All the methods inside the contract are also available (with same signature) 101 | to use from the contract instance. 102 | 103 | .. note:: 104 | All the methods that are NOT `stateful`, by default are processed using the `dry-run` endpoint to save gas. 105 | And therefore, a transaction hash will also not be provided for them. 106 | This functionality can be either diabled for the contract instance or per method by using `use_dry_run` argument. 107 | 108 | 109 | .. literalinclude:: ../../tests/test_tutorial05-contracts.py 110 | :lines: 65-69 111 | :dedent: 4 112 | 113 | 114 | And in a similar way a not stateful call can be invoked 115 | 116 | .. literalinclude:: ../../tests/test_tutorial05-contracts.py 117 | :lines: 73-77 118 | :dedent: 4 119 | -------------------------------------------------------------------------------- /docs/intro/tutorial02-contracts-deploy.rst: -------------------------------------------------------------------------------- 1 | ==================== 2 | Deploying a contract 3 | ==================== 4 | 5 | This guide describes how you can leverage aepp-sdk to compile and 6 | deploy an aeternity smart contracts. 7 | 8 | .. seealso:: Sophia: An Æternity Blockchain Language 9 | 10 | The Sophia is a language in the ML family. 11 | It is strongly typed and has restricted mutable state. 12 | Check out more about sophia `here `_. 13 | 14 | 15 | Prerequisites 16 | ============= 17 | 18 | 1. An account with some initial AE 19 | 2. A smart contract written in sophia 20 | 3. An aeternity node 21 | 4. A sophia aehttp compiler 22 | 23 | .. admonition:: Assumptions 24 | 25 | We're going to assume that you have working knowledge of the SDK and 26 | know how to create an Account instance, a NodeClient, and a CompilerClient. 27 | 28 | 29 | Sample Sophia Contract 30 | ====================== 31 | 32 | Below is the sample sophia contract that we'll use in this guide. 33 | 34 | .. literalinclude:: ../../tests/testdata/CryptoHamster.aes 35 | 36 | Importing required classes and methods 37 | ====================================== 38 | 39 | We need to import the following classes to use contracts. 40 | 41 | .. literalinclude:: ../../tests/test_tutorial05-contracts.py 42 | :lines: 11-14 43 | 44 | 45 | Initializing NodeClient and Compiler 46 | ==================================== 47 | 48 | Below are the steps required to initialize the `NodeClient` and `Compiler`. 49 | As you can see below, during the initialization of `NodeClient` we're also providing the `internal_url`. 50 | 51 | `internal_url` provides the debug endpoint to `dry_run` a contract method which 52 | can also be used to do static calls on deployed contracts and this is what exactly we're going to use this for. 53 | 54 | You can also not provide the `internal_url` but then you'll have to disable the use of `dry-run` endpoint. 55 | We'll see how to do that when we initialize our contract. 56 | 57 | .. literalinclude:: ../../tests/test_tutorial05-contracts.py 58 | :lines: 18-28 59 | :dedent: 4 60 | 61 | Generate an Account 62 | ===================== 63 | 64 | You'll need an account (using the `Account` class) to deploy the contract and also for stateful contract calls. 65 | 66 | .. literalinclude:: ../../tests/test_tutorial05-contracts.py 67 | :lines: 32-33 68 | :dedent: 4 69 | 70 | Read the Contract from file and initialize 71 | ========================================== 72 | 73 | You can read the contract from the stored `.aes` file and use it to initialize the contract instance. 74 | If you have not provided the `internal_endpoint` or simple do not want to use the `dry-run` functionality 75 | you can disable it by passing `use-dry-run=False` to the `ContractNative` constructor. 76 | 77 | .. warning:: 78 | If you DO NOT provide the `internal_url` during NodeClient initialization 79 | and also DID NOT disable the `dry-run` then the contract method calls for 80 | `un-stateful` methods WILL FAIL. 81 | 82 | .. literalinclude:: ../../tests/test_tutorial05-contracts.py 83 | :lines: 37-52 84 | :dedent: 4 85 | 86 | Compile and deploy the contract 87 | =============================== 88 | 89 | You can compile the contract and deploy it using the `deploy` method. 90 | If your `init` method accepts any arguments then please provide them inside the `deploy` method. 91 | Once the contract is compiled and deployed, the signed transaction is returned. 92 | 93 | .. literalinclude:: ../../tests/test_tutorial05-contracts.py 94 | :lines: 53-57 95 | :dedent: 4 96 | 97 | 98 | Thats all, you have successfully deployed a smart contract in Aeternity, the next tutorial 99 | will guide you to call the contract 100 | 101 | -------------------------------------------------------------------------------- /docs/intro/tutorial03-aens.rst: -------------------------------------------------------------------------------- 1 | ====================== 2 | Claiming a name 3 | ====================== 4 | 5 | This guide describes how you can leverage aepp-sdk to claim a name 6 | on the aternity blockchain. 7 | 8 | 9 | Prerequisites 10 | ============= 11 | 12 | 13 | 14 | .. admonition:: Assumptions 15 | 16 | We're going to assume that you have working knowledge of the SDK and 17 | know how to create an Account instance, a NodeClient, and a CompilerClient. 18 | 19 | 20 | TODO -------------------------------------------------------------------------------- /docs/intro/tutorial06-hdwallet.rst: -------------------------------------------------------------------------------- 1 | ================================ 2 | Generating and using a HD Wallet 3 | ================================ 4 | 5 | This tutorial covers the generation, use and management 6 | of a HD Wallet using aepp-sdk. 7 | 8 | .. note:: 9 | Do not know what HD Wallets are? 10 | You can read about it `here `_. 11 | 12 | Importing the HD Wallet class 13 | ============================= 14 | 15 | .. literalinclude:: ../../tests/test_tutorial06-hdwallet.py 16 | :lines: 1-1 17 | 18 | Generating a Mnemonic 19 | ====================== 20 | 21 | The HdWallet class exposes a static method to generate 22 | a BIP39 compatible mnemonic seed phrase. 23 | 24 | .. literalinclude:: ../../tests/test_tutorial06-hdwallet.py 25 | :lines: 5-6 26 | :dedent: 4 27 | 28 | Generating a HD Wallet 29 | ====================== 30 | 31 | You can instantiate a HDWallet instance by using the 32 | `HDWallet` class. 33 | The constructor accepts a mnemonic seed phrase and 34 | if no seed phrase is provided then one is auto generated. 35 | 36 | .. literalinclude:: ../../tests/test_tutorial06-hdwallet.py 37 | :lines: 10-12 38 | :dedent: 4 39 | 40 | Deriving a child private key 41 | ============================ 42 | 43 | Once the HD wallet is instantiated, you can access the 44 | provided class method to derive child private/secret keys. 45 | 46 | .. literalinclude:: ../../tests/test_tutorial06-hdwallet.py 47 | :lines: 16-20 48 | :dedent: 4 49 | -------------------------------------------------------------------------------- /docs/intro/tutorial07-offline.rst: -------------------------------------------------------------------------------- 1 | ==================== 2 | Offline transactions 3 | ==================== 4 | 5 | Throughout this tutorial, we'll walk you through the creation of a basic 6 | script to generate a list of spend transactions and to sign them. 7 | 8 | It'll consist of three parts: 9 | 10 | * Build a list of spend transactions (offline) 11 | * Sign each transactions (offline) 12 | * Broadcast the transactions obtained in the above steps 13 | 14 | We'll assume you have :doc:`the SDK installed `. 15 | 16 | 17 | First import the required libraries 18 | 19 | .. literalinclude:: ../../tests/test_tutorial07-offline.py 20 | :lines: 10-14 21 | 22 | For the next steps we need to have an account available. 23 | 24 | .. literalinclude:: ../../tests/test_tutorial07-offline.py 25 | :lines: 18-19 26 | :dedent: 4 27 | 28 | .. hint:: 29 | To successfully complete the tutorial, you will have to have a positive balance on the account just creataed. 30 | How to get some funds is :doc:`explained in the first tutorial ` using the `Faucet`_ app. 31 | 32 | .. _Faucet: https://testnet.faucet.aepps.com 33 | 34 | Once we have an account available we will use the :doc:`TxBuilder ` to prepare the transactions. 35 | 36 | .. literalinclude:: ../../tests/test_tutorial07-offline.py 37 | :lines: 25-50 38 | :dedent: 4 39 | 40 | Once the transactions are ready they need to be signed and encapsulated in signed transaction: 41 | 42 | .. hint:: 43 | the Network ID is required to compute a valid signature for transactions; when using the online 44 | node client the Network ID is automatically retrieved from the node. 45 | 46 | .. literalinclude:: ../../tests/test_tutorial07-offline.py 47 | :lines: 52-53 48 | :dedent: 4 49 | 50 | The :doc:`TxSigner ` object provides the functionality to compute the signature for 51 | transactions. 52 | 53 | .. literalinclude:: ../../tests/test_tutorial07-offline.py 54 | :lines: 59-69 55 | :dedent: 4 56 | 57 | Finally we can instantiate a node client and broadcast the transactions: 58 | 59 | 60 | .. literalinclude:: ../../tests/test_tutorial07-offline.py 61 | :lines: 70-85 62 | :dedent: 4 63 | 64 | .. warn:: 65 | Make sure that you connect to a node that uses the same Network ID that you used before. 66 | 67 | 68 | .. warn:: 69 | if you intend to broadcast more then 5 transactions for the same account you need to be sure that 70 | the node that you are connecting to is configured to accept those transactions (parameter ``nonce_offset`` in the node configuration) 71 | 72 | 73 | Thats it! You have successfully executed your transaction in the Aeternity Blockchain testnet network. For the mainnet network the procedure is the same except you will have to get some tokens via an exchange or via other means. 74 | 75 | 76 | -------------------------------------------------------------------------------- /docs/library.md: -------------------------------------------------------------------------------- 1 | # Python as a library 2 | 3 | 4 | ## Operations: 5 | 6 | Spend Transaction: 7 | 8 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=source 11 | set BUILDDIR=build 12 | 13 | if "%1" == "" goto help 14 | 15 | %SPHINXBUILD% >NUL 2>NUL 16 | if errorlevel 9009 ( 17 | echo. 18 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 19 | echo.installed, then set the SPHINXBUILD environment variable to point 20 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 21 | echo.may add the Sphinx directory to PATH. 22 | echo. 23 | echo.If you don't have Sphinx installed, grab it from 24 | echo.http://sphinx-doc.org/ 25 | exit /b 1 26 | ) 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /docs/mkdir: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aeternity/aepp-sdk-python/58ac2d12e0062896a473c254781ee397e3da318e/docs/mkdir -------------------------------------------------------------------------------- /docs/ref/client_and_config.rst: -------------------------------------------------------------------------------- 1 | ============ 2 | Node Client 3 | ============ 4 | 5 | The ``Config`` class is used to intialized the ``NodeClient`` 6 | 7 | .. autoclass:: aeternity.node.Config 8 | :members: __init__ 9 | 10 | The ``NodeClient`` is the main object to interact with an Aeternity node. 11 | 12 | .. autoclass:: aeternity.node.NodeClient 13 | :members: 14 | -------------------------------------------------------------------------------- /docs/ref/constants.rst: -------------------------------------------------------------------------------- 1 | =========== 2 | Constants 3 | =========== 4 | 5 | 6 | The following are a list of constants used throughout the SDK 7 | 8 | .. literalinclude:: ../../aeternity/defaults.py 9 | :lines: 3- 10 | -------------------------------------------------------------------------------- /docs/ref/index.rst: -------------------------------------------------------------------------------- 1 | ============= 2 | API Reference 3 | ============= 4 | 5 | .. toctree:: 6 | :maxdepth: 1 7 | 8 | client_and_config 9 | txobject 10 | txbuilder 11 | txsigner 12 | constants 13 | -------------------------------------------------------------------------------- /docs/ref/txbuilder.rst: -------------------------------------------------------------------------------- 1 | ========= 2 | TxBuilder 3 | ========= 4 | 5 | 6 | .. autoclass:: aeternity.transactions.TxBuilder 7 | :members: 8 | -------------------------------------------------------------------------------- /docs/ref/txobject.rst: -------------------------------------------------------------------------------- 1 | ======== 2 | TxObject 3 | ======== 4 | 5 | ``TxObject`` is one of the central entity of the Python SDK, 6 | and it represent a transaction object. 7 | 8 | .. autoclass:: aeternity.transactions.TxObject 9 | :members: get, meta, ga_meta 10 | 11 | The fields of a ``TxObject`` are 12 | 13 | - ``hash``: the transaction hash 14 | - ``data``: the transaction data, it varies for every transaction type 15 | - ``metadata``: it contains additional data that may be relevant in the transaction context 16 | - ``tx``: the rlp + base64 check encoded string of the transaction that is used to broadcast a transaction 17 | 18 | Since a transaction can be a nested structured, the ``TxObject`` is nested as well: 19 | considering a simple spend transaction, the actual structure of the transaction is: 20 | 21 | :: 22 | 23 | SignedTx( 24 | tag: - signed transaction type (11) 25 | version - transaction version, 1 in this case 26 | [signature] - the list of signatures for the signed transaction 27 | tx: SpendTx( - the inner spend transaction 28 | tag - spend transaction type (12) 29 | version - spend transaction version, 1 in this case 30 | sender_id - spend sender 31 | recipient_id - spend recipient 32 | amount - amount being transferred 33 | fee - fee for the miner 34 | ttl - the mempool time to live 35 | nonce - sender account nonce 36 | payload - arbitrary payload 37 | ) 38 | ) 39 | 40 | This means that to access, for example, the spend transaction ``recipient_id`` from a ``TxObject``, 41 | the code would be: 42 | 43 | :: 44 | 45 | tx_object = node_cli.spend(sender, recipient, "100AE") 46 | # access the recipient_id 47 | tx_object.data.tx.data.recipient_id 48 | 49 | unless the transaction has been posted from a generalized account, in which case there 50 | are 4 levels of nesting: 51 | 52 | :: 53 | 54 | tx_object = node_cli.spend(sender, recipient, "100AE") 55 | # access the recipient_id for a GA generated transaction 56 | tx_object.data.tx.data.tx.data.tx.data.recipient_id 57 | 58 | This is of course somewhat awkward, and therefore the ``TxObject`` provides the ``get(NAME)``, ``meta(NAME)``, ``ga_meta(NAME)`` functions. 59 | 60 | The functions are used to access the values of the properties without worrying about the structure of the transaction, 61 | so the example above will become: 62 | 63 | :: 64 | 65 | tx_object = node_cli.spend(sender, recipient, "100AE") 66 | # access the recipient_id for any spend transaction 67 | tx_object.get("recipient_id") 68 | 69 | Metadata 70 | -------- 71 | Metadatas are special informations that are not part of the transaction itself but my be generated 72 | ad a additional output while creating or parsing a transaction, in particular metadata fields are: 73 | 74 | - ``min_fee`` minimum fee for a transaction, this value is always calculaed and can be used to 75 | evaluate the actual fee used for the transaction. 76 | - ``contract_id`` the id of a contract, only present when deploying a new contract (starts with prefix ``ct_``). 77 | - ``salt`` the random generated salt to prepare the ``commitment_id`` of a name pre-claim transaction. The 78 | salt must be used then to prepare a claim transaction. 79 | 80 | TxObject data fields 81 | -------------------- 82 | Here is the complete list of transactions and available fields: 83 | 84 | 85 | .. literalinclude:: ../../aeternity/transactions.py 86 | :lines: 65-389 87 | :dedent: 4 88 | -------------------------------------------------------------------------------- /docs/ref/txsigner.rst: -------------------------------------------------------------------------------- 1 | ======== 2 | TxSigner 3 | ======== 4 | 5 | 6 | .. autoclass:: aeternity.transactions.TxSigner 7 | :members: 8 | -------------------------------------------------------------------------------- /docs/snippets/accounts.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import argparse 3 | import json 4 | from aeternity.signing import Account 5 | 6 | """ 7 | Example app to deal with common dev issues: 8 | - export secret/public key from a keystore 9 | - generate a number of accounts to be used 10 | """ 11 | 12 | # max number of account to generate 13 | MAX_N_ACCOUNTS = 1000 14 | 15 | def cmd_export(args): 16 | try: 17 | a = Account.from_keystore(args.keystore_path, args.password) 18 | print(json.dumps( 19 | { 20 | "keystore": args.keystore_path, 21 | "secret_key": a.get_secret_key(), 22 | "address": a.get_address() 23 | }, indent=2)) 24 | except Exception as e: 25 | print(f"Invalid keystore or password: {e}") 26 | 27 | def cmd_generate(args): 28 | try: 29 | if args.n > MAX_N_ACCOUNTS: 30 | print(f"Max number of accounts to generate is {MAX_N_ACCOUNTS}, requested: {args.n}") 31 | accounts = [] 32 | for i in range(args.n): 33 | a = Account.generate() 34 | accounts.append({ 35 | "index": i, 36 | "secret_key": a.get_secret_key(), 37 | "address": a.get_address() 38 | }) 39 | print(json.dumps(accounts, indent=2)) 40 | except Exception as e: 41 | print(f"Generation error: {e}") 42 | 43 | 44 | if __name__ == "__main__": 45 | commands = [ 46 | { 47 | 'name': 'export', 48 | 'help': 'export the secret/public key of a encrypted keystore as plain text WARNING! THIS IS UNSAFE, USE FOR DEV ONLY', 49 | 'target': cmd_export, 50 | 'opts': [ 51 | { 52 | "names": ["keystore_path"], 53 | "help": "the keystore to use export", 54 | }, 55 | { 56 | "names": ["-p", "--password"], 57 | "help": "the keystore password (default blank)", 58 | "default": "" 59 | } 60 | ] 61 | }, 62 | { 63 | 'name': 'generate', 64 | 'help': 'generate one or more accounts and print them on the stdout', 65 | 'target': cmd_generate, 66 | 'opts': [ 67 | { 68 | "names": ["-n"], 69 | "help": "number of accounts to generate (default 10)", 70 | "default": 10, 71 | } 72 | ] 73 | } 74 | ] 75 | parser = argparse.ArgumentParser() 76 | subparsers = parser.add_subparsers() 77 | subparsers.required = True 78 | subparsers.dest = 'command' 79 | # register all the commands 80 | for c in commands: 81 | subparser = subparsers.add_parser(c['name'], help=c['help']) 82 | subparser.set_defaults(func=c['target']) 83 | # add the sub arguments 84 | for sa in c.get('opts', []): 85 | subparser.add_argument(*sa['names'], 86 | help=sa['help'], 87 | action=sa.get('action'), 88 | default=sa.get('default')) 89 | 90 | # parse the arguments 91 | args = parser.parse_args() 92 | # call the function 93 | args.func(args) 94 | -------------------------------------------------------------------------------- /docs/snippets/index.rst: -------------------------------------------------------------------------------- 1 | ======================= 2 | Code Snippets 3 | ======================= 4 | 5 | 6 | This is a collection of code snippets and scripts that may 7 | be used for copy/paste or quick references. 8 | 9 | 10 | Top up account from the Faucet 11 | ============================== 12 | 13 | Code to programmatically top-up an account using the `Faucet`_ 14 | 15 | .. _faucet: https://testnet.faucet.aepps.com 16 | 17 | .. code-block:: python 18 | 19 | def top_up_account(account_address): 20 | 21 | print(f"top up account {account_address} using the testnet.faucet.aepps.com app") 22 | r = requests.post(f"https://testnet.faucet.aepps.com/account/{account_address}").json() 23 | tx_hash = r.get("tx_hash") 24 | balance = utils.format_amount(r.get("balance")) 25 | print(f"account {account_address} has now a balance of {balance}") 26 | print(f"faucet transaction hash {tx_hash}") 27 | 28 | 29 | Generate multiple accounts 30 | =============================== 31 | 32 | The following is a command line tool to generate multiple accounts 33 | and to export the accounts secret/public keys. 34 | Useful for testing 35 | 36 | 37 | .. literalinclude:: accounts.py 38 | 39 | -------------------------------------------------------------------------------- /docs/topics/index.rst: -------------------------------------------------------------------------------- 1 | Topics 2 | ============== 3 | 4 | .. toctree:: 5 | :maxdepth: 1 6 | 7 | install 8 | keystore_format_change 9 | -------------------------------------------------------------------------------- /docs/topics/install.rst: -------------------------------------------------------------------------------- 1 | ================================ 2 | How to install the Aeternity SDK 3 | ================================ 4 | 5 | This document will explain different way to install the Aeternity Python SDK 6 | 7 | 8 | Install Python 9 | ============== 10 | 11 | Get the latest version of Python at https://www.python.org/downloads/ or with your operating system’s package manager. 12 | 13 | 14 | Install the SDK 15 | =============== 16 | 17 | Installation instructions are slightly different depending on whether you're 18 | installing official release or fetching the latest development version. 19 | 20 | .. _installing-official-release: 21 | 22 | Installing an official release with ``pip`` 23 | ------------------------------------------- 24 | 25 | This is the recommended way to install the Aeternity SDK 26 | 27 | #. Install pip_. The easiest is to use the `standalone pip installer`_. If your 28 | distribution already has ``pip`` installed, you might need to update it if 29 | it's outdated. If it's outdated, you'll know because installation won't 30 | work. 31 | 32 | #. Take a look at virtualenv_ and virtualenvwrapper_. These tools provide 33 | isolated Python environments, which are more practical than installing 34 | packages system-wide. They also allow installing packages without 35 | administrator privileges. The :doc:`contributing tutorial 36 | ` walks through how to create a virtualenv. 37 | 38 | #. After you've created and activated a virtual environment, enter the command: 39 | 40 | :: 41 | 42 | $ python -m pip install aepp-sdk 43 | 44 | .. _pip: https://pip.pypa.io/ 45 | .. _virtualenv: https://virtualenv.pypa.io/ 46 | .. _virtualenvwrapper: https://virtualenvwrapper.readthedocs.io/en/latest/ 47 | .. _standalone pip installer: https://pip.pypa.io/en/latest/installing/#installing-with-get-pip-py 48 | 49 | .. _installing-distribution-package: 50 | 51 | 52 | Installing the development version 53 | ---------------------------------- 54 | 55 | If you'd like to be able to update your SDK code occasionally with the 56 | latest bug fixes and improvements, follow these instructions: 57 | 58 | #. Make sure that you have Git_ installed and that you can run its commands 59 | from a shell. (Enter ``git help`` at a shell prompt to test this.) 60 | 61 | #. Install Poetry_ and make sure it is available in your ``PATH`` 62 | 63 | 64 | #. Check out the SDK main development branch like so: 65 | 66 | :: 67 | 68 | $ git clone https://github.com/aeternity/aepp-sdk-python.git 69 | 70 | This will create a directory ``aepp-sdk-python`` in your current directory. 71 | 72 | #. Make sure that the Python interpreter can load the SDK's code. The most 73 | convenient way to do this is to use virtualenv_, virtualenvwrapper_, and 74 | pip_. 75 | 76 | #. After setting up and activating the virtualenv, run the following command: 77 | 78 | :: 79 | 80 | $ poetry build 81 | $ python -m pip install dist/$(ls -tr dist | grep whl | tail -1) 82 | 83 | This will make the SDK code importable, and will also make the 84 | ``aecli`` utility command available. In other words, you're all set! 85 | 86 | .. _Poetry: https://poetry.eustace.io/ 87 | 88 | When you want to update your copy of the SDK source code, run the command 89 | ``git pull`` from within the ``aepp-sdk-directory`` directory. When you do this, Git will 90 | download any changes. 91 | 92 | 93 | .. admonition:: Where is the command line client? 94 | 95 | The Python SDK bundles a command line client ``aecli`` that can be use to submit 96 | transactions and poke around the Aeternity blockchain. 97 | To be able to access the command line client you have to add it to your executable 98 | path. To find out where is your base path for installed python libraries use the command 99 | ``python -m site --user-base`` 100 | 101 | 102 | -------------------------------------------------------------------------------- /docs/topics/keystore_format_change.rst: -------------------------------------------------------------------------------- 1 | =============================== 2 | Keystore format change [LEGACY] 3 | =============================== 4 | 5 | The release 0.25.0.1 of the Python SDK change the format of the keystore used to store 6 | a secret key in an encrypted format. 7 | 8 | The previous format, suported till version ``0.25.0.1b1`` is not supported anymore and secret keys 9 | encrypted with the legacy format will have to be updated manually. 10 | 11 | .. warning:: ⚠️ BEFORE YOU START 12 | 13 | The following procedure will print your secret key unencrypted on the terminal. 14 | 15 | **Make sure you perform this procedure in a secret setting.** 16 | 17 | 18 | 19 | The follwing steps are required to upgade a keystore (named `my_account.json`). 20 | 21 | #. Install the `aepp-sdk` v0.25.0.1b1 22 | 23 | :: 24 | 25 | $ pip install aepp-sdk==0.25.0.1b1 26 | 27 | Verify that you are using the correct version 28 | 29 | :: 30 | 31 | $ aecli --version 32 | aecli, version 0.25.0.1b1 33 | 34 | #. Print your secret key to the terminal 35 | 36 | :: 37 | 38 | aecli account address --secret-key my_account.json 39 | Enter the account password []: 40 | !Warning! this will print your secret key on the screen, are you sure? [y/N]: y 41 | 42 | Address ___________________________________________ ak_2UeaQn7Ei7HoMvDTiq2jyDuE8ymEQMPZExzC64qWTxpUnanYsE 43 | Signing key _______________________________________ 507f598f2fac1a4ab57edae53650cbf7ffae9eeeea1a297cc7c3b6172052e55ec27954c4ba901cf9b3760dc12b2c313d60fcc674ba2d04746ed813a91499a2ed 44 | 45 | 46 | #. Install the `aepp-sdk` v0.25.0.1 47 | 48 | :: 49 | 50 | $ pip install aepp-sdk==0.25.0.1 51 | 52 | Verify that you are using the correct version 53 | 54 | :: 55 | 56 | $ aecli --version 57 | aecli, version 0.25.0.1 58 | 59 | #. Save the secret key to the new format 60 | 61 | :: 62 | 63 | aecli account save my_account_new.json 507f598f2fac1a4ab57edae53650cbf7ffae9eeeea1a297cc7c3b6172052e55ec27954c4ba901cf9b3760dc12b2c313d60fcc674ba2d04746ed813a91499a2ed 64 | Enter the account password []: 65 | 66 | Address ___________________________________________ ak_2UeaQn7Ei7HoMvDTiq2jyDuE8ymEQMPZExzC64qWTxpUnanYsE 67 | Path ______________________________________________ /Users/andrea/tmp/aeternity/my_account_new.json 68 | 69 | 70 | 5. Verify that the new keystore has the correct format 71 | 72 | the content of the keystore file should appear as the following: 73 | 74 | :: 75 | 76 | $ cat my_account_new.json 77 | {"public_key": "ak_2UeaQn7Ei7HoMvDTiq2jyDuE8ymEQMPZExzC64qWTxpUnanYsE", "crypto": {"secret_type": "ed25519", "symmetric_alg": "xsalsa20-poly1305", "ciphertext": "f431af7e6f00da7f9acc8187900b97d42526eb135f4db0da80a7809f36a00e37f3a313f7c611784f381e58620bb2c23ef2686c3e61af28381f3a2dc6b0fcc168d46fd8d3c2bd473311140b7ee5acaa2d", "cipher_params": {"nonce": "1ea25d885a68adf13998e0fad17b22e7ade78f5cf1670eb1"}, "kdf": "argon2id", "kdf_params": {"memlimit_kib": 262144, "opslimit": 3, "salt": "c3dd4a4ac8347b3ad706756b96919387", "parallelism": 1}}, "id": "44c3d693-a890-4ac1-936b-0a65c8293388", "name": "", "version": 1} 78 | 79 | 6. Cleanup! 80 | 81 | Once the operation is completed, cleanup the terminaly history that contains 82 | your secret key 83 | 84 | :: 85 | 86 | history -c 87 | 88 | And remove the old account file 89 | 90 | :: 91 | 92 | rm my_account.json 93 | -------------------------------------------------------------------------------- /docs/utilities.md: -------------------------------------------------------------------------------- 1 | # Utilities 2 | 3 | This page collects some examples and utilities that can come handy during development 4 | 5 | 6 | ## Accounts 7 | 8 | This section is dedicated to accounts. 9 | 10 | ### Export and generation 11 | 12 | The following script can be used to export an account or to generate multiple accounts for testing purposes, 13 | 14 | 15 | ```python 16 | 17 | #!/usr/bin/env python 18 | import argparse 19 | import json 20 | from aeternity.signing import Account 21 | 22 | """ 23 | Example app to deal with common dev issues: 24 | - export secret/public key from a keystore 25 | - generate a number of accounts to be used 26 | """ 27 | 28 | # max number of account to generate 29 | MAX_N_ACCOUNTS = 1000 30 | 31 | def cmd_export(args): 32 | try: 33 | a = Account.from_keystore(args.keystore_path, args.password) 34 | print(json.dumps( 35 | { 36 | "keystore": args.keystore_path, 37 | "secret_key": a.get_secret_key(), 38 | "address": a.get_address() 39 | }, indent=2)) 40 | except Exception as e: 41 | print(f"Invalid keystore or password: {e}") 42 | 43 | def cmd_generate(args): 44 | try: 45 | if args.n > MAX_N_ACCOUNTS: 46 | print(f"Max number of accounts to generate is {MAX_N_ACCOUNTS}, requested: {args.n}") 47 | accounts = [] 48 | for i in range(args.n): 49 | a = Account.generate() 50 | accounts.append({ 51 | "index": i, 52 | "secret_key": a.get_secret_key(), 53 | "address": a.get_address() 54 | }) 55 | print(json.dumps(accounts, indent=2)) 56 | except Exception as e: 57 | print(f"Generation error: {e}") 58 | 59 | 60 | if __name__ == "__main__": 61 | commands = [ 62 | { 63 | 'name': 'export', 64 | 'help': 'export the secret/public key of a encrypted keystore as plain text WARNING! THIS IS UNSAFE, USE FOR DEV ONLY', 65 | 'target': cmd_export, 66 | 'opts': [ 67 | { 68 | "names": ["keystore_path"], 69 | "help": "the keystore to use export", 70 | }, 71 | { 72 | "names": ["-p", "--password"], 73 | "help": "the keystore password (default blank)", 74 | "default": "" 75 | } 76 | ] 77 | }, 78 | { 79 | 'name': 'generate', 80 | 'help': 'generate one or more accounts and print them on the stdout', 81 | 'target': cmd_generate, 82 | 'opts': [ 83 | { 84 | "names": ["-n"], 85 | "help": "number of accounts to generate (default 10)", 86 | "default": 10, 87 | } 88 | ] 89 | } 90 | ] 91 | parser = argparse.ArgumentParser() 92 | subparsers = parser.add_subparsers() 93 | subparsers.required = True 94 | subparsers.dest = 'command' 95 | # register all the commands 96 | for c in commands: 97 | subparser = subparsers.add_parser(c['name'], help=c['help']) 98 | subparser.set_defaults(func=c['target']) 99 | # add the sub arguments 100 | for sa in c.get('opts', []): 101 | subparser.add_argument(*sa['names'], 102 | help=sa['help'], 103 | action=sa.get('action'), 104 | default=sa.get('default')) 105 | 106 | # parse the arguments 107 | args = parser.parse_args() 108 | # call the function 109 | args.func(args) 110 | 111 | ``` 112 | 113 | ## Transactions 114 | 115 | this section is dedicated to common operations on accounts. 116 | 117 | ### Transactions confirmation 118 | 119 | The following snippet illustrate how to explicitly wait for transactions. 120 | 121 | ```python 122 | #!/usr/bin/env python 123 | from aeternity.node import NodeClient 124 | 125 | 126 | def main(): 127 | try: 128 | # example node hash 129 | tx_hash = "th_XWPEjQfF6PY27V2ruJgDeMkbHTScqyEkgQoPBF2DNSokRSW98" 130 | # instantiate the node client 131 | # set the key_block_confirmation_num to 10 132 | n = NodeClient( 133 | external_url="https://sdk-mainnet.aepps.com", 134 | key_block_confirmation_num=10 # number of blocks for considering a block mined 135 | ) 136 | # wait for a transaction to be included and confirmed 137 | # this call is blocking 138 | n.wait_for_confirmation(tx_hash) 139 | # alternatively test only if a transaction 140 | # is in the chain, no confirmation beside that 141 | # has been seen in the chain. 142 | n.wait_for_transaction(tx_hash) 143 | except Exception as e: 144 | print(f"Error: {e}") 145 | 146 | if __name__ == "__main__": 147 | main() 148 | ``` -------------------------------------------------------------------------------- /docs/utils.md: -------------------------------------------------------------------------------- 1 | # Utilities built in in the Python SDK 2 | 3 | The scope of this page is to list some of the utilities built-in the Python SDK 4 | 5 | ## Transactions 6 | 7 | The section covers the utilities related to transaction management. 8 | 9 | ### Compute a transaction hash 10 | 11 | In the Æternity blockchain the transaction hashes are deterministic and can be 12 | computed without interacting with a node. 13 | 14 | The following is a code snippet about how to compute a transaction hash: 15 | 16 | ```python 17 | 18 | from aeternity.transactions import TxBuilder 19 | from aeternity.node import NodeClient, Config # we need this to retrieve the transaction 20 | 21 | # The TxBuilder object is the one that is responsible to 22 | # compose, encode and decode transactions 23 | 24 | # let's get as example the transaction th_UDARRXdLrgGyzZsvKrkzo3i51x6gRdfjpbKxjA5dDHSjdBaEr 25 | # you can get the details from https://sdk-mainnet.aepps.com/v2/transactions/th_UDARRXdLrgGyzZsvKrkzo3i51x6gRdfjpbKxjA5dDHSjdBaEr 26 | 27 | x = { 28 | "block_hash":"mh_2ALC3nX5Hm9488yhPKn65egU6KWugMnAyhYiBq3eRVn9Bf2mD1","block_height":123003,"hash":"th_UDARRXdLrgGyzZsvKrkzo3i51x6gRdfjpbKxjA5dDHSjdBaEr","signatures":["sg_HPuAmqhiY84sKbQp8LiDSjH7yYiTubdVkPYewftBihaTLcAzDyY4EwRW8m6nsaLHtZN4GkBs3LtjWyKRWZybUocuwREHe"],"tx":{"amount":1000000000000000000,"fee":16820000000000,"nonce":11,"payload":"ba_Xfbg4g==","recipient_id":"ak_VcWJxsExtNwwe46junXTq8CpcNuhfxFKsaqWm5CGJuqqCAjTJ","sender_id":"ak_u2gFpRN5nABqfqb5Q3BkuHCf8c7ytcmqovZ6VyKwxmVNE5jqa","type":"SpendTx","version":1}} 29 | 30 | tx_hash = "th_UDARRXdLrgGyzZsvKrkzo3i51x6gRdfjpbKxjA5dDHSjdBaEr" 31 | 32 | n = NodeClient(Config(external_url="https://sdk-mainnet.aepps.com")) 33 | # the get_transaction from the node will retrieve the transaction 34 | # from the api and build a TxObject for us 35 | tx = n.get_transaction(hash=tx_hash) 36 | 37 | # the TxObject contains the calculated hash 38 | tx.hash 39 | 40 | 41 | 42 | 43 | 44 | 45 | ``` 46 | -------------------------------------------------------------------------------- /examples/aens.md: -------------------------------------------------------------------------------- 1 | # Aens 2 | 3 | This example will show how to claim a name using the python sdk 4 | 5 | ## Programmatically 6 | 7 | TODO 8 | 9 | # CLI 10 | 11 | Register a name using the cli 12 | 13 | ### Step 1: pre-claim 14 | 15 | ``` 16 | $ aecli name pre-claim rea abcsdads.test 17 | Enter the account password: 18 | 19 | 20 | Tag _____________________________________________ 33 21 | Vsn _____________________________________________ 1 22 | Account id ______________________________________ ak_REAu6DK8eqW5juKhGKTZo7svi1EcKxAsu7vq6yRUaNw4K6Ypg 23 | Nonce ___________________________________________ 1 24 | Commitment id ___________________________________ cm_2j44A8k8BrM7i3bF8daP3G6GEKcRxnJR5Dmhpb4XBbKFUGcLiw 25 | Fee _____________________________________________ 16660000000000 26 | Ttl _____________________________________________ 0 27 | 28 | 29 | Salt ____________________________________________ 2506137870146646926 30 | 31 | Tx ________________________________________________ tx_+JkLAfhCuEB55SXB1T4Ky58B5p93LJ+AxRrPK86JkHsjrGcHm40k64ISb2q5Sw86pSl76xBJRx4/hiGoP1CiZugUPzn6SLQFuFH4TyEBoQE3ArNgfdSENAyDj62+HrTamcCQMBT3YS6Do12LJb96/QGhA+MuiyIt2sbqyUTNiDsi2beVwzRZtAwg3qdu4en3foqYhg8m9WHIAABg1TjO 32 | Hash ______________________________________________ th_EtkGeA8fSMgBSD4QkR2mbDBd6tMSDTFX9ydKfHT5kwkAjWmbF 33 | Signature _________________________________________ sg_GwwtULX1Y1CNB8Jpxz7XKx6opZ8TidEEZPaTrcKtfULvMuCiYpBjPp5H7shfRRMmNGSRQzQyfWSQen8YUgrPmiipAUbL6 34 | Network id ________________________________________ ae_mainnet 35 | 36 | ``` 37 | 38 | Inspect the transaction: 39 | 40 | ``` 41 | $ aecli inspect th_EtkGeA8fSMgBSD4QkR2mbDBd6tMSDTFX9ydKfHT5kwkAjWmbF 42 | 43 | Block hash ________________________________________ mh_UdXT6AZaAAp1B8GBy3qcyKzYJ29mJEVZ7KRh4JWBLE5GJUSCR 44 | Block height ______________________________________ 46893 45 | Hash ______________________________________________ th_EtkGeA8fSMgBSD4QkR2mbDBd6tMSDTFX9ydKfHT5kwkAjWmbF 46 | 47 | Signature #1 ____________________________________ sg_GwwtULX1Y1CNB8Jpxz7XKx6opZ8TidEEZPaTrcKtfULvMuCiYpBjPp5H7shfRRMmNGSRQzQyfWSQen8YUgrPmiipAUbL6 48 | 49 | 50 | Account id ______________________________________ ak_REAu6DK8eqW5juKhGKTZo7svi1EcKxAsu7vq6yRUaNw4K6Ypg 51 | Commitment id ___________________________________ cm_2j44A8k8BrM7i3bF8daP3G6GEKcRxnJR5Dmhpb4XBbKFUGcLiw 52 | Fee _____________________________________________ 16660000000000 53 | Nonce ___________________________________________ 1 54 | Type ____________________________________________ NamePreclaimTx 55 | Version _________________________________________ 1 56 | 57 | 58 | ``` 59 | 60 | ### Step 2: claim 61 | 62 | Run the claim contract immediately will fail 63 | 64 | ``` 65 | $ aecli name claim rea abcsdads.test --name-salt=2506137870146646926 --preclaim-tx-hash=th_EtkGeA8fSMgBSD4QkR2mbDBd6tMSDTFX9ydKfHT5kwkAjWmbF 66 | Enter the account password: 67 | 68 | Message ___________________________________________ It is not safe to execute the name claim before height 46896, current height: 46893 69 | 70 | ``` 71 | 72 | This happens because it is not safe to send a claim transaction before making sure the pre-claim is confirmed. 73 | The claim operation exposes the actual domain been registered and if a preclaim has not been confirmed the domain 74 | name could be squatted. 75 | 76 | 77 | After waiting the the height has been reached: 78 | 79 | ``` 80 | $ aecli chain top 81 | $ aecli chain top 82 | 83 | Beneficiary _______________________________________ ak_542o93BKHiANzqNaFj6UurrJuDuxU61zCGr9LJCwtTUg34kWt 84 | Hash ______________________________________________ kh_zkMD8E8ttzKE5nVswAfmhQNcmwWWxhFsJ7xt5c4A2NC2am3ij 85 | Height ____________________________________________ 46896 86 | Miner _____________________________________________ ak_2f5T1WYcbLBxifqtBBi4ZNrMbUUfaFyhxJcupyHqJBMWRvYrWA 87 | Nonce _____________________________________________ 113778650302622 88 | Prev hash _________________________________________ kh_C9v4tKCj54Qhcstg5H2fRAZpXKSoG4sv5U7Ged697PYJ6wvRP 89 | Prev key hash _____________________________________ kh_C9v4tKCj54Qhcstg5H2fRAZpXKSoG4sv5U7Ged697PYJ6wvRP 90 | State hash ________________________________________ bs_Z8ohBeRtZ72VyWaqZZCPzQhjrjTtoBDwbpUSXCtc4HpTymdHS 91 | Target ____________________________________________ 503658739 92 | Time ______________________________________________ 2019-03-05T15:53:14.494000+00:00 93 | Version ___________________________________________ 1 94 | 95 | 96 | ``` 97 | 98 | It is safe to claim the name: 99 | 100 | ``` 101 | $ aecli name claim rea abcsdads.test --name-salt=2506137870146646926 --preclaim-tx-hash=th_EtkGeA8fSMgBSD4QkR2mbDBd6tMSDTFX9ydKfHT5kwkAjWmbF 102 | Enter the account password: 103 | 104 | 105 | Tag _____________________________________________ 32 106 | Vsn _____________________________________________ 1 107 | Account id ______________________________________ ak_REAu6DK8eqW5juKhGKTZo7svi1EcKxAsu7vq6yRUaNw4K6Ypg 108 | Nonce ___________________________________________ 2 109 | Name ____________________________________________ abcsdads.test 110 | Name salt _______________________________________ 2506137870146646926 111 | Fee _____________________________________________ 16440000000000 112 | Ttl _____________________________________________ 0 113 | 114 | Metadata 115 | Tx ________________________________________________ tx_+I4LAfhCuEA7HGDR5TMHWErNQcQuML1bHTwBYWtMXAmNqyTTZMikhZ37SythjjUPdX2RfWfQ/id+Sg3d8HMjHJtfyThDSecAuEb4RCABoQE3ArNgfdSENAyDj62+HrTamcCQMBT3YS6Do12LJb96/QKNYWJjc2RhZHMudGVzdIgix5cdZD6zjoYO87xcMAAAP5EMUg== 116 | Hash ______________________________________________ th_hmYqc3BDeTqLMw2Ti6eotGSU53DBpiG2Gngm5pt4aJXtqfGDp 117 | Signature _________________________________________ sg_8jY6iqQuPejk5divU4wD4TF9Xq4RJxKmYQd8Rnve7yy5AJJP7QT5XLKKvVYih64ECBU5XoscUdbySmL2XPtxCxZM1vT96 118 | Network id ________________________________________ ae_mainnet 119 | 120 | $ 121 | ``` 122 | 123 | And to inspect it: 124 | ``` 125 | $ aecli inspect abcsdads.test  ✔  10060  2019-03-05T17:00 126 | 127 | Id ________________________________________________ nm_2RLergfp7jmg18aaU1JL5NMm4fJhqhSqN9HbG6AqZikm575NqK 128 | 129 | 130 | Ttl _______________________________________________ 96896 131 | 132 | ``` -------------------------------------------------------------------------------- /examples/index.md: -------------------------------------------------------------------------------- 1 | # Examples 2 | 3 | ## Spend transaction (programmatically) 4 | 5 | The sample will use `testnet` 6 | 7 | ### Before you start 8 | 9 | Will be using a newly created account for the following example. The first account will be the sender account. 10 | 11 | ```python 12 | 13 | from aeternity import signing 14 | # generate a new account 15 | sender_account = signing.Account.generate() 16 | # save the account in an encrypted format 17 | sender_account.save_to_keystore_file("sender_account.json", "mypassword") 18 | # print the account address 19 | print("Sender account address: ", sender_account.get_address()) 20 | 21 | ``` 22 | 23 | Go to [testnet.faucet.aepps.com](testnet.faucet.aepps.com) and paste there the sender account address 24 | 25 | ### First example: easy spend transaction 26 | 27 | The account created at this step will be the recipient account 28 | 29 | ```python 30 | 31 | from aeternity import signing, node 32 | 33 | # open the sender account 34 | sender_account = signing.Account.from_keystore("sender_account.json", "mypassword") 35 | # generate a new account 36 | recipient_account = signing.Account.generate() 37 | 38 | # initialize a node client 39 | aeternity_cli = node.NodeClient(node.Config( 40 | external_url="https://sdk-testnet.aepps.com", 41 | network_id="ae_uat" # optional 42 | )) 43 | # now build the transaction 44 | tx = aeternity_cli.spend(sender_account, 45 | recipient_account.get_address(), 46 | 1000000000000000000 47 | ) 48 | print(f"https://testnet.explorer.aepps.com/#/tx/{tx.hash}") 49 | 50 | 51 | ``` 52 | 53 | ## Secound example: 3 steps spend transaction 54 | 55 | ```python 56 | 57 | from aeternity import transactions, signing, node, identifiers 58 | 59 | # open the sender account 60 | sender_account = signing.Account.from_keystore("sender_account.json", "mypassword") 61 | # generate a new account 62 | recipient_account = signing.Account.generate() 63 | 64 | 65 | # Step 1: build the transaction 66 | 67 | # in this case we have to know the nonce before 68 | # since building the transaction is performed offline 69 | nonce = 2 70 | 71 | tx_builder = transactions.TxBuilder() 72 | tx = tx_builder.tx_spend(sender_account.get_address(), 73 | recipient_account.get_address(), 74 | 1000000000000000000, 75 | "test tx", 76 | 0, # fee, when 0 it is automatically computed 77 | 0, # ttl for the transaction in number of blocks (default 0) 78 | nonce) 79 | 80 | # Step 2: sign the transaction 81 | tx_signer = transactions.TxSigner( 82 | sender_account, 83 | identifiers.NETWORK_ID_TESTNET # ae_uat 84 | ) 85 | tx_signed = tx_signer.sign_encode_transaction(tx) 86 | 87 | 88 | # Step 3: broadcast the transaction 89 | 90 | # initialize a node client 91 | aeternity_cli = node.NodeClient(node.Config( 92 | external_url="https://sdk-testnet.aepps.com", 93 | )) 94 | 95 | # broadcast the transaction 96 | aeternity_cli.broadcast_transaction(tx_signed.tx, tx_signed.hash) 97 | 98 | print(f"https://testnet.explorer.aepps.com/#/tx/{tx_signed.hash}") 99 | print(f"now waiting for confirmation (it will take ~9 minutes)...") 100 | 101 | # Step 4: [optional] wait for transaction verification 102 | # the default will wait 3 blocks after the transaction generation blocks 103 | # the confirmation blocks number can be changed passing the 104 | # key_block_confirmation_num 105 | # parameter to teh node.Config 106 | aeternity_cli.wait_for_confirmation(tx_signed.hash) 107 | 108 | print(f"transaction confirmed!") 109 | 110 | ``` 111 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "aepp-sdk" 3 | version = "7.0.0" 4 | description = "Python SDK to interact with the Æternity blockchain" 5 | authors = [ 6 | "Andrea Giacobino ", 7 | "Shubhendu Shekhar " 8 | ] 9 | license = "ISC" 10 | homepage = "https://github.com/aeternity/aepp-sdk-python" 11 | repository = "https://github.com/aeternity/aepp-sdk-python" 12 | readme = "README.md" 13 | keywords = ["aeternity", "blockchain", "sdk"] 14 | include = ["CHANGELOG.md"] 15 | classifiers = [ 16 | 'License :: OSI Approved :: ISC License (ISCL)', 17 | 'Intended Audience :: Developers', 18 | 'Operating System :: OS Independent', 19 | ] 20 | packages = [ 21 | { include = "aeternity" }, 22 | ] 23 | 24 | [tool.poetry.dependencies] 25 | python = "^3.7" 26 | base58 = ">=1,<3" 27 | click = "^7.0" 28 | rlp = "^1.1" 29 | PyNaCl = "^1.3" 30 | requests = "^2.20" 31 | validators = ">=0.13,<0.15" 32 | semver = "^2.8" 33 | Deprecated = "^1.2" 34 | websockets = ">=7,<9" 35 | simplejson = "^3.16.0" 36 | mnemonic = "^0.19.0" 37 | munch = "^2.5" 38 | 39 | [tool.poetry.dev-dependencies] 40 | pytest = "^5.3" 41 | flake8 = "^3.7" 42 | pytest-cov = "^2.7" 43 | coverage = "^5.0" 44 | sphinx = "^2.3" 45 | sphinx_rtd_theme = "^0.4.3" 46 | git-changelog = "^0.2.0" 47 | 48 | [tool.poetry.extras] 49 | test = ["coverage", "pytest"] 50 | 51 | [tool.poetry.scripts] 52 | aecli = "aeternity.__main__:run" 53 | 54 | [tool.dephell.main] 55 | from = {format = "poetry", path = "pyproject.toml"} 56 | to = {format = "pip", path = "requirements.txt"} 57 | 58 | [build-system] 59 | requires = ["poetry>=1.0.1"] 60 | build-backend = "poetry.masonry.api" 61 | 62 | 63 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | import logging 2 | # for tempdir 3 | 4 | 5 | logging.getLogger("requests").setLevel(logging.DEBUG) 6 | logging.getLogger("urllib3").setLevel(logging.DEBUG) 7 | logging.getLogger("aeternity").setLevel(logging.DEBUG) 8 | logging.root.setLevel(logging.DEBUG) 9 | 10 | 11 | # default values for tests 12 | TEST_TTL = 50 13 | -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import os 3 | import shutil 4 | import tempfile 5 | import random 6 | import string 7 | from munch import Munch 8 | from aeternity.signing import Account 9 | from aeternity.node import NodeClient, Config 10 | from aeternity.compiler import CompilerClient 11 | 12 | 13 | PUBLIC_KEY = os.environ.get('WALLET_PUB') 14 | PRIVATE_KEY = os.environ.get('WALLET_PRIV') 15 | NODE_URL = os.environ.get('TEST_URL', 'http://localhost:3013') 16 | NODE_URL_DEBUG = os.environ.get('TEST_DEBUG_URL', 'http://localhost:3113') 17 | NETWORK_ID = os.environ.get('TEST_NETWORK_ID', 'ae_devnet') 18 | COMPILER_URL = os.environ.get("COMPILER_URL", 'http://localhost:3080') 19 | FORCE_COMPATIBILITY = os.environ.get("FORCE_COMPATIBILITY", False) 20 | 21 | 22 | @pytest.fixture 23 | def tempdir(scope="module"): 24 | # contextmanager to generate and delete a temporary directory 25 | path = tempfile.mkdtemp() 26 | try: 27 | yield path 28 | finally: 29 | shutil.rmtree(path) 30 | 31 | 32 | def random_domain(length=10, tld='chain'): 33 | rand_str = ''.join(random.choice(string.ascii_letters) for _ in range(length)) 34 | return f"{rand_str}.{tld}" 35 | 36 | 37 | @pytest.fixture 38 | def client_fixture(scope="module"): 39 | # Instantiate the node client for the tests 40 | fc = True if FORCE_COMPATIBILITY == "true" else False 41 | NODE_CLI = NodeClient(Config( 42 | external_url=NODE_URL, 43 | internal_url=NODE_URL_DEBUG, 44 | # network_id=NETWORK_ID, 45 | blocking_mode=True, 46 | debug=True, 47 | force_compatibility=fc, 48 | )) 49 | return Munch.fromDict({"NODE_CLI": NODE_CLI}) 50 | 51 | 52 | @pytest.fixture 53 | def chain_fixture(scope="module"): 54 | 55 | # create a new account and fill it with some money 56 | ACCOUNT = Account.generate() 57 | ACCOUNT_1 = Account.generate() # used by for oracles 58 | # set the key folder as environment variables 59 | genesis = Account.from_private_key_string(PRIVATE_KEY) 60 | 61 | # Instantiate the node client for the tests 62 | NODE_CLI = NodeClient(Config( 63 | external_url=NODE_URL, 64 | internal_url=NODE_URL_DEBUG, 65 | # network_id=NETWORK_ID, 66 | blocking_mode=True, 67 | debug=True, 68 | )) 69 | 70 | NODE_CLI.spend(genesis, ACCOUNT.get_address(), '5000AE') 71 | a = NODE_CLI.get_account_by_pubkey(pubkey=ACCOUNT.get_address()) 72 | print(f"Test account is {ACCOUNT.get_address()} with balance {a.balance}") 73 | 74 | NODE_CLI.spend(genesis, ACCOUNT_1.get_address(), '5000AE') 75 | a = NODE_CLI.get_account_by_pubkey(pubkey=ACCOUNT_1.get_address()) 76 | print(f"Test account (1) is {ACCOUNT_1.get_address()} with balance {a.balance}") 77 | 78 | return Munch.fromDict({"NODE_CLI": NODE_CLI, "ALICE": ACCOUNT, "BOB": ACCOUNT_1}) 79 | 80 | 81 | @pytest.fixture 82 | def compiler_fixture(scope="module"): 83 | # Instantiate the node client for the tests 84 | compiler = CompilerClient(COMPILER_URL) 85 | return Munch.fromDict({"COMPILER": compiler}) 86 | 87 | @pytest.fixture 88 | def testdata_fixture(scope="module"): 89 | return os.path.join(os.path.dirname(os.path.realpath(__file__)), "testdata") 90 | -------------------------------------------------------------------------------- /tests/test_aens.py: -------------------------------------------------------------------------------- 1 | from aeternity.aens import AEName 2 | from tests.conftest import random_domain 3 | from aeternity.signing import Account 4 | 5 | from pytest import raises, skip 6 | 7 | 8 | def test_name_validation_fails(chain_fixture): 9 | with raises(ValueError): 10 | chain_fixture.NODE_CLI.AEName('test.lol') 11 | chain_fixture.NODE_CLI.AEName('test.chain') 12 | chain_fixture.NODE_CLI.AEName('test.test') 13 | 14 | 15 | def test_name_validation_succeeds(chain_fixture): 16 | chain_fixture.NODE_CLI.AEName('test.test') 17 | chain_fixture.NODE_CLI.AEName('test.chain') 18 | 19 | 20 | def test_name_is_available(chain_fixture): 21 | domain = random_domain() 22 | name = chain_fixture.NODE_CLI.AEName(domain) 23 | assert name.is_available() 24 | 25 | 26 | def test_name_status_available(chain_fixture): 27 | domain = random_domain(length=13) 28 | name = chain_fixture.NODE_CLI.AEName(domain) 29 | assert name.status == AEName.Status.UNKNOWN 30 | name.update_status() 31 | assert name.status == AEName.Status.AVAILABLE 32 | 33 | 34 | def test_name_claim_lifecycle(chain_fixture): 35 | try: 36 | # avoid auctions 37 | domain = random_domain(length=13) 38 | node_cli = chain_fixture.NODE_CLI 39 | name = node_cli.AEName(domain) 40 | assert name.status == AEName.Status.UNKNOWN 41 | name.update_status() 42 | assert name.status == AEName.Status.AVAILABLE 43 | preclaim = name.preclaim(chain_fixture.ALICE) 44 | assert name.status == AEName.Status.PRECLAIMED 45 | node_cli.wait_for_confirmation(preclaim.hash) 46 | name.claim(preclaim.hash, chain_fixture.ALICE, preclaim.metadata.salt) 47 | assert name.status == AEName.Status.CLAIMED 48 | except Exception as e: 49 | print(e) 50 | assert e is None 51 | 52 | def test_name_auction(chain_fixture): 53 | try: 54 | domain = random_domain(length=12) 55 | node_cli = chain_fixture.NODE_CLI 56 | name = node_cli.AEName(domain) 57 | assert name.status == AEName.Status.UNKNOWN 58 | name.update_status() 59 | assert name.status == AEName.Status.AVAILABLE 60 | preclaim = name.preclaim(chain_fixture.ALICE) 61 | assert name.status == AEName.Status.PRECLAIMED 62 | node_cli.wait_for_confirmation(preclaim.hash) 63 | claim_tx = name.claim(preclaim.hash, chain_fixture.ALICE, preclaim.metadata.salt) 64 | print(claim_tx) 65 | assert name.status == AEName.Status.CLAIMED 66 | # bob will make another bid 67 | name2 = node_cli.AEName(domain) 68 | bid = AEName.compute_bid_fee(domain) 69 | bid_tx = name2.bid(chain_fixture.BOB, bid) 70 | print("BID TX", bid_tx) 71 | # get the tx height 72 | bid_h = node_cli.wait_for_transaction(bid_tx.hash) 73 | print(f"BOB BID Height is {bid_h}") 74 | # now we should wait to see that bob gets the name 75 | bid_ends = AEName.compute_auction_end_block(domain, bid_h) 76 | # 77 | print(f"BID STARTED AT {bid_h} WILL END AT {bid_ends}") 78 | except Exception as e: 79 | print(e) 80 | assert e is None 81 | 82 | 83 | def test_name_status_unavailable(chain_fixture): 84 | # avoid auctions 85 | domain = random_domain(length=13) 86 | print(f"domain is {domain}") 87 | occupy_name = chain_fixture.NODE_CLI.AEName(domain) 88 | occupy_name.full_claim_blocking(chain_fixture.ALICE) 89 | # try to get the same name 90 | same_name = chain_fixture.NODE_CLI.AEName(domain) 91 | assert not same_name.is_available() 92 | 93 | 94 | def test_name_update(chain_fixture): 95 | # avoid auctions 96 | domain = random_domain(length=13) 97 | print(f"domain is {domain}") 98 | name = chain_fixture.NODE_CLI.AEName(domain) 99 | print("Claim name ", domain) 100 | name.full_claim_blocking(chain_fixture.ALICE, chain_fixture.ALICE.get_address()) 101 | # domain claimed 102 | name.update_status() 103 | assert not chain_fixture.NODE_CLI.AEName(domain).is_available(), 'The name should be claimed now' 104 | name.update_status() 105 | print("claimed name", name) 106 | print("pointers", name.pointers) 107 | assert len(name.pointers) > 0, 'Pointers should not be empty' 108 | assert name.pointers[0].id == chain_fixture.ALICE.get_address() 109 | assert name.pointers[0].key == "account_pubkey" 110 | # run another update 111 | name.update(chain_fixture.ALICE, chain_fixture.BOB.get_address(), ("origin",chain_fixture.ALICE.get_address())) 112 | name.update_status() 113 | assert len(name.pointers) == 2, 'Pointers should not be empty' 114 | assert name.pointers[0].id == chain_fixture.BOB.get_address() 115 | assert name.pointers[0].key == "account_pubkey" 116 | assert name.pointers[1].id == chain_fixture.ALICE.get_address() 117 | assert name.pointers[1].key == "origin" 118 | 119 | def test_spend_by_name(chain_fixture): 120 | # claim a domain 121 | domain = random_domain(length=20) 122 | print(f"domain is {domain}") 123 | name = chain_fixture.NODE_CLI.AEName(domain) 124 | print("Claim name ", domain) 125 | # generate a new address 126 | target_address = Account.generate().get_address() 127 | print(f"Target address {target_address}") 128 | name.full_claim_blocking(chain_fixture.ALICE, target_address) 129 | # domain claimed 130 | name.update_status() 131 | assert not chain_fixture.NODE_CLI.AEName(domain).is_available(), 'The name should be claimed now' 132 | name.update_status() 133 | print(f"domain is {name.domain} name_id {name.name_id}") 134 | print("pointers", name.pointers) 135 | tx = chain_fixture.NODE_CLI.spend(chain_fixture.ALICE, domain, '1AE') 136 | print("DATA ", tx) 137 | recipient_account = chain_fixture.NODE_CLI.get_account_by_pubkey(pubkey=target_address) 138 | print(f"recipient address {target_address}, balance {recipient_account.balance}") 139 | assert recipient_account.balance == 1000000000000000000 140 | 141 | # TODO: enable the test check for pointers 142 | 143 | def test_name_transfer_ownership(chain_fixture): 144 | # avoid auctions 145 | domain = random_domain(length=13) 146 | name = chain_fixture.NODE_CLI.AEName(domain) 147 | name.full_claim_blocking(chain_fixture.ALICE) 148 | assert name.status == AEName.Status.CLAIMED 149 | name.update_status() 150 | assert len(name.pointers) == 0, "Pointers should be empty" 151 | 152 | # now transfer the name to the other account 153 | name.transfer_ownership(chain_fixture.ALICE, chain_fixture.BOB.get_address()) 154 | assert name.status == AEName.Status.TRANSFERRED 155 | # try changing the target using that new account 156 | name.update_status() 157 | name.update(chain_fixture.BOB, chain_fixture.BOB.get_address()) 158 | name.update_status() 159 | assert len(name.pointers) > 0, 'Pointers should not be empty' 160 | assert name.pointers[0].id == chain_fixture.BOB.get_address() 161 | assert name.pointers[0].key == "account_pubkey" 162 | 163 | 164 | def test_name_revocation(chain_fixture): 165 | # avoid auctions 166 | domain = random_domain(length=13) 167 | name = chain_fixture.NODE_CLI.AEName(domain) 168 | name.full_claim_blocking(chain_fixture.ALICE) 169 | name.revoke(chain_fixture.ALICE) 170 | assert name.status == AEName.Status.REVOKED 171 | assert chain_fixture.NODE_CLI.AEName(domain).is_available() 172 | -------------------------------------------------------------------------------- /tests/test_api.py: -------------------------------------------------------------------------------- 1 | from tests.conftest import PUBLIC_KEY 2 | from aeternity import __node_compatibility__ 3 | from aeternity.signing import Account 4 | from aeternity import openapi 5 | import semver 6 | 7 | 8 | def test_api_get_account(chain_fixture): 9 | 10 | account = chain_fixture.NODE_CLI.get_account_by_pubkey(pubkey=PUBLIC_KEY) 11 | assert account.balance > 0 12 | 13 | 14 | def test_api_get_version(chain_fixture): 15 | version = chain_fixture.NODE_CLI.get_version() 16 | assert semver.match(version, __node_compatibility__[0]) 17 | assert semver.match(version, __node_compatibility__[1]) 18 | 19 | 20 | def test_api_get_status(chain_fixture): 21 | version = chain_fixture.NODE_CLI.get_version() 22 | assert semver.match(version, __node_compatibility__[0]) 23 | assert semver.match(version, __node_compatibility__[1]) 24 | 25 | 26 | def test_api_get_top_block(chain_fixture): 27 | block = chain_fixture.NODE_CLI.get_top_block() 28 | # assert type(block) == BlockWithTx 29 | assert block.height > 0 30 | 31 | 32 | def test_api_get_block_by_height(chain_fixture): 33 | height = chain_fixture.NODE_CLI.get_current_key_block_height() 34 | 35 | block = chain_fixture.NODE_CLI.get_key_block_by_height(height=height) 36 | # assert type(block) == BlockWithTx 37 | assert block.height > 0 38 | 39 | 40 | def test_api_get_block_by_hash(chain_fixture): 41 | 42 | has_kb, has_mb = False, False 43 | while not has_kb or not has_mb: 44 | # the latest block could be both micro or key block 45 | latest_block = chain_fixture.NODE_CLI.get_top_block() 46 | has_mb = latest_block.hash.startswith("mh_") or has_mb # check if is a microblock 47 | has_kb = latest_block.hash.startswith("kh_") or has_kb # check if is a keyblock 48 | print(has_kb, has_mb, latest_block.hash) 49 | # create a transaction so the top block is going to be an micro block 50 | if not has_mb: 51 | account = Account.generate().get_address() 52 | chain_fixture.NODE_CLI.spend(chain_fixture.ALICE, account, '0.1AE') 53 | # wait for the next block 54 | block = chain_fixture.NODE_CLI.get_block_by_hash(hash=latest_block.hash) 55 | # assert block.hash == latest_block.hash 56 | assert block.height == latest_block.height 57 | 58 | 59 | def test_api_get_genesis_block(chain_fixture): 60 | node_status = chain_fixture.NODE_CLI.get_status() 61 | genesis_block = chain_fixture.NODE_CLI.get_key_block_by_hash(hash=node_status.genesis_key_block_hash) 62 | zero_height_block = chain_fixture.NODE_CLI.get_key_block_by_height(height=0) # these should be equivalent 63 | # assert type(genesis_block) == BlockWithTx 64 | # assert type(zero_height_block) == BlockWithTx 65 | assert genesis_block.height == genesis_block.height == 0 66 | # TODO: The following check should not fail. I feel that's a problem with 67 | # TODO: the current state of the api --devsnd 68 | assert genesis_block.hash == zero_height_block.hash 69 | 70 | 71 | def test_api_get_generation_transaction_count_by_hash(chain_fixture): 72 | # get the latest block 73 | block_hash = chain_fixture.NODE_CLI.get_current_key_block_hash() 74 | print(block_hash) 75 | assert block_hash is not None 76 | # get the transaction count that should be a number >= 0 77 | generation = chain_fixture.NODE_CLI.get_generation_by_hash(hash=block_hash) 78 | print(generation) 79 | assert len(generation.micro_blocks) >= 0 80 | 81 | 82 | def test_api_get_transaction_by_hash_not_found(chain_fixture): 83 | tx_hash = 'th_LUKGEWyZSwyND7vcQwZwLgUXi23WJLQb9jKgJTr1it9QFViMC' 84 | try: 85 | chain_fixture.NODE_CLI.get_transaction_by_hash(hash=tx_hash) 86 | assert False 87 | except openapi.OpenAPIClientException as e: 88 | assert e.code == 404 89 | 90 | 91 | def test_api_get_transaction_by_hash_bad_request(chain_fixture): 92 | tx_hash = 'th_LUKG' 93 | try: 94 | chain_fixture.NODE_CLI.get_transaction_by_hash(hash=tx_hash) 95 | assert False 96 | except openapi.OpenAPIClientException as e: 97 | assert e.code == 400 98 | -------------------------------------------------------------------------------- /tests/test_channels.py: -------------------------------------------------------------------------------- 1 | def test_channel_connection(): 2 | """ 3 | temporarily pass the test for CI 4 | """ 5 | pass 6 | 7 | """ acc = signing.Account.from_keystore('/Users/shubhendu/dev/aepp-cli-js/two', '') 8 | tx_signer = transactions.TxSigner( 9 | acc, 10 | 'ae_devnet' 11 | ) 12 | opts = { 13 | 'url': 'ws://localhost:3014', 14 | 'role': 'initiator', 15 | 'push_amount': 3, 16 | 'initiator_amount': 100000000000000000, 17 | 'responder_amount': 100000000000000000, 18 | 'channel_reserve': 2, 19 | 'ttl': 10000, 20 | 'host': 'localhost', 21 | 'port': 3001, 22 | 'lock_period': 1000000, 23 | 'protocol': 'json-rpc', 24 | 'initiator_id': 'ak_UHssTe2mXhj6LrbH6MfGQNbvn6X8YAgwjwdgefEaEB1d2HdLX', 25 | 'responder_id': 'ak_2jgyqAtNS68QWgCej56sZpGpRrEwvUyPpL5ZbG4TeZBxByBMS8', 26 | 'sign': tx_signer.sign_encode_transaction 27 | } 28 | loop = asyncio.get_event_loop() 29 | sch = channel.Channel(opts) 30 | sch.create() 31 | loop.run_forever() 32 | """ 33 | -------------------------------------------------------------------------------- /tests/test_compiler.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from aeternity.contract_native import ContractNative 3 | 4 | def test_sophia_contract_compile(compiler_fixture, testdata_fixture): 5 | tests = [ 6 | { 7 | "name": "identity.aes", 8 | "sourcecode": "contract Identity =\n entrypoint main(x : int) = x", 9 | }, 10 | { 11 | "name": "simplestorage.aes", 12 | "sourcecode": "contract SimpleStorage =\n record state = { data : int }\n entrypoint init(value : int) : state = { data = value }\n function get() : int = state.data\n stateful function set(value : int) = put(state{data = value})\n" 13 | }, 14 | ] 15 | 16 | compiler = compiler_fixture.COMPILER 17 | for t in tests: 18 | result = compiler.compile(t.get('sourcecode')) 19 | assert hasattr(result, 'bytecode') and result.bytecode.startswith('cb_') 20 | 21 | 22 | def test_sophia_encode_calldata(compiler_fixture): 23 | tests = [ 24 | { 25 | "name": "simplestorage.aes", 26 | "sourcecode": "contract SimpleStorage =\n record state = { data : int }\n entrypoint init(value : int) : state = { data = value }\n function get() : int = state.data\n stateful function set(value : int) = put(state{data = value})\n", 27 | "function": "set", 28 | "arguments": [42], 29 | }, 30 | { 31 | "name": "simplestorage.aes", 32 | "sourcecode": "contract SimpleStorage =\n record state = { data : int }\n entrypoint init(value : int) : state = { data = value }\n function get() : int = state.data\n stateful function set(value : int) = put(state{data = value})\n", 33 | "function": "init", 34 | "arguments": [42], 35 | }, 36 | { 37 | "sourcecode": "contract Identity =\n entrypoint main(z : int) = z", 38 | "function": "init", 39 | "arguments": [], 40 | }, 41 | ] 42 | compiler = compiler_fixture.COMPILER 43 | for t in tests: 44 | result = compiler.encode_calldata(t.get("sourcecode"), t.get('function'), *t.get('arguments')) 45 | assert hasattr(result, 'calldata') and result.calldata.startswith("cb_") 46 | 47 | 48 | def test_sophia_decode_calldata_bytecode(compiler_fixture): 49 | tests = [ 50 | { 51 | "name": "simplestorage.aes", 52 | "sourcecode": "contract SimpleStorage =\n record state = { data : int }\n entrypoint init(value : int) : state = { data = value }\n entrypoint get() : int = state.data\n stateful entrypoint set(value : int) = put(state{data = value})\n", 53 | "bytecode": "cb_+JFGA6DcSHcAbyhLqfbIDJRe1S7ZJLCZQJBUuvMmCLK5OirpHsC4YLg8/i+GW9kANwAHKCwAggD+RNZEHwA3AQc3AAwBACcMAhoCggEDP/7oxF62ADcBBzcADAEAJwwCGgKCAQM/ni8DES+GW9kNZ2V0EUTWRB8RaW5pdBHoxF62DXNldIIvAIk0LjAuMC1yYzUAFlcOUg==", 54 | "match": True, 55 | "calldata": "cb_KxHoxF62G1Sy3bqn", 56 | "function": "set", 57 | "arguments": [42], 58 | }, 59 | { 60 | "name": "identity.aes", 61 | "sourcecode": "contract Identity =\n entrypoint main(x : int) = x", 62 | "bytecode": "cb_+GpGA6Abk28ISckRxfWDHCo6FYfNzlAYCRW6TBDvQZ2BYQUmH8C4OZ7+RNZEHwA3ADcAGg6CPwEDP/64F37sADcBBwcBAQCWLwIRRNZEHxFpbml0EbgXfuwRbWFpboIvAIk0LjAuMC1yYzUAfpEWYw==", 63 | "match": True, 64 | "calldata": "cb_KxG4F37sG1Q/+F7e", 65 | "function": "main", 66 | "arguments": [42], 67 | } 68 | ] 69 | compiler = compiler_fixture.COMPILER 70 | for t in tests: 71 | result = compiler.decode_calldata_with_bytecode(t.get("bytecode"), t.get('calldata')) 72 | print("RESULT", result) 73 | assert (t.get("match") and ( 74 | result.function == t.get("function") and 75 | result.arguments[0].value == t.get("arguments")[0] 76 | )) 77 | 78 | def test_sophia_validate_bytecode(compiler_fixture, chain_fixture): 79 | compiler = compiler_fixture.COMPILER 80 | account = chain_fixture.ALICE 81 | node_client = chain_fixture.NODE_CLI 82 | 83 | sourcecode = """contract SimpleStorage = 84 | record state = { data : int } 85 | entrypoint init(value : int) : state = { data = value } 86 | entrypoint get() : int = state.data 87 | stateful entrypoint set(value : int) = put(state{data = value})""" 88 | 89 | contract_native = ContractNative(client=node_client, source=sourcecode, compiler=compiler, account=account) 90 | contract_native.deploy(12) 91 | 92 | chain_bytecode = node_client.get_contract_code(pubkey=contract_native.address).bytecode 93 | result = compiler.validate_bytecode(sourcecode, chain_bytecode) 94 | 95 | assert result == {} 96 | 97 | sourcecode_identity = """contract Identity = 98 | entrypoint main(x : int) = x 99 | entrypoint mainString(x : string) = x""" 100 | 101 | try: 102 | result = compiler.validate_bytecode(sourcecode_identity, chain_bytecode) 103 | raise RuntimeError("Method call should fail") 104 | except Exception as e: 105 | assert str(e) == 'Invalid contract' 106 | 107 | def test_sophia_decode_calldata_sourcecode(compiler_fixture): 108 | tests = [ 109 | { 110 | "name": "simplestorage.aes", 111 | "sourcecode": "contract SimpleStorage =\n record state = { data : int }\n entrypoint init(value : int) : state = { data = value }\n entrypoint get() : int = state.data\n stateful entrypoint set(value : int) = put(state{data = value})\n", 112 | "bytecode": "cb_+JFGA6DcSHcAbyhLqfbIDJRe1S7ZJLCZQJBUuvMmCLK5OirpHsC4YLg8/i+GW9kANwAHKCwAggD+RNZEHwA3AQc3AAwBACcMAhoCggEDP/7oxF62ADcBBzcADAEAJwwCGgKCAQM/ni8DES+GW9kNZ2V0EUTWRB8RaW5pdBHoxF62DXNldIIvAIk0LjAuMC1yYzUAFlcOUg==", 113 | "match": True, 114 | "calldata": "cb_KxHoxF62G1Sy3bqn", 115 | "function": "set", 116 | "arguments": [42], 117 | "response": 42 118 | }, 119 | { 120 | "name": "identity.aes", 121 | "sourcecode": "contract Identity =\n entrypoint main(x : int) = x", 122 | "bytecode": "cb_+GpGA6Abk28ISckRxfWDHCo6FYfNzlAYCRW6TBDvQZ2BYQUmH8C4OZ7+RNZEHwA3ADcAGg6CPwEDP/64F37sADcBBwcBAQCWLwIRRNZEHxFpbml0EbgXfuwRbWFpboIvAIk0LjAuMC1yYzUAfpEWYw==", 123 | "match": True, 124 | "calldata": "cb_KxG4F37sG1Q/+F7e", 125 | "function": "main", 126 | "arguments": [42], 127 | "response": 42 128 | } 129 | ] 130 | compiler = compiler_fixture.COMPILER 131 | for t in tests: 132 | result = compiler.decode_calldata_with_sourcecode(t.get("sourcecode"), t.get('function'), t.get('calldata')) 133 | assert (t.get("match") and ( 134 | result.function == t.get("function") and 135 | result.arguments[0].value == t.get("response") 136 | )) 137 | -------------------------------------------------------------------------------- /tests/test_contract_lima.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | def _sophia_contract_tx_create_online(node_cli, account): 4 | tests = [ 5 | { 6 | # init(42) 7 | "name": "simplestorage.aes", 8 | "sourcecode": "contract SimpleStorage =\n record state = { data : int }\n entrypoint init(value : int) : state = { data = value }\n entrypoint get() : int = state.data\n stateful entrypoint set(value : int) = put(state{data = value})\n", 9 | "bytecode": "cb_+JFGA6CaF4v9syrdOevYZSWs8H6yoVk//r4Azu+V96W3B5CXFMC4YLg8/i+GW9kANwAHKCwAggD+RNZEHwA3AQc3AAwBACcMAhoCggEDP/7oxF62ADcBBzcADAEAJwwCGgKCAQM/ni8DES+GW9kNZ2V0EUTWRB8RaW5pdBHoxF62DXNldIIvAIk0LjAuMC1yYzUAYj/1oA==", 10 | "calldata": "cb_KxFE1kQfG1TH2kjs", 11 | }, 12 | { 13 | "name": "identity.aes", 14 | "sourcecode": "contract Identity =\n entrypoint main(x : int) = x", 15 | "bytecode": "cb_+GpGA6Abk28ISckRxfWDHCo6FYfNzlAYCRW6TBDvQZ2BYQUmH8C4OZ7+RNZEHwA3ADcAGg6CPwEDP/64F37sADcBBwcBAQCWLwIRRNZEHxFpbml0EbgXfuwRbWFpboIvAIk0LjAuMC1yYzUAfpEWYw==", 16 | "calldata": "cb_KxFE1kQfP4oEp9E=", # init() 17 | } 18 | 19 | ] 20 | for t in tests: 21 | contract = node_cli.Contract() 22 | tx = contract.create(account, t.get("bytecode"), calldata=t.get("calldata"), gas=100000) 23 | c_id = tx.metadata.contract_id 24 | deployed = node_cli.get_contract(pubkey=c_id) 25 | assert deployed.active is True 26 | assert deployed.owner_id == account.get_address() 27 | 28 | 29 | def _sophia_contract_tx_call_online(node_cli, account): 30 | tests = [ 31 | { 32 | "name": "simplestorage.aes", 33 | "sourcecode": "contract SimpleStorage =\n record state = { data : int }\n entrypoint init(value : int) : state = { data = value }\n entrypoint get() : int = state.data\n stateful entrypoint set(value : int) = put(state{data = value})\n", 34 | "bytecode": "cb_+JFGA6CaF4v9syrdOevYZSWs8H6yoVk//r4Azu+V96W3B5CXFMC4YLg8/i+GW9kANwAHKCwAggD+RNZEHwA3AQc3AAwBACcMAhoCggEDP/7oxF62ADcBBzcADAEAJwwCGgKCAQM/ni8DES+GW9kNZ2V0EUTWRB8RaW5pdBHoxF62DXNldIIvAIk0LjAuMC1yYzUAYj/1oA==", 35 | # init(42) 36 | "init.calldata": "cb_KxFE1kQfG1TH2kjs", 37 | "call.function": "set", 38 | "call.arguments": [24], 39 | "call.calldata": "cb_KxHoxF62GzAP+Odz", 40 | "return.value": "cb_P4fvHVw=" # 0, 41 | }, 42 | { 43 | "name": "identity.aes", 44 | "sourcecode": "contract Identity =\n entrypoint main(x : int) = x", 45 | "bytecode": "cb_+GpGA6Abk28ISckRxfWDHCo6FYfNzlAYCRW6TBDvQZ2BYQUmH8C4OZ7+RNZEHwA3ADcAGg6CPwEDP/64F37sADcBBwcBAQCWLwIRRNZEHxFpbml0EbgXfuwRbWFpboIvAIk0LjAuMC1yYzUAfpEWYw==", 46 | "init.calldata": "cb_KxFE1kQfP4oEp9E=", 47 | "call.function": "main", 48 | "call.arguments": [42], 49 | "call.calldata": "cb_KxG4F37sG1Q/+F7e", 50 | "return.value": "cb_VNLOFXc=" # 42 51 | 52 | } 53 | 54 | ] 55 | for t in tests: 56 | print(f"call contract {t.get('name')}") 57 | contract = node_cli.Contract() 58 | tx = contract.create(account, t.get("bytecode"), calldata=t.get("init.calldata")) 59 | c_id = tx.metadata.contract_id 60 | deployed = node_cli.get_contract(pubkey=c_id) 61 | assert deployed.active is True 62 | assert deployed.owner_id == account.get_address() 63 | tx = contract.call(c_id, account, t.get("call.function"), t.get("call.calldata")) 64 | # retrieve the call object 65 | call = contract.get_call_object(tx.hash) 66 | assert call.return_value == t.get("return.value") 67 | assert call.return_type == "ok" 68 | 69 | def _sophia_contract_tx_call_static(node_cli, account): 70 | tests = [ 71 | { 72 | "name": "identity.aes", 73 | "sourcecode": "contract Identity =\n entrypoint main(x : int) = x", 74 | "bytecode": "cb_+GpGA6Abk28ISckRxfWDHCo6FYfNzlAYCRW6TBDvQZ2BYQUmH8C4OZ7+RNZEHwA3ADcAGg6CPwEDP/64F37sADcBBwcBAQCWLwIRRNZEHxFpbml0EbgXfuwRbWFpboIvAIk0LjAuMC1yYzUAfpEWYw==", 75 | "init.calldata": "cb_KxFE1kQfP4oEp9E=", 76 | "call.function": "main", 77 | "call.arguments": [42], 78 | "call.calldata": "cb_KxG4F37sG1Q/+F7e", 79 | "return.value": "cb_VNLOFXc=" # 42 80 | 81 | } 82 | 83 | ] 84 | for t in tests: 85 | contract = node_cli.Contract() 86 | tx = contract.create(account, t.get("bytecode"), calldata=t.get("init.calldata")) 87 | c_id = tx.metadata.contract_id 88 | deployed = node_cli.get_contract(pubkey=c_id) 89 | assert deployed.active is True 90 | assert deployed.owner_id == account.get_address() 91 | tx = contract.call_static(c_id, t.get("call.function"), t.get("call.calldata"), address=account.get_address()) 92 | assert tx.result == "ok" 93 | 94 | def test_sophia_contract_tx_create_native_lima(chain_fixture): 95 | # save settings and go online 96 | _sophia_contract_tx_create_online(chain_fixture.NODE_CLI, chain_fixture.ALICE) 97 | 98 | 99 | def test_sophia_contract_tx_call_native_lima(chain_fixture): 100 | # save settings and go online 101 | _sophia_contract_tx_call_online(chain_fixture.NODE_CLI, chain_fixture.ALICE) 102 | # restore settings 103 | 104 | def test_sophia_contract_tx_call_static_native_lima(chain_fixture): 105 | # save settings and go online 106 | _sophia_contract_tx_call_static(chain_fixture.NODE_CLI, chain_fixture.BOB) 107 | # restore settings 108 | 109 | 110 | -------------------------------------------------------------------------------- /tests/test_hashing.py: -------------------------------------------------------------------------------- 1 | from aeternity import hashing, transactions 2 | from pytest import raises 3 | 4 | 5 | def test_hashing_name_id(): 6 | assert hashing.name_id('aeternity.chain') == 'nm_S4ofw6861biSJrXgHuJPo7VotLbrY8P9ngTLvgrRwbDEA3svc' 7 | assert hashing.name_id('apeunit.chain') == 'nm_vXDbXQHeSqLUwXdYMioZdg4i1AizRR6kH5bzj16zzUN7gdFri' 8 | assert hashing.name_id('abc.chain') != 'nm_S4ofw6861biSJrXgHuJPo7VotLbrY8P9ngTLvgrRwbDEA3svc' 9 | 10 | 11 | def test_hashing_rlp(): 12 | args = [ 13 | { 14 | "data": ("tx", [b"a", b"b", 1, 0, False, True]), 15 | "rlp": "tx_xmFiAYCAAXNBplc=", 16 | }, 17 | { 18 | "data": ("th", [b"a", b"b", 1, 0, False, True]), 19 | "rlp": "th_rCDULgdVmqKQR6W", 20 | }, 21 | ] 22 | 23 | for a in args: 24 | prefix, data = a.get("data") 25 | assert hashing.encode_rlp(prefix, data) == a.get("rlp") 26 | 27 | with raises(TypeError): 28 | hashing.encode_rlp("tx", "a string") # valid prefix wrong data 29 | with raises(ValueError): 30 | hashing.encode_rlp(None, [1, b'a']) 31 | with raises(ValueError): 32 | hashing.encode_rlp("eg", [1, b'a']) 33 | 34 | def test_hashing_base64(): 35 | args = [ 36 | { 37 | "data": "sample_data".encode('utf8'), 38 | "b64": "c2FtcGxlX2RhdGG8Ktkc" 39 | } 40 | ] 41 | 42 | for a in args: 43 | assert a.get("b64") == hashing._base64_encode(a.get("data")) 44 | assert a.get("data") == hashing._base64_decode(a.get("b64")) 45 | 46 | args = [ 47 | None, 48 | "", 49 | "abcdzz", 50 | "c2FtcGxlX2RhdGG8Ktka" 51 | ] 52 | 53 | for a in args: 54 | with raises(ValueError): 55 | hashing._base64_decode(a) 56 | 57 | 58 | def test_hashing_base58_encode(): 59 | 60 | inputs = [ 61 | { 62 | "data": "test".encode("utf-8"), 63 | "prefix": "th", 64 | "hash": "LUC1eAJa5jW", 65 | "match": True, 66 | "raise_error": False, 67 | }, 68 | { 69 | "data": "test".encode("utf-8"), 70 | "prefix": "th", 71 | "hash": "LUC1eAJa", 72 | "match": False, 73 | "raise_error": True, 74 | 75 | }, 76 | { 77 | "data": "aeternity".encode("utf-8"), 78 | "prefix": "th", 79 | "hash": "97Wv2fcowb3y3qVnDC", 80 | "match": True, 81 | "raise_error": False, 82 | }, 83 | ] 84 | 85 | for i in inputs: 86 | try: 87 | h = hashing._base58_encode(i.get("data")) 88 | assert (h == i.get("hash")) is i.get("match") 89 | e = hashing.encode(i.get("prefix"), i.get("data")) 90 | assert (e == f'{i.get("prefix")}_{i.get("hash")}') is i.get("match") 91 | o = hashing._base58_decode(i.get("hash")) 92 | assert (o == i.get("data")) is i.get("match") 93 | except Exception as e: 94 | assert i.get("raise_error") is True 95 | 96 | 97 | def test_hashing_transactions_binary(): 98 | tts = [ 99 | {"in": "test", "bval": "test".encode("utf-8"), "match": True, "err": False}, 100 | {"in": "test", "bval": "t".encode("utf-8"), "match": False, "err": False}, 101 | {"in": 8, "bval": b'\x08', "match": True, "err": False}, 102 | {"in": 1000, "bval": b'\x00\x03\xe8', "match": False, "err": False}, # with padding 103 | {"in": 1000, "bval": b'\x03\xe8', "match": True, "err": False}, # without padding 104 | {"in": 1000, "bval": b'\x03\xe8', "match": True, "err": False}, # without padding 105 | {"in": 13141231, "bval": b'\xc8\x84\xef', "match": True, "err": False}, # without padding 106 | {"in": ["312321"], "bval": b'', "match": False, "err": True}, 107 | {"in": b'\x00\x16\xba\x14\xfb', "bval": b'\x00\x16\xba\x14\xfb', "match": True, "err": False}, 108 | {"in": b'\x16\xba\x14\xfb', "bval": b'\x00\x16\xba\x14\xfb', "match": False, "err": False}, 109 | ] 110 | 111 | for tt in tts: 112 | print(tt) 113 | if tt['err']: 114 | with raises(TypeError): 115 | transactions._binary(tt['in']) 116 | elif tt['match']: 117 | assert transactions._binary(tt['in']) == tt['bval'] 118 | elif not tt['match']: 119 | assert transactions._binary(tt['in']) != tt['bval'] 120 | else: 121 | assert False 122 | 123 | 124 | def test_hashing_contract_id(): 125 | # TODO: add more scenarios 126 | # owner_id, nonce -> contract_id, match 127 | tt = [ 128 | ('ak_P1hn3JnJXcdx8USijBcgZHLgvZywH5PbjQK5G1iZaEu9obHiH', 2, 'ct_5ye5dEQwtCrRhsKYq8BprAMFptpY59THUyTxSBQKpDTcywEhk', True), 129 | ('ak_P1hn3JnJXcdx8USijBcgZHLgvZywH5PbjQK5G1iZaEu9obHiH', 1, 'ct_5ye5dEQwtCrRhsKYq8BprAMFptpY59THUyTxSBQKpDTcywEhk', False), 130 | ] 131 | 132 | for t in tt: 133 | assert (hashing.contract_id(t[0], t[1]) == t[2]) == t[3] 134 | 135 | 136 | def test_hashing_oracle_query_id(): 137 | # TODO: add more scenarios 138 | # sender_id, nonce, oracle_id -> query_id, match 139 | tt = [ 140 | ('ak_2ZjpYpJbzq8xbzjgPuEpdq9ahZE7iJRcAYC1weq3xdrNbzRiP4', 1, 'ok_2iqfJjbhGgJFRezjX6Q6DrvokkTM5niGEHBEJZ7uAG5fSGJAw1', 141 | 'oq_2YvZnoohcSvbQCsPKSMxc98i5HZ1sU5mR6xwJUZC3SvkuSynMj', True), 142 | ] 143 | 144 | for t in tt: 145 | assert (hashing.oracle_query_id(t[0], t[1], t[2]) == t[3]) == t[4] 146 | 147 | 148 | def test_hashing_oracle_id(): 149 | # account_id, -> oracle_id, match 150 | tt = [ 151 | ('ak_2iqfJjbhGgJFRezjX6Q6DrvokkTM5niGEHBEJZ7uAG5fSGJAw1', 'ok_2iqfJjbhGgJFRezjX6Q6DrvokkTM5niGEHBEJZ7uAG5fSGJAw1', True), 152 | ] 153 | 154 | for t in tt: 155 | assert (hashing.oracle_id(t[0]) == t[1]) == t[2] 156 | 157 | 158 | def test_hashing_committment_id(): 159 | 160 | tests = [ 161 | { 162 | "domain": "aeternity.chain", 163 | "salt": 10692426485854419779, 164 | "commitment_id": "cm_j5Aa3senWdNskwSSHh3M182ucbqrAaSE5DVjejM8fBCgR97kq" 165 | }, 166 | { 167 | "domain": "whatever.chain", 168 | "salt": 4703192432112, 169 | "commitment_id": "cm_2Hd42FoCDfYxcG3MyZkiN9wXiBKfHHzBWycEvrazPYgoEh1ien" 170 | }, 171 | { 172 | "domain": "aepps.chain", 173 | "salt": 723907012945811264198, 174 | "commitment_id": "cm_7aMmYzWVGK2t6gqWYD9WFbwHWaLzcux6t63i2J7VHPZfcuzjs" 175 | }, 176 | ] 177 | 178 | for t in tests: 179 | cid, salt = hashing.commitment_id(t.get("domain"), t.get("salt")) 180 | assert t.get("commitment_id") == cid 181 | -------------------------------------------------------------------------------- /tests/test_node.py: -------------------------------------------------------------------------------- 1 | from aeternity.signing import Account 2 | from aeternity import defaults, identifiers, hashing, utils 3 | import pytest 4 | import random 5 | # from aeternity.exceptions import TransactionNotFoundException 6 | 7 | 8 | blind_auth_contract = """contract BlindAuth = 9 | record state = { owner : address } 10 | 11 | entrypoint init(owner' : address) = { owner = owner' } 12 | 13 | stateful entrypoint authorize(r: int) : bool = 14 | // r is a random number only used to make tx hashes unique 15 | switch(Auth.tx_hash) 16 | None => abort("Not in Auth context") 17 | Some(tx_hash) => true 18 | 19 | entrypoint to_sign(h : hash, n : int) : hash = 20 | Crypto.blake2b((h, n)) 21 | """ 22 | 23 | 24 | 25 | def test_node_spend_native(chain_fixture): 26 | node_cli = chain_fixture.NODE_CLI 27 | sender_account = chain_fixture.ALICE 28 | recipient_id = Account.generate().get_address() 29 | # with numbers 30 | tx = node_cli.spend(sender_account, recipient_id, 100) 31 | print("DATA", tx) 32 | assert recipient_id == tx.data.tx.data.recipient_id 33 | assert sender_account.get_address() == tx.data.tx.data.sender_id 34 | account_balance = node_cli.get_account_by_pubkey(pubkey=recipient_id).balance 35 | assert account_balance == 100 36 | # spend some string 37 | tx = node_cli.spend(sender_account, recipient_id, "0.5ae") 38 | 39 | assert recipient_id == tx.data.tx.data.recipient_id 40 | assert sender_account.get_address() == tx.data.tx.data.sender_id 41 | 42 | account_balance = node_cli.get_account_by_pubkey(pubkey=recipient_id).balance 43 | assert account_balance == 500000000000000100 44 | 45 | def test_node_spend_burst(chain_fixture): 46 | sender_account = chain_fixture.ALICE 47 | # make a new non blocking client 48 | ae_cli = chain_fixture.NODE_CLI 49 | ae_cli.config.blocking_mode = False 50 | # recipient account 51 | recipient_account = Account.generate().get_address() 52 | # send 50 consecutive spend 53 | ths = [] 54 | print(">>"*20, "spend burst start") 55 | for i in range(50): 56 | tx = ae_cli.spend(sender_account, recipient_account, "1AE") 57 | ths.append(tx.hash) 58 | print("<<"*20, "spend burst end") 59 | 60 | for th in ths: 61 | ae_cli.wait_for_transaction(th) 62 | 63 | assert(ae_cli.get_balance(recipient_account) == utils.amount_to_aettos("50ae")) 64 | 65 | 66 | @pytest.mark.parametrize("height,protocol_version", [(0, 1), (1, 1), (2, 2), (3, 2), (4, 3), (5, 3)]) 67 | def test_node_get_protocol_version(chain_fixture, height, protocol_version): 68 | # this test assumes that the configuration of the node bein tested has the follwing configuration: 69 | # hard_forks: 70 | # "1": 0 71 | # "2": 2 72 | # "3": 4 73 | assert(chain_fixture.NODE_CLI.get_consensus_protocol_version(height)) == protocol_version 74 | 75 | def test_node_ga_attach(chain_fixture, compiler_fixture): 76 | ae_cli = chain_fixture.NODE_CLI 77 | account = chain_fixture.ALICE 78 | c_cli = compiler_fixture.COMPILER 79 | # test that the account is not already generalized 80 | poa_account = ae_cli.get_account_by_pubkey(pubkey=account.get_address()) 81 | assert poa_account.kind == identifiers.ACCOUNT_KIND_BASIC 82 | # transform the account 83 | # compile the contract 84 | ga_contract = c_cli.compile(blind_auth_contract).bytecode 85 | # now encode the call data 86 | init_calldata = c_cli.encode_calldata(blind_auth_contract, "init", account.get_address()).calldata 87 | # this will return an object 88 | # init_calldata.calldata 89 | # now we can execute the transaction 90 | tx = ae_cli.account_basic_to_ga(account, ga_contract, calldata=init_calldata) 91 | 92 | # now check if it is a ga 93 | ga_account = ae_cli.get_account_by_pubkey(pubkey=account.get_address()) 94 | assert ga_account.kind == identifiers.ACCOUNT_KIND_GENERALIZED 95 | 96 | def test_node_ga_meta_spend(chain_fixture, compiler_fixture): 97 | 98 | ae_cli = chain_fixture.NODE_CLI 99 | account = chain_fixture.ALICE 100 | c_cli = compiler_fixture.COMPILER 101 | # make the account poa 102 | 103 | # transform the account 104 | # compile the contract 105 | ga_contract = c_cli.compile(blind_auth_contract).bytecode 106 | # now encode the call data 107 | init_calldata = c_cli.encode_calldata(blind_auth_contract, "init", account.get_address()).calldata 108 | # this will return an object 109 | # init_calldata.calldata 110 | # now we can execute the transaction 111 | tx = ae_cli.account_basic_to_ga(account, ga_contract, calldata=init_calldata) 112 | 113 | print("ACCOUNT is now GA", account.get_address()) 114 | 115 | # retrieve the account data 116 | ga_account = ae_cli.get_account(account.get_address()) 117 | # create a dummy account 118 | recipient_id = Account.generate().get_address() 119 | 120 | # generate the spend transactions 121 | amount = 54321 122 | payload = "ga spend tx" 123 | fee = defaults.FEE 124 | ttl = defaults.TX_TTL 125 | 126 | spend_tx = ae_cli.tx_builder.tx_spend(account.get_address(), recipient_id, amount, payload, fee, ttl, 0) 127 | 128 | 129 | # encode the call data for the transaction 130 | calldata = c_cli.encode_calldata(blind_auth_contract, ga_account.auth_fun, hashing.randint()).calldata 131 | # now we can sign the transaction (it will use the auth for do that) 132 | spend_tx = ae_cli.sign_transaction(ga_account, spend_tx, auth_data=calldata) 133 | # broadcast 134 | tx_hash = ae_cli.broadcast_transaction(spend_tx) 135 | print(f"GA_META_TX {tx_hash}") 136 | # check that the account received the tokens 137 | assert ae_cli.get_account_by_pubkey(pubkey=recipient_id).balance == amount 138 | -------------------------------------------------------------------------------- /tests/test_node_delegate_sig.py: -------------------------------------------------------------------------------- 1 | from aeternity.contract_native import ContractNative 2 | from aeternity import hashing, utils 3 | from tests.conftest import random_domain 4 | import pytest 5 | 6 | 7 | contractAens = """contract DelegateTest = 8 | // Transactions 9 | stateful payable entrypoint signedPreclaim(addr : address, 10 | chash : hash, 11 | sign : signature) : unit = 12 | AENS.preclaim(addr, chash, signature = sign) 13 | 14 | stateful entrypoint signedClaim(addr : address, 15 | name : string, 16 | salt : int, 17 | name_fee : int, 18 | sign : signature) : unit = 19 | AENS.claim(addr, name, salt, name_fee, signature = sign) 20 | 21 | 22 | stateful entrypoint signedTransfer(owner : address, 23 | new_owner : address, 24 | name : string, 25 | sign : signature) : unit = 26 | AENS.transfer(owner, new_owner, name, signature = sign) 27 | 28 | stateful entrypoint signedRevoke(owner : address, 29 | name : string, 30 | sign : signature) : unit = 31 | AENS.revoke(owner, name, signature = sign) 32 | """ 33 | 34 | 35 | @pytest.mark.skip("blocked by https://github.com/aeternity/aepp-sdk-python/issues/306") 36 | def test_node_contract_signature_delegation(compiler_fixture, chain_fixture): 37 | compiler = compiler_fixture.COMPILER 38 | account = chain_fixture.ALICE 39 | bob = chain_fixture.BOB 40 | contract_native = ContractNative(client=chain_fixture.NODE_CLI, source=contractAens, compiler=compiler, account=account) 41 | contract_native.deploy() 42 | 43 | assert(contract_native.address is not None) 44 | 45 | # the contract_id 46 | contract_id = contract_native.address 47 | # node client 48 | ae_cli = contract_native.client 49 | 50 | # example name 51 | name = random_domain(length=15) 52 | c_id, salt = hashing.commitment_id(name, 9876) 53 | name_ttl = 500000 54 | client_ttl = 36000 55 | name_fee = utils.amount_to_aettos("20AE") 56 | print(f"name is {name}, commitment_id: {c_id}") 57 | 58 | # aens calls 59 | signature = ae_cli.delegate_name_preclaim_signature(account, contract_id) 60 | call, r = contract_native.signedPreclaim(account.get_address(), c_id, signature) 61 | assert(call.return_type == 'ok') 62 | ae_cli.wait_for_confirmation(call.tx_hash) 63 | 64 | signature = ae_cli.delegate_name_claim_signature(account, contract_id, name) 65 | call, _ = contract_native.signedClaim(account.get_address(), name, salt, name_fee, signature) 66 | assert(call.return_type == 'ok') 67 | 68 | signature = ae_cli.delegate_name_transfer_signature(account, contract_id, name) 69 | call, _ = contract_native.signedTransfer(account.get_address(), bob.get_address(), name, signature) 70 | assert(call.return_type == 'ok') 71 | 72 | signature = ae_cli.delegate_name_revoke_signature(bob, contract_id, name) 73 | call, _ = contract_native.signedRevoke(bob.get_address(), name, signature) 74 | assert(call.return_type == 'ok') 75 | 76 | 77 | 78 | contractOracles = """contract DelegateTest = 79 | type fee = int 80 | type ttl = Chain.ttl 81 | 82 | type query_t = string 83 | type answer_t = int 84 | 85 | type oracle_id = oracle(query_t, answer_t) 86 | type query_id = oracle_query(query_t, answer_t) 87 | 88 | stateful payable entrypoint signedRegisterOracle(acct : address, 89 | sign : signature, 90 | qfee : fee, 91 | ttl : ttl) : oracle_id = 92 | Oracle.register(acct, qfee, ttl, signature = sign) 93 | 94 | stateful payable entrypoint signedExtendOracle(o : oracle_id, 95 | sign : signature, // Signed oracle address 96 | ttl : ttl) : unit = 97 | Oracle.extend(o, signature = sign, ttl) 98 | 99 | datatype complexQuestion = Why(int) | How(string) 100 | datatype complexAnswer = NoAnswer | Answer(complexQuestion, string, int) 101 | 102 | stateful entrypoint signedComplexOracle(question, sig) = 103 | let o = Oracle.register(signature = sig, Contract.address, 0, FixedTTL(1000)) : oracle(complexQuestion, complexAnswer) 104 | let q = Oracle.query(o, question, 0, RelativeTTL(100), RelativeTTL(100)) 105 | Oracle.respond(o, q, Answer(question, "magic", 1337), signature = sig) 106 | Oracle.get_answer(o, q) 107 | """ 108 | 109 | -------------------------------------------------------------------------------- /tests/test_openapi.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from aeternity.openapi import OpenAPICli 3 | from tests.conftest import NODE_URL, NODE_URL_DEBUG, PUBLIC_KEY 4 | 5 | client, priv_key, pub_key = None, None, None 6 | 7 | 8 | def test_generatedcli(): 9 | # open client 10 | client = OpenAPICli(NODE_URL, NODE_URL_DEBUG) 11 | calls = [ 12 | { 13 | "name": "get_top_block", 14 | "method": "get_top_block", 15 | "scenarios": [ 16 | { 17 | "name": "ok", 18 | "params": {}, 19 | "wantErr": False 20 | } 21 | ] 22 | }, 23 | { 24 | "name": "get_header_by_hash", 25 | "method": "get_header_by_hash", 26 | "scenarios": [ 27 | { 28 | "name": "err invalid hash", 29 | "params": { 30 | "hash": "xxxxx" 31 | }, 32 | "wantErr": True 33 | }, 34 | { 35 | "name": "err missing parameter required", 36 | "params": {}, 37 | "wantErr": True 38 | } 39 | ] 40 | }, 41 | { 42 | "name": "get_key_block_by_height", 43 | "method": "get_key_block_by_height", 44 | "scenarios": [ 45 | { 46 | "name": "ok ", 47 | "params": { 48 | "height": 1 49 | }, 50 | "wantErr": False 51 | }, 52 | { 53 | "name": "invalid type", 54 | "params": { 55 | "height": "1" 56 | }, 57 | "wantErr": True 58 | }, 59 | { 60 | "name": "err missing parameter required", 61 | "params": {}, 62 | "wantErr": True 63 | } 64 | ] 65 | }, 66 | { 67 | "method": "get_account_by_pubkey", 68 | "scenarios": [ 69 | { 70 | "name": "ok", 71 | "params": { 72 | "pubkey": PUBLIC_KEY 73 | }, 74 | "wantErr": False 75 | }, 76 | { 77 | "name": "ok", 78 | "params": { 79 | "pubkey": "xxxx" 80 | }, 81 | "wantErr": True 82 | }, 83 | { 84 | "name": "err missing parameter required", 85 | "params": { 86 | "height": 12, 87 | }, 88 | "wantErr": True 89 | } 90 | ] 91 | }, 92 | ] 93 | 94 | for a in client.get_api_methods(): 95 | print(a.name) 96 | 97 | for c in calls: 98 | for s in c.get("scenarios"): 99 | try: 100 | getattr(client, c.get("method"))(**s.get("params")) 101 | if s.get("wantErr"): 102 | pytest.fail(f"{c.get('method')}/{s.get('name')} wantErr = {s.get('wantErr')} ") 103 | except Exception as e: 104 | if not s.get("wantErr"): 105 | pytest.fail(f"{c.get('method')}/{s.get('name')} wantErr = false , got {e} ") 106 | -------------------------------------------------------------------------------- /tests/test_oracle.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import pytest 3 | 4 | from aeternity.oracles import Oracle, OracleQuery 5 | from aeternity import hashing 6 | 7 | logger = logging.getLogger(__name__) 8 | # to run this test in other environments set the env vars as specified in the 9 | # config.py 10 | 11 | logging.basicConfig(level=logging.DEBUG) 12 | 13 | 14 | class WeatherOracle(Oracle): 15 | def get_response(self, message): 16 | query = message['payload']['query'] 17 | logger.debug('Received query %s' % query) 18 | response = "{'temp_c': 30}" # nice and sunny 19 | logger.debug('Sending back %s' % str(response)) 20 | return response 21 | 22 | 23 | class WeatherQuery(OracleQuery): 24 | def __init__(self, *args, **kwargs): 25 | super().__init__(*args, **kwargs) 26 | self.response_received = False # for testing, record if we received anything 27 | 28 | def on_response(self, response, query): 29 | logger.debug('Weather Oracle Received a response! %s' % response) 30 | self.response_received = True 31 | 32 | 33 | def _test_oracle_registration(node_cli, account): 34 | oracle = node_cli.Oracle() 35 | weather_oracle = dict( 36 | account=account, 37 | query_format="{'city': str}", 38 | response_format="{'temp_c': int}", 39 | ) 40 | oracle.register(**weather_oracle) 41 | assert oracle.id == account.get_address().replace("ak_", "ok_") 42 | oracle_api_response = node_cli.get_oracle_by_pubkey(pubkey=oracle.id) 43 | assert oracle_api_response.id == oracle.id 44 | return oracle 45 | 46 | 47 | def _test_oracle_query(node_cli, oracle, sender, query): 48 | q = node_cli.OracleQuery(oracle.id) 49 | q.execute(sender, query) 50 | return q 51 | 52 | 53 | def _test_oracle_respond(oracle, query, account, response): 54 | oracle.respond(account, query.id, response) 55 | 56 | 57 | def _test_oracle_response(query, expected): 58 | r = query.get_response_object() 59 | assert r.oracle_id == query.oracle_id 60 | assert r.id == query.id 61 | assert r.response == hashing.encode("or", expected) 62 | 63 | 64 | def test_oracle_lifecycle_native(chain_fixture): 65 | # registration 66 | oracle = _test_oracle_registration(chain_fixture.NODE_CLI, chain_fixture.BOB) 67 | # query 68 | query = _test_oracle_query(chain_fixture.NODE_CLI, oracle, chain_fixture.ALICE, "{'city': 'Sofia'}") 69 | # respond 70 | _test_oracle_respond(oracle, query, chain_fixture.BOB, "{'temp_c': 2000}") 71 | _test_oracle_response(query, "{'temp_c': 2000}") 72 | -------------------------------------------------------------------------------- /tests/test_signing.py: -------------------------------------------------------------------------------- 1 | from pytest import raises 2 | from tests import TEST_TTL 3 | from aeternity.signing import Account, is_signature_valid 4 | from aeternity.utils import is_valid_hash 5 | from aeternity import hashing, identifiers 6 | import os 7 | 8 | 9 | def test_signing_create_transaction_signature(chain_fixture): 10 | # generate a new account 11 | new_account = Account.generate() 12 | receiver_address = new_account.get_address() 13 | # create a spend transaction 14 | nonce, ttl = chain_fixture.NODE_CLI._get_nonce_ttl(chain_fixture.ALICE.get_address(), TEST_TTL) 15 | tx = chain_fixture.NODE_CLI.tx_builder.tx_spend(chain_fixture.ALICE.get_address(), receiver_address, 321, "test test ", 0, ttl, nonce) 16 | tx_signed = chain_fixture.NODE_CLI.sign_transaction(chain_fixture.ALICE, tx) 17 | # this call will fail if the hashes of the transaction do not match 18 | chain_fixture.NODE_CLI.broadcast_transaction(tx_signed) 19 | # make sure this works for very short block times 20 | spend_tx = chain_fixture.NODE_CLI.get_transaction_by_hash(hash=tx_signed.hash) 21 | assert spend_tx.signatures[0] == tx_signed.get_signature(0) 22 | 23 | 24 | def test_signing_is_valid_hash(): 25 | # input (hash_str, prefix, expected output) 26 | args = [ 27 | ('ak_me6L5SSXL4NLWv5EkQ7a16xaA145Br7oV4sz9JphZgsTsYwGC', None, True), 28 | ('ak_me6L5SSXL4NLWv5EkQ7a16xaA145Br7oV4sz9JphZgsTsYwGC', 'ak', True), 29 | ('ak_me6L5SSXL4NLWv5EkQ7a16xaA145Br7oV4sz9JphZgsTsYwGC', 'bh', False), 30 | ('ak_me6L5SSXL4NLWv5EkQ7a16xaA145Br7oV4sz9JphZgsTsYwYC', None, False), 31 | ('ak_me6L5SSXL4NLWv5EkQ7a18xaA145Br7oV4sz9JphZgsTsYwGC', None, False), 32 | ('bh_vzUC2jVuAfpBC3tMAHhxwxJnTFymckNYeQ5TWZua1pydabqNu', None, False), # bh does not exists 33 | ('kh_vzUC2jVuAfpBC3tMAHhxwxJnTFymckNYeQ5TWZua1pydabqNu', None, True), 34 | ('th_YqPSTzs73PiKFhFcALYWWu41uNLc6yp63ZC35jzzuJYA9PMui', None, True), 35 | ] 36 | 37 | for a in args: 38 | got = is_valid_hash(a[0], a[1]) 39 | expected = a[2] 40 | assert got == expected 41 | 42 | 43 | def test_signing_keystore_load(): 44 | 45 | a = Account.from_keystore(os.path.join(os.path.dirname(os.path.realpath(__file__)), "testdata", "keystore.json"), "aeternity") 46 | assert a.get_address() == "ak_2hSFmdK98bhUw4ar7MUdTRzNQuMJfBFQYxdhN9kaiopDGqj3Cr" 47 | 48 | 49 | def test_signing_keystore_save_load(tempdir): 50 | original_account = Account.generate() 51 | filename = original_account.save_to_keystore(tempdir, "whatever") 52 | path = os.path.join(tempdir, filename) 53 | print(f"\nAccount keystore is {path}") 54 | # now load again the same 55 | a = Account.from_keystore(path, "whatever") 56 | assert a.get_address() == original_account.get_address() 57 | ###### 58 | original_account = Account.generate() 59 | filename = "account_ks" 60 | filename = original_account.save_to_keystore(tempdir, "whatever") 61 | path = os.path.join(tempdir, filename) 62 | print(f"\nAccount keystore is {path}") 63 | # now load again the same 64 | a = Account.from_keystore(path, "whatever") 65 | assert a.get_address() == original_account.get_address() 66 | 67 | 68 | def test_signing_keystore_save_load_wrong_pwd(tempdir): 69 | original_account = Account.generate() 70 | filename = original_account.save_to_keystore(tempdir, "whatever") 71 | path = os.path.join(tempdir, filename) 72 | print(f"\nAccount keystore is {path}") 73 | # now load again the same 74 | with raises(ValueError): 75 | a = Account.from_keystore(path, "nononon") 76 | assert a.get_address() == original_account.get_address() 77 | 78 | 79 | def test_signing_is_signature_valid(): 80 | sg_ae = "sg_6cXUU8rimh8B3byLHJA9SaG29uRggtyrpGi5YAFiL9cJUoVtMX4P4kpd4UPTjiGXYSaquSN3gidJ73U8CtfweQ14GFgsC" 81 | account_id = "ak_axjxzUJpj9siJDQKZrBFNTvQLR2JwcZoVPjgdCQdnGUtwf66r" 82 | 83 | sg_b64 = "KuZVJ8kK6xCmujLfd8AjU3IfENn1WwcQRA0hI/WWzyXp97zerFg9XRx/ICHcHRGmvxstsul/QEDma2uHf6DIAEcr8Vs=" 84 | account_b64 = "TRza0pA9oaZw7tltPULKlkRaGV2qXT9vJx9q2HGY4Lom4qR3" 85 | 86 | msg = "aeternity".encode("utf-8") 87 | 88 | assert is_signature_valid(account_id, sg_ae, msg) 89 | assert is_signature_valid( 90 | hashing._base64_decode(account_b64), 91 | hashing._base64_decode(sg_b64), 92 | msg) 93 | 94 | msg = "wrong".encode("utf-8") 95 | assert not is_signature_valid(account_id, sg_ae, msg) 96 | assert not is_signature_valid( 97 | hashing._base64_decode(account_b64), 98 | hashing._base64_decode(sg_b64), 99 | msg) 100 | 101 | 102 | def test_signing_get_address(): 103 | a = Account.from_private_key_string( 104 | '3960180c89e27fcc1559f631d664a4b56b569aa5768ba827ddc9ba9616fecd9d8134464ef14b1433790e259d40b6ad8ca39f397a2bbc5261eeba1018a67ce35a') 105 | assert(a.get_address() == 'ak_yuMB5S3yiTwRVJC1NcNEGppcGAbq26qFWNJTCJWnLqoihCpCG') 106 | assert(a.get_address(format=identifiers.ACCOUNT_API_FORMAT) == 'ak_yuMB5S3yiTwRVJC1NcNEGppcGAbq26qFWNJTCJWnLqoihCpCG') 107 | assert(a.get_address(format=identifiers.ACCOUNT_API_FORMAT) != 'ak_xuMB5S3yiTwRVJC1NcNEGppcGAbq26qFWNJTCJWnLqoihCpCG') 108 | assert(a.get_address(format=identifiers.ACCOUNT_SOFIA_FORMAT) == '0x8134464ef14b1433790e259d40b6ad8ca39f397a2bbc5261eeba1018a67ce35a') 109 | assert(a.get_address(format=identifiers.ACCOUNT_SOFIA_FORMAT) != '8134464ef14b1433790e259d40b6ad8ca39f397a2bbc5261eeba1018a67ce35a') 110 | assert(a.get_address(format=identifiers.ACCOUNT_RAW_FORMAT) == b'\x814FN\xf1K\x143y\x0e%\x9d@\xb6\xad\x8c\xa3\x9f9z+\xbcRa\xee\xba\x10\x18\xa6|\xe3Z') 111 | assert(a.get_address(format=identifiers.ACCOUNT_RAW_FORMAT) != b'\x814FN\xf1K\x143y\x0e%\x9d@\xb6\x00\x8c\xa3\x9f9z+\xbcRa\xee\xba\x10\x18\xa6|\xe3Z') 112 | -------------------------------------------------------------------------------- /tests/test_transactions.py: -------------------------------------------------------------------------------- 1 | 2 | from aeternity.signing import Account 3 | from aeternity import transactions, exceptions, identifiers as idf, hashing 4 | import pytest 5 | from pytest import raises 6 | import json 7 | from munch import Munch 8 | 9 | 10 | def _execute_test(test_cases, NODE_CLI): 11 | for tt in test_cases: 12 | # get a native transaction 13 | txbn = transactions.TxBuilder() 14 | txn = getattr(txbn, tt.get("tx"))(**tt["native"]) 15 | # get a debug transaction 16 | txbd = transactions.TxBuilder(api=NODE_CLI, native=False) 17 | txd = getattr(txbd, tt.get("tx"))(**tt["debug"]) 18 | # theys should be the same 19 | if tt["match"]: 20 | assert txn == txd 21 | else: 22 | assert txn != txd 23 | 24 | 25 | def test_transaction_fee_calculation(): 26 | sender_id = Account.generate().get_address() 27 | recipient_id = Account.generate().get_address() 28 | # account_id, recipient_id, amount, payload, fee, ttl, nonce 29 | tts = [ 30 | { 31 | "native": (sender_id, recipient_id, 1000, "", 16740000000000, 0, 1), 32 | "field_fee_idx": 4, 33 | "want_err": False 34 | }, { 35 | "native": (sender_id, recipient_id, 9845, "another payload", 26740000000000, 500, 1241), 36 | "field_fee_idx": 4, 37 | "want_err": False 38 | }, { 39 | "native": (sender_id, recipient_id, 9845, "another payload", 26740000000000, 0, 32131), 40 | "field_fee_idx": 4, 41 | "want_err": False 42 | }, { 43 | "native": (sender_id, recipient_id, 410000, "this is a very long payload that is not good to have ", 96740000000000, 0, 1241), 44 | "field_fee_idx": 4, 45 | "want_err": False 46 | }, { 47 | "native": (sender_id, recipient_id, 410000, "another payload", 26740000000000, 0, 1241), 48 | "field_fee_idx": 4, 49 | "want_err": False 50 | }, { 51 | "native": (sender_id, recipient_id, 5000000000000000000, "Faucet TX", 26740000000000, 0, 1241), 52 | "field_fee_idx": 4, 53 | "want_err": False # 16920000000000 54 | }, { 55 | "native": (sender_id, recipient_id, 50000000, "", -1, 0, 1241), 56 | "field_fee_idx": 4, 57 | "want_err": True 58 | }, { 59 | "native": (sender_id, recipient_id, 50000000, "", 321312, 0, 1241), 60 | "field_fee_idx": 4, 61 | "want_err": True 62 | }, 63 | ] 64 | 65 | for tt in tts: 66 | # get a native transaction 67 | txbn = transactions.TxBuilder() 68 | try: 69 | txbn.tx_spend(tt["native"][0], tt["native"][1], tt["native"][2], tt["native"][3], tt["native"][4], tt["native"][5], tt["native"][6]) 70 | except exceptions.TransactionFeeTooLow: 71 | assert(tt["want_err"]) 72 | except ValueError: 73 | assert(tt["want_err"]) 74 | # get a debug transaction 75 | 76 | def test_transaction_tx_signer(): 77 | sk = 'ed067bef18b3e2be42822b32e3fa468ceee1c8c2c8744ca15e96855b0db10199af08c7e24c71c39f119f07616621cb86d774c7af07b84e9fd82cc9592c7f7d0a' 78 | pk = 'ak_2L61wjvTKBKK985sbgn7vryr66K8F4ZwyUVrzYYvro85j5sCeU' 79 | tx = 'tx_+FAMAaEBrwjH4kxxw58RnwdhZiHLhtd0x68HuE6f2CzJWSx/fQqhAa8Ix+JMccOfEZ8HYWYhy4bXdMevB7hOn9gsyVksf30KZIUukO3QAAABgJoyIic=' 80 | sg = 'sg_Tzrf8pDzK53RVfiTdr3GnM86E4jWoGmA2RR6XaCws4PFfnbUTGQ2adRWc8Y55NpxXBaEYD5b1FP5RzNST1GpBZUVfZrLo' 81 | network_id = "ae_testnet" 82 | account = Account.from_secret_key_string(sk) 83 | with raises(ValueError): 84 | txs = transactions.TxSigner(None, network_id) 85 | with raises(ValueError): 86 | txs = transactions.TxSigner(account, None) 87 | 88 | # parse the transaction 89 | txo = transactions.TxBuilder().parse_tx_string(tx) 90 | # verify the signature 91 | signer = transactions.TxSigner(account, network_id) 92 | signature = signer.sign_transaction(txo) 93 | assert f"{signer}" == f"{network_id}:{account.get_address()}" 94 | assert signature == sg 95 | # incorrect network_id 96 | signer = transactions.TxSigner(account, "not_good") 97 | signature = signer.sign_transaction(txo) 98 | assert signature != sg 99 | 100 | 101 | def test_transaction_tx_object_signed(): 102 | sk = 'ed067bef18b3e2be42822b32e3fa468ceee1c8c2c8744ca15e96855b0db10199af08c7e24c71c39f119f07616621cb86d774c7af07b84e9fd82cc9592c7f7d0a' 103 | pk = 'ak_2L61wjvTKBKK985sbgn7vryr66K8F4ZwyUVrzYYvro85j5sCeU' 104 | tx = 'tx_+FAMAaEBrwjH4kxxw58RnwdhZiHLhtd0x68HuE6f2CzJWSx/fQqhAa8Ix+JMccOfEZ8HYWYhy4bXdMevB7hOn9gsyVksf30KZIUukO3QAAABgJoyIic=' 105 | sg = 'sg_Tzrf8pDzK53RVfiTdr3GnM86E4jWoGmA2RR6XaCws4PFfnbUTGQ2adRWc8Y55NpxXBaEYD5b1FP5RzNST1GpBZUVfZrLo' 106 | th = 'th_LL9hH3LvaLYdzm1wXnFeaBkhbNtsqZFCFfWf7oo5rtMMUC3Jk' 107 | network_id = "ae_testnet" 108 | 109 | txb = transactions.TxBuilder() 110 | txo = txb.parse_tx_string(tx) 111 | # create a signed transaction 112 | txs = txb.tx_signed([sg], txo) 113 | # verify the hash 114 | assert txs.hash == th 115 | assert transactions.TxBuilder.compute_tx_hash(txs.tx) == th 116 | assert txs.get("signatures")[0] == sg 117 | 118 | 119 | def test_transaction_tx_object_spend(): 120 | amount=1870600000000000000 121 | fee=20500000000000 122 | nonce=316260 123 | payload="ba_VGltZSBpcyBtb25leSwgbXkgZnJpZW5kcy4gL1lvdXJzIEJlZXBvb2wuLyrtvsY=" 124 | recipient_id="ak_YZeWQYL8UzStPmvPdQREcvrdVTAA6xd3jim3PohRbMX2983hg" 125 | sender_id="ak_nv5B93FPzRHrGNmMdTDfGdd5xGZvep3MVSpJqzcQmMp59bBCv" 126 | _type="SpendTx" 127 | ttl = 0 128 | version=1 129 | tag=idf.OBJECT_TAG_SPEND_TRANSACTION 130 | signatures="sg_RogsZGaYNefpT9hCZwEQNbgHVqWeR8MHD624xUYPjgjQPnK61vAuw7i63kCsYCiaRpbkYgyZEF4i9ipDAB6VS1AhrARwh" 131 | tx_hash="th_JPyq2xJuxsm8qWdwuySnGde9SU2CTqvqKFikW2jLoTfyMg2BF" 132 | # meta properties 133 | meta_min_fee = 17740000000000 134 | 135 | 136 | api_spend_tx = { 137 | "amount":amount, 138 | "fee":fee, 139 | "nonce":nonce, 140 | "payload":payload, 141 | "recipient_id":recipient_id, 142 | "sender_id":sender_id, 143 | "type":_type, 144 | "version":version 145 | } 146 | 147 | api_signed_tx = Munch.fromDict({ 148 | "block_height":181115, 149 | "block_hash":"mh_2essz8vfq4A8UZU98Jpm5VcB3Wt7p5CbQhzBgojjsM4AFq6z2s", 150 | "hash":tx_hash, 151 | "signatures":[ 152 | signatures 153 | ], 154 | "tx": api_spend_tx 155 | }) 156 | 157 | 158 | txbl = transactions.TxBuilder() 159 | txo_from_py = txbl.tx_spend(sender_id, recipient_id, amount, hashing.decode(payload), fee, ttl, nonce) 160 | txo_from_api = txbl.parse_node_reply(api_signed_tx).data.tx 161 | txo_from_str = txbl.parse_tx_string(txo_from_py.tx) 162 | 163 | assert txo_from_py.tx == txo_from_str.tx 164 | assert txo_from_py.tx == txo_from_api.tx 165 | assert txo_from_py.get("recipient_id") == txo_from_api.get("recipient_id") == txo_from_str.get("recipient_id") == recipient_id 166 | assert txo_from_py.meta("min_fee") == meta_min_fee 167 | assert txo_from_api.meta("min_fee") == meta_min_fee 168 | assert txo_from_str.meta("min_fee") == meta_min_fee 169 | 170 | 171 | -------------------------------------------------------------------------------- /tests/test_tutorial01-spend.py: -------------------------------------------------------------------------------- 1 | from . import conftest 2 | 3 | """ 4 | WARNING: this file is used as source for the tutorial in 5 | docs/intro/tutorial01-spend.rst 6 | line numbers are important 7 | verify the doc file for consistency after edit 8 | """ 9 | 10 | from aeternity.node import NodeClient, Config 11 | from aeternity.signing import Account 12 | from aeternity import utils 13 | import os 14 | 15 | def test_tutorial_spend(chain_fixture): 16 | 17 | NODE_URL = os.environ.get('TEST_URL', 'https://testnet.aeternity.io') 18 | 19 | node_cli = NodeClient(Config( 20 | external_url=NODE_URL, 21 | blocking_mode=True, 22 | )) 23 | 24 | # generate ALICE account 25 | alice = Account.generate() 26 | 27 | # generate BOB account 28 | bob = Account.generate() 29 | 30 | # retrieve the balances for the accounts 31 | bob_balance = node_cli.get_balance(bob) 32 | alice_balance = node_cli.get_balance(alice) 33 | 34 | # print the balance of the two accounts 35 | print(f"Alice address is {alice.get_address()}") 36 | print(f"with balance {utils.format_amount(alice_balance)}") 37 | print(f"Bob address is {bob.get_address()}") 38 | print(f"with balance {utils.format_amount(bob_balance)}") 39 | 40 | # begin - tests execution section 41 | # top up the account from the test suite account, 42 | # outside the tests use the faucet to top_up an account 43 | tx = node_cli.spend(chain_fixture.ALICE, alice.get_address(), "5AE") 44 | # end - tests execution section 45 | 46 | #TODO pause the execution while using the faucet 47 | # execute the spend transaction 48 | tx = node_cli.spend(alice, bob.get_address(), "3AE") 49 | print(f"transaction hash: {tx.hash}") 50 | print(f"inspect transaction at {NODE_URL}/v2/transactions/{tx.hash}") 51 | 52 | # begin - tests execution section 53 | assert bob.get_address() == tx.data.tx.data.recipient_id 54 | assert alice.get_address() == tx.data.tx.data.sender_id 55 | # end - tests execution section 56 | 57 | # retrieve the balances for the accounts 58 | bob_balance = node_cli.get_balance(bob) 59 | alice_balance = node_cli.get_balance(alice) 60 | 61 | print(f"Alice balance is {utils.format_amount(alice_balance)}") 62 | print(f"Bob balance is {utils.format_amount(bob_balance)}") 63 | 64 | # begin - tests execution section 65 | assert bob_balance > 0 66 | assert alice_balance > 0 67 | assert alice_balance < bob_balance 68 | # end - tests execution section 69 | 70 | -------------------------------------------------------------------------------- /tests/test_tutorial05-contracts.py: -------------------------------------------------------------------------------- 1 | from . import conftest 2 | 3 | """ 4 | WARINING: this file is used as source for the tutorial in 5 | docs/intro/tutorial05-contracts.rst 6 | line numbers are important 7 | verify the doc file for consistency after edit 8 | """ 9 | import os 10 | 11 | from aeternity.node import NodeClient, Config 12 | from aeternity.compiler import CompilerClient 13 | from aeternity.contract_native import ContractNative 14 | from aeternity.signing import Account 15 | 16 | def test_example_contract_native(chain_fixture): 17 | 18 | NODE_URL = os.environ.get('TEST_URL', 'http://127.0.0.1:3013') 19 | NODE_INTERNAL_URL = os.environ.get('TEST_DEBUG_URL', 'http://127.0.0.1:3113') 20 | COMPILER_URL = os.environ.get('TEST_COMPILER__URL', 'https://compiler.aepps.com') 21 | 22 | node_cli = NodeClient(Config( 23 | external_url=NODE_URL, 24 | internal_url=NODE_INTERNAL_URL, 25 | blocking_mode=True, 26 | )) 27 | 28 | compiler = CompilerClient(compiler_url=COMPILER_URL) 29 | 30 | sender_account = chain_fixture.ALICE 31 | 32 | # genrate ALICE account (and transfer AE to alice account) 33 | alice = Account.generate() 34 | 35 | node_cli.spend(sender_account, alice.get_address(), 5000000000000000000) 36 | 37 | CONTRACT_FILE = os.path.join(os.path.dirname(__file__), "testdata/CryptoHamster.aes") 38 | 39 | # read contract file 40 | with open(CONTRACT_FILE, 'r') as file: 41 | crypto_hamster_contract = file.read() 42 | 43 | """ 44 | Initialize the contract instance 45 | Note: To disable use of dry-run endpoint add the parameter 46 | use_dry_run=False 47 | """ 48 | crypto_hamster = ContractNative(client=node_cli, 49 | compiler=compiler, 50 | account=alice, 51 | source=crypto_hamster_contract) 52 | 53 | # deploy the contract 54 | # (also pass the args of thr init function - if any) 55 | tx = crypto_hamster.deploy() 56 | print(f"contract address: {crypto_hamster.address}") 57 | 58 | 59 | # PART 2 Call the contract 60 | CONTRACT_ID = crypto_hamster.address 61 | 62 | # CONTRACT_ID is the address of the deployed contract 63 | crypto_hamster.at(CONTRACT_ID) 64 | 65 | # call the contract method (stateful) 66 | tx_info, tx_result = crypto_hamster.create_hamster("SuperCryptoHamster") 67 | 68 | print(f"Transaction Hash: {tx_info.tx_hash}") 69 | print(f"Transaction Result/Return Data: {tx_result}") 70 | 71 | 72 | assert(hasattr(tx_info, 'tx_hash')) 73 | 74 | # call contract method (not stateful) 75 | tx_info, tx_result = crypto_hamster.get_hamster_dna("SuperCryptoHamster", None) 76 | 77 | print(f"Transaction Result/Return Data: {tx_result}") 78 | 79 | assert tx_result is not None 80 | assert not hasattr(tx_info, 'tx_hash') 81 | 82 | -------------------------------------------------------------------------------- /tests/test_tutorial06-hdwallet.py: -------------------------------------------------------------------------------- 1 | from aeternity.hdwallet import HDWallet 2 | 3 | def test_tutorial_hdwallet(): 4 | 5 | # Generating a mnemonic 6 | mnemonic = HDWallet.generate_mnemonic() 7 | 8 | assert mnemonic is not None 9 | 10 | # Initializing the HDWallet 11 | # (if mnemonic is not provided, a one will be autogenerated during initialization) 12 | hdwallet = HDWallet(mnemonic) 13 | 14 | assert hdwallet.master_key is not None 15 | 16 | # Derive child accounts 17 | # if you want to derive a child key with an specific account and/or address index 18 | # then you can pass them to the method 19 | # By default, every time you generate a child key, the account index is auto generated 20 | key_path, account = hdwallet.derive_child() 21 | 22 | assert key_path is not None and account is not None -------------------------------------------------------------------------------- /tests/test_tutorial07-offline.py: -------------------------------------------------------------------------------- 1 | from . import conftest 2 | 3 | """ 4 | WARNING: this file is used as source for the tutorial in 5 | docs/intro/tutorial07-offline.rst 6 | line numbers are important 7 | verify the doc file for consistency after edit 8 | """ 9 | 10 | from aeternity.node import NodeClient, Config 11 | from aeternity.signing import Account 12 | from aeternity.transactions import TxBuilder, TxSigner 13 | from aeternity import utils, defaults, identifiers 14 | import os 15 | 16 | def test_tutorial_offline_tx(chain_fixture): 17 | 18 | # Accounts addresses 19 | account = Account.generate() 20 | 21 | # --- hide --- override the account for tests 22 | account = chain_fixture.ALICE 23 | # /--- hide --- 24 | 25 | # instantiate the transactions builder 26 | build = TxBuilder() 27 | 28 | # we will be creating 5 transactions for later broadcast TODO: warn about the nonce limit 29 | txs = [] 30 | 31 | # each transaction is going to be a spend 32 | amount = utils.amount_to_aettos("0.05AE") 33 | payload = b'' 34 | 35 | for i in range(5): 36 | # increase the account nonce 37 | account.nonce = account.nonce + 1 38 | # build the transaction 39 | tx = build.tx_spend( 40 | account.get_address(), # sender 41 | Account.generate().get_address(), # random generated recipient 42 | amount, 43 | payload, 44 | defaults.FEE, 45 | defaults.TX_TTL, 46 | account.nonce 47 | ) 48 | # save the transaction 49 | txs.append(tx) 50 | 51 | # Sign the transactions 52 | # define the network_id 53 | network_id = identifiers.NETWORK_ID_TESTNET 54 | 55 | # --- hide --- override the network_id for tests 56 | network_id = chain_fixture.NODE_CLI.config.network_id 57 | # /--- hide --- 58 | 59 | # instantiate a transaction signer 60 | signer = TxSigner(account, network_id) 61 | 62 | # collect the signed tx for broadcast 63 | signed_txs = [] 64 | # sign all transactions 65 | for tx in txs: 66 | signature = signer.sign_transaction(tx) 67 | signed_tx = build.tx_signed([signature], tx) 68 | signed_txs.append(signed_tx) 69 | 70 | # Broadcast the transactions 71 | NODE_URL = os.environ.get('TEST_URL', 'https://testnet.aeternity.io') 72 | 73 | node_cli = NodeClient(Config( 74 | external_url=NODE_URL, 75 | blocking_mode=False, 76 | )) 77 | 78 | # broadcast all transactions 79 | for stx in signed_txs: 80 | node_cli.broadcast_transaction(stx) 81 | 82 | # verify that all transactions have been posted 83 | for stx in signed_txs: 84 | height = node_cli.wait_for_transaction(stx) 85 | assert(height > 0) 86 | 87 | -------------------------------------------------------------------------------- /tests/test_utils.py: -------------------------------------------------------------------------------- 1 | from aeternity import utils 2 | import pytest 3 | import random 4 | 5 | 6 | def test_utils_is_valid_hash(): 7 | # input (hash_str, prefix, expected output) 8 | args = [ 9 | ('ak_me6L5SSXL4NLWv5EkQ7a16xaA145Br7oV4sz9JphZgsTsYwGC', None, True), 10 | ('ak_me6L5SSXL4NLWv5EkQ7a16xaA145Br7oV4sz9JphZgsTsYwGC', 'ak', True), 11 | ('ak_me6L5SSXL4NLWv5EkQ7a16xaA145Br7oV4sz9JphZgsTsYwGC', 'bh', False), 12 | ('ak_me6L5SSXL4NLWv5EkQ7a16xaA145Br7oV4sz9JphZgsTsYwYC', None, False), 13 | ('ak_me6L5SSXL4NLWv5EkQ7a18xaA145Br7oV4sz9JphZgsTsYwGC', None, False), 14 | ('kh_vzUC2jVuAfpBC3tMAHhxwxJnTFymckNYeQ5TWZua1pydabqNu', None, True), 15 | ('kh_vzUC2jVuAfpBC3tMAHhxwxJnTFymckNYeQ5TWZua1pydabqNu', ["kh", "mh"], True), 16 | ('mh_vzUC2jVuAfpBC3tMAHhxwxJnTFymckNYeQ5TWZua1pydabqNu', ["kh", "mh"], True), 17 | ('th_vzUC2jVuAfpBC3tMAHhxwxJnTFymckNYeQ5TWZua1pydabqNu', ["kh", "mh"], False), 18 | ('th_YqPSTzs73PiKFhFcALYWWu41uNLc6yp63ZC35jzzuJYA9PMui', None, True), 19 | ('th_YqPSTzs73PiKFhFcALYWWu41uNLc6yp63ZC35jzzuJYA9PMui', ["th"], True), 20 | ] 21 | 22 | for a in args: 23 | got = utils.is_valid_hash(a[0], a[1]) 24 | expected = a[2] 25 | assert got == expected 26 | 27 | 28 | def test_utils_is_valid_name(): 29 | # input (hash_str, prefix, expected output) 30 | # TODO: 31 | args = [ 32 | ('valid.test', True), 33 | ('v.test', True), 34 | ('isaverylongnamethatidontknow.test', True), 35 | ('0123.test', True), 36 | ('0alsoGOod.test', True), 37 | ('valid.test', True), 38 | ('valid.test', True), 39 | ('aeternity.com', False), 40 | ('aeternity.chain', True), 41 | ('om', False), 42 | (None, False), 43 | (".o.test", False), 44 | ("-o.test", False), 45 | ] 46 | 47 | for a in args: 48 | got = utils.is_valid_aens_name(a[0]) 49 | expected = a[1] 50 | assert got == expected 51 | 52 | 53 | def test_utils_format_amount(): 54 | # input (hash_str, prefix, expected output) 55 | args = [ 56 | (1000000000000000000, "1AE"), 57 | (2000000000000000000, "2AE"), 58 | (20000000000000000000, "20AE"), 59 | (20100000000000000000, "20.1AE"), 60 | (2000000000000000, "0.002AE"), 61 | (1116270000000000000000, "1116.27AE"), 62 | (28000000001760, "0.00002800000000176AE"), 63 | (0, "0AE") 64 | 65 | ] 66 | 67 | for a in args: 68 | got = utils.format_amount(a[0]) 69 | expected = a[1] 70 | assert got == expected 71 | 72 | 73 | 74 | def test_utils_amount_to_aettos(): 75 | args = [ 76 | ("1.2AE", 1200000000000000000), 77 | ("1.2ae", 1200000000000000000), 78 | (" 1.2ae ", 1200000000000000000), 79 | ("1.25ae", 1250000000000000000), 80 | (1.3, 1300000000000000000), 81 | (10, 10), 82 | (-1, TypeError()), 83 | ("10", 10), 84 | (" 1000 ", 1000), 85 | ("1001 ", 1001), 86 | (" 1002", 1002), 87 | ("1,25ae", TypeError()), 88 | ("1ae", 1000000000000000000), 89 | ("0.000000005", 5000000000), 90 | ("0", 0), 91 | (0, 0), 92 | ] 93 | 94 | 95 | # TODO: test more float 96 | for i in range(10000): 97 | val = random.randint(0, 1000000000000000000000000) 98 | args.append((utils.format_amount(val), val)) 99 | 100 | for i in range(10000): 101 | val = random.randint(0, 1000000) 102 | args.append((utils.format_amount(val), val)) 103 | 104 | # the default context has 28 for max prcision 105 | # therefore anything greater than 1e28 will fail 106 | for i in range(10000): 107 | val = random.randint(1000000000000000000, 10000000000000000000000000000) 108 | args.append((utils.format_amount(val), val)) 109 | 110 | for a in args: 111 | expected = a[1] 112 | if issubclass(type(expected), Exception): 113 | with pytest.raises(type(expected)): 114 | utils.amount_to_aettos(a[0]) 115 | else: 116 | got = utils.amount_to_aettos(a[0]) 117 | assert got == expected 118 | -------------------------------------------------------------------------------- /tests/testdata/CryptoHamster.aes: -------------------------------------------------------------------------------- 1 | @compiler >= 4 2 | 3 | contract CryptoHamster = 4 | 5 | record state = { 6 | index : int, 7 | map_hamsters : map(string, hamster), 8 | testvalue: int} 9 | 10 | record hamster = { 11 | id : int, 12 | name : string, 13 | dna : int} 14 | 15 | stateful entrypoint init() = 16 | { index = 1, 17 | map_hamsters = {}, 18 | testvalue = 42} 19 | 20 | public entrypoint read_test_value() : int = 21 | state.testvalue 22 | 23 | public entrypoint return_caller() : address = 24 | Call.caller 25 | 26 | public entrypoint cause_error() : unit = 27 | require(2 == 1, "require failed") 28 | 29 | public stateful entrypoint add_test_value(one: int, two: int) : int = 30 | put(state{testvalue = one + two}) 31 | one + two 32 | 33 | public entrypoint locally_add_two(one: int, two: int) : int = 34 | one + two 35 | 36 | public stateful entrypoint statefully_add_two(one: int, two: int) = 37 | put(state{testvalue = one + two}) 38 | 39 | stateful entrypoint create_hamster(hamster_name: string) = 40 | require(!name_exists(hamster_name), "Name is already taken") 41 | let dna : int = generate_random_dna(hamster_name) 42 | create_hamster_by_name_dna(hamster_name, dna) 43 | 44 | entrypoint name_exists(name: string) : bool = 45 | Map.member(name, state.map_hamsters) 46 | 47 | entrypoint get_hamster_dna(name: string, test: option(int)) : int = 48 | require(name_exists(name), "There is no hamster with that name!") 49 | 50 | let needed_hamster : hamster = state.map_hamsters[name] 51 | 52 | needed_hamster.dna 53 | 54 | private stateful function create_hamster_by_name_dna(name: string, dna: int) = 55 | let new_hamster : hamster = { 56 | id = state.index, 57 | name = name, 58 | dna = dna} 59 | 60 | put(state{map_hamsters[name] = new_hamster}) 61 | put(state{index = (state.index + 1)}) 62 | 63 | private function generate_random_dna(name: string) : int = 64 | get_block_hash_bytes_as_int() - Chain.timestamp + state.index 65 | 66 | private function get_block_hash_bytes_as_int() : int = 67 | switch(Chain.block_hash(Chain.block_height - 1)) 68 | None => abort("blockhash not found") 69 | Some(bytes) => Bytes.to_int(bytes) 70 | 71 | entrypoint test(name: string) : hash = 72 | String.sha3(name) -------------------------------------------------------------------------------- /tests/testdata/identity.aes: -------------------------------------------------------------------------------- 1 | contract Identity = 2 | entrypoint main(x : int) = x 3 | -------------------------------------------------------------------------------- /tests/testdata/identity.aes.aci.json: -------------------------------------------------------------------------------- 1 | {"encoded_aci": {"contract": {"functions": [{"arguments": [{"name": "x", "type": "int"}], "name": "main", "payable": false, "returns": "int", "stateful": false}], "name": "Identity", "payable": false, "type_defs": []}}, "interface": "contract Identity =\n entrypoint main : (int) => int\n"} -------------------------------------------------------------------------------- /tests/testdata/identity.aes.bin: -------------------------------------------------------------------------------- 1 | cb_+GZGA6CDq5XXOLq+J7CRox1K3/qS6GcWIwhKuCs49nbD/AVG/cC4OZ7+RNZEHwA3ADcAGg6CPwEDP/64F37sADcBBwcBAQCWLwIRRNZEHxFpbml0EbgXfuwRbWFpboIvAIU0LjAuMACIA0qR -------------------------------------------------------------------------------- /tests/testdata/keystore.invalid.json: -------------------------------------------------------------------------------- 1 | { 2 | "public_key": "ak_2hSFmdK98bhUw4ar7MUdTRzNQuMJfBFQYxdhN9kaiopDGqj3Cr", 3 | "crypto": { 4 | "secret_type": "ed25519", 5 | "symmetric_alg": "xsalsa20-poly1305", 6 | "ciphertext": "71acf8412331806b3ad5482cda1f6e682c541c522f61715056faf5ed2d21a9c4d68fe2cdcf147b1be99fbab20b33433f82b2a2d3bbc772957bd2fb6cf9a97f611670e0f044d8076efbe31fe30142e6f5", 7 | "cipher_params": { 8 | "nonce": "375673a887fd10910fe3bfa9c9abfd72d3d240d124ed3f3b" 9 | }, 10 | "kdf": "argon2id", 11 | "kdf_params": { 12 | "memlimit_kib": 65536, 13 | "opslimit": 2, 14 | "salt": "a84887d9539cbd9490d2b1a5c41262b2", 15 | "parallelism": 3 16 | } 17 | }, 18 | "id": "ff7d9f9c-e0ab-4fab-b8fc-beab2d322f6b", 19 | "name": "!!!FOR TESTING PURPOSE ONLY!!! - Wed 14 Nov 2018 09:40:10 CET - password:aeternity", 20 | "version": 1 21 | } 22 | -------------------------------------------------------------------------------- /tests/testdata/keystore.json: -------------------------------------------------------------------------------- 1 | { 2 | "public_key": "ak_2hSFmdK98bhUw4ar7MUdTRzNQuMJfBFQYxdhN9kaiopDGqj3Cr", 3 | "crypto": { 4 | "secret_type": "ed25519", 5 | "symmetric_alg": "xsalsa20-poly1305", 6 | "ciphertext": "71acf8412331806b3ad5482cda1f6e682c541c522f61715056faf5ed2d21a9c4d68fe2cdcf147b1be99fbab20b33433f82b2a2d3bbc772957bd2fb6cf9a97f611670e0f044d8076efbe31fe30142e6f5", 7 | "cipher_params": { 8 | "nonce": "375673a887fd10910fe3bfa9c9abfd72d3d240d124ed3f3b" 9 | }, 10 | "kdf": "argon2id", 11 | "kdf_params": { 12 | "memlimit_kib": 65536, 13 | "opslimit": 2, 14 | "salt": "a84887d9539cbd9490d2b1a5c41262b2", 15 | "parallelism": 1 16 | } 17 | }, 18 | "id": "ff7d9f9c-e0ab-4fab-b8fc-beab2d322f6b", 19 | "name": "!!!FOR TESTING PURPOSE ONLY!!! - Wed 14 Nov 2018 09:40:10 CET - password:aeternity", 20 | "version": 1 21 | } 22 | -------------------------------------------------------------------------------- /tests/testdata/simplestorage.aes: -------------------------------------------------------------------------------- 1 | contract SimpleStorage = 2 | record state = { data : int } 3 | function init(value : int) : state = { data = value } 4 | function get() : int = state.data 5 | stateful function set(value : int) = put(state{data = value}) 6 | -------------------------------------------------------------------------------- /tests/testdata/simplestorage.aes.bin: -------------------------------------------------------------------------------- 1 | cb_+QYYRgKg9YdYcn5E3qBuiLxgjZ2rJv0YsOSknFA+gkbNeaVpfGf5BKX5AUmgOoWULXtHOgf10E7h2cFqXOqxa3kc6pKJYRpEw/nlugeDc2V0uMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoP//////////////////////////////////////////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC4YAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///////////////////////////////////////////jJoEnsSQdsAgNxJqQzA+rc5DsuLDKUV7ETxQp+ItyJgJS3g2dldLhgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA///////////////////////////////////////////uEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+QKLoOIjHWzfyTkW3kyzqYV79lz0D8JW9KFJiz9+fJgMGZNEhGluaXS4wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACg//////////////////////////////////////////8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALkBoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEA//////////////////////////////////////////8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYD//////////////////////////////////////////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAuQFEYgAAj2IAAMKRgICAUX9J7EkHbAIDcSakMwPq3OQ7LiwylFexE8UKfiLciYCUtxRiAAE5V1CAgFF/4iMdbN/JORbeTLOphXv2XPQPwlb0oUmLP358mAwZk0QUYgAA0VdQgFF/OoWULXtHOgf10E7h2cFqXOqxa3kc6pKJYRpEw/nlugcUYgABG1dQYAEZUQBbYAAZWWAgAZCBUmAgkANgAFmQgVKBUllgIAGQgVJgIJADYAOBUpBZYABRWVJgAFJgAPNbYACAUmAA81tgAFFRkFZbYCABUVGQUIOSUICRUFCAWZCBUllgIAGQgVJgIJADYAAZWWAgAZCBUmAgkANgAFmQgVKBUllgIAGQgVJgIJADYAOBUoFSkFCQVltgIAFRUVlQgJFQUGAAUYFZkIFSkFBgAFJZkFCQVltQUFlQUGIAAMpWhTMuMS4wZEveVQ== --------------------------------------------------------------------------------