├── .coveragerc ├── .flake8 ├── .github ├── ignore-for-release-notes.yml └── workflows │ ├── build-docs.yml │ ├── ci.yml │ ├── coverage.yml │ ├── linter-flake8.yml │ ├── linter-pyright.yml │ └── release.yml ├── .gitignore ├── .pre-commit-config.yaml ├── .vscode └── settings.json ├── LICENSE ├── README.md ├── docs ├── Makefile ├── conf.py ├── index.rst ├── make.bat ├── modules.rst ├── multiversx_sdk.abi.rst ├── multiversx_sdk.account_management.rst ├── multiversx_sdk.accounts.rst ├── multiversx_sdk.adapters.rst ├── multiversx_sdk.builders.rst ├── multiversx_sdk.controllers.rst ├── multiversx_sdk.converters.rst ├── multiversx_sdk.core.adapters.rst ├── multiversx_sdk.core.proto.rst ├── multiversx_sdk.core.rst ├── multiversx_sdk.core.transaction_builders.rst ├── multiversx_sdk.core.transaction_parsers.rst ├── multiversx_sdk.core.transactions_factories.rst ├── multiversx_sdk.core.transactions_outcome_parsers.rst ├── multiversx_sdk.delegation.rst ├── multiversx_sdk.entrypoints.rst ├── multiversx_sdk.ledger.rst ├── multiversx_sdk.native_auth.rst ├── multiversx_sdk.network_providers.rst ├── multiversx_sdk.relayed.rst ├── multiversx_sdk.rst ├── multiversx_sdk.smart_contracts.rst ├── multiversx_sdk.token_management.rst ├── multiversx_sdk.transfers.rst ├── multiversx_sdk.validators.rst ├── multiversx_sdk.wallet.crypto.rst ├── multiversx_sdk.wallet.libraries.rst └── multiversx_sdk.wallet.rst ├── examples ├── Cookbook.ipynb ├── contracts │ ├── adder.abi.json │ ├── adder.wasm │ └── multisig-full.abi.json └── v1.ipynb ├── multiversx_sdk ├── __init__.py ├── abi │ ├── __init__.py │ ├── abi.py │ ├── abi_definition.py │ ├── abi_test.py │ ├── address_value.py │ ├── address_value_test.py │ ├── array_value.py │ ├── array_value_test.py │ ├── bigint_value.py │ ├── biguint_value.py │ ├── bool_value.py │ ├── bytes_value.py │ ├── bytes_value_test.py │ ├── code_metadata_value.py │ ├── code_metadata_value_test.py │ ├── codec.py │ ├── constants.py │ ├── counted_variadic_values.py │ ├── counted_variadic_values_test.py │ ├── enum_value.py │ ├── enum_value_test.py │ ├── explicit_enum_value.py │ ├── fields.py │ ├── fields_test.py │ ├── interface.py │ ├── list_value.py │ ├── list_value_test.py │ ├── localnet_integration_test.py │ ├── managed_decimal_signed_value.py │ ├── managed_decimal_value.py │ ├── managed_decimal_value_test.py │ ├── multi_value.py │ ├── multi_value_test.py │ ├── option_value.py │ ├── option_value_test.py │ ├── optional_value.py │ ├── optional_value_test.py │ ├── parts.py │ ├── serializer.py │ ├── serializer_test.py │ ├── shared.py │ ├── shared_test.py │ ├── small_int_values.py │ ├── string_value.py │ ├── string_value_test.py │ ├── struct_value.py │ ├── struct_value_test.py │ ├── token_identifier_value.py │ ├── tuple_value.py │ ├── tuple_value_test.py │ ├── type_formula.py │ ├── type_formula_parser.py │ ├── type_formula_parser_test.py │ ├── typesystem.py │ ├── typesystem_test.py │ ├── variadic_values.py │ └── variadic_values_test.py ├── account_management │ ├── __init__.py │ ├── account_controller.py │ ├── account_transactions_factory.py │ └── account_transactions_factory_test.py ├── accounts │ ├── __init__.py │ ├── account.py │ ├── account_test.py │ ├── ledger_account.py │ └── ledger_account_test.py ├── builders │ ├── __init__.py │ ├── token_transfers_data_builder.py │ └── transaction_builder.py ├── core │ ├── __init__.py │ ├── address.py │ ├── address_test.py │ ├── base_controller.py │ ├── base_controller_test.py │ ├── bech32.py │ ├── code_metadata.py │ ├── code_metadata_test.py │ ├── config.py │ ├── constants.py │ ├── errors.py │ ├── interfaces.py │ ├── message.py │ ├── message_test.py │ ├── proto │ │ ├── __init__.py │ │ ├── transaction.proto │ │ ├── transaction_pb2.py │ │ ├── transaction_pb2.pyi │ │ ├── transaction_serializer.py │ │ └── transaction_serializer_test.py │ ├── tokens.py │ ├── tokens_test.py │ ├── transaction.py │ ├── transaction_computer.py │ ├── transaction_events_parser.py │ ├── transaction_events_parser_test.py │ ├── transaction_on_network.py │ ├── transaction_status.py │ ├── transaction_test.py │ └── transactions_factory_config.py ├── delegation │ ├── __init__.py │ ├── delegation_controller.py │ ├── delegation_transactions_factory.py │ ├── delegation_transactions_factory_test.py │ ├── delegation_transactions_outcome_parser.py │ ├── delegation_transactions_outcome_parser_test.py │ ├── delegation_transactions_outcome_parser_types.py │ └── errors.py ├── entrypoints │ ├── __init__.py │ ├── config.py │ ├── entrypoints.py │ ├── entrypoints_test.py │ └── errors.py ├── ledger │ ├── __init__.py │ ├── config.py │ ├── errors.py │ └── ledger_app.py ├── native_auth │ ├── __init__.py │ ├── config.py │ ├── constants.py │ ├── errors.py │ ├── native_auth_client.py │ ├── native_auth_client_test.py │ ├── native_auth_server.py │ ├── native_auth_server_test.py │ └── resources.py ├── network_providers │ ├── __init__.py │ ├── account_awaiter.py │ ├── account_awaiter_test.py │ ├── api_network_provider.py │ ├── api_network_provider_test.py │ ├── config.py │ ├── constants.py │ ├── errors.py │ ├── http_resources.py │ ├── interface.py │ ├── proxy_network_provider.py │ ├── proxy_network_provider_test.py │ ├── resources.py │ ├── shared.py │ ├── transaction_awaiter.py │ ├── transaction_awaiter_test.py │ ├── transaction_decoder.py │ ├── transaction_decoder_test.py │ └── user_agent.py ├── py.typed ├── relayed │ ├── __init__.py │ ├── errors.py │ ├── relayed_controller.py │ ├── relayed_transactions_factory.py │ └── relayed_transactions_factory_test.py ├── smart_contracts │ ├── __init__.py │ ├── errors.py │ ├── smart_contract_controller.py │ ├── smart_contract_controller_test.py │ ├── smart_contract_query.py │ ├── smart_contract_transaction_factory_test.py │ ├── smart_contract_transactions_factory.py │ ├── smart_contract_transactions_outcome_parser.py │ ├── smart_contract_transactions_outcome_parser_devnet_test.py │ ├── smart_contract_transactions_outcome_parser_mainnet_test.py │ ├── smart_contract_transactions_outcome_parser_test.py │ └── smart_contract_transactions_outcome_parser_types.py ├── testutils │ ├── mock_network_provider.py │ ├── mock_transaction_on_network.py │ ├── testdata │ │ ├── README.md │ │ ├── adder.abi.json │ │ ├── adder.wasm │ │ ├── answer.abi.json │ │ ├── artificial.abi.json │ │ ├── basic-features.abi.json │ │ ├── basic-features.wasm │ │ ├── counted-variadic.abi.json │ │ ├── esdt-safe.abi.json │ │ ├── lottery-esdt.abi.json │ │ ├── multisig-full.abi.json │ │ └── transactions.mainnet.json │ ├── testwallets │ │ ├── alice.json │ │ ├── alice.pem │ │ ├── bob.json │ │ ├── bob.pem │ │ ├── carol.json │ │ ├── carol.pem │ │ ├── frank.pem │ │ ├── grace.pem │ │ ├── multipleUserKeys.pem │ │ ├── multipleValidatorKeys.pem │ │ ├── validatorKey00.pem │ │ ├── validators.pem │ │ ├── withDummyMnemonic.json │ │ └── withoutKind.json │ ├── utils.py │ └── wallets.py ├── token_management │ ├── __init__.py │ ├── token_management_controller.py │ ├── token_management_transactions_factory.py │ ├── token_management_transactions_factory_test.py │ ├── token_management_transactions_outcome_parser.py │ ├── token_management_transactions_outcome_parser_test.py │ └── token_management_transactions_outcome_parser_types.py ├── transfers │ ├── __init__.py │ ├── transfer_transactions_factory.py │ ├── transfer_transactions_factory_test.py │ └── transfers_controller.py ├── validators │ ├── __init__.py │ ├── validators_controller.py │ ├── validators_controller_test.py │ ├── validators_signers.py │ ├── validators_transactions_factory.py │ └── validators_transactions_factory_test.py └── wallet │ ├── __init__.py │ ├── constants.py │ ├── core.py │ ├── crypto │ ├── __init__.py │ ├── constants.py │ ├── decryptor.py │ ├── encrypted_data.py │ ├── encryptor.py │ └── randomness.py │ ├── errors.py │ ├── interfaces.py │ ├── keypair.py │ ├── keypair_test.py │ ├── libraries │ ├── __init__.py │ ├── bls_facade.py │ ├── bls_facade_test.py │ ├── libbls.dll │ ├── libbls.dylib │ ├── libbls.so │ └── libbls_arm64.dylib │ ├── mnemonic.py │ ├── mnemonic_test.py │ ├── pem_entry.py │ ├── pem_entry_test.py │ ├── user_keys.py │ ├── user_pem.py │ ├── user_signer.py │ ├── user_test.py │ ├── user_verifer.py │ ├── user_wallet.py │ ├── validator_keys.py │ ├── validator_pem.py │ ├── validator_signer.py │ ├── validator_test.py │ └── validator_verifier.py ├── pyproject.toml ├── pyrightconfig.json ├── pytest.ini ├── requirements-dev.txt └── requirements.txt /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | relative_files = true 3 | omit = *test.py 4 | -------------------------------------------------------------------------------- /.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | extend-ignore = E501, E722, E203 3 | exclude = transaction_pb2* 4 | -------------------------------------------------------------------------------- /.github/ignore-for-release-notes.yml: -------------------------------------------------------------------------------- 1 | changelog: 2 | exclude: 3 | labels: 4 | - ignore-for-release-notes 5 | categories: 6 | - title: What's Changed 7 | labels: 8 | - "*" 9 | -------------------------------------------------------------------------------- /.github/workflows/build-docs.yml: -------------------------------------------------------------------------------- 1 | name: Build and deploy sphinx docs 2 | 3 | on: 4 | push: 5 | branches: main 6 | workflow_dispatch: 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - uses: actions/checkout@v4 14 | 15 | - name: Set up Python 16 | uses: actions/setup-python@v3 17 | - name: Install dependencies 18 | run: | 19 | python -m pip install --upgrade pip 20 | pip install -r requirements.txt 21 | pip install -r requirements-dev.txt 22 | - name: Build documentation 23 | run: | 24 | cd docs/ 25 | make html 26 | 27 | - uses: actions/checkout@v4 28 | with: 29 | ref: "gh-pages" 30 | repository: ${{ github.repository }} 31 | path: "gh-pages-docs" 32 | 33 | - name: Deploy to GitHub Pages 34 | run: | 35 | git config --global user.email "actions@github.com" 36 | git config --global user.name "GitHub Actions" 37 | rm -rf ./gh-pages-docs/* 38 | mv -f ${{ github.workspace }}/docs/_build/html/* ./gh-pages-docs 39 | cd ./gh-pages-docs 40 | touch .nojekyll 41 | git add . 42 | git commit -m "Deploy documentation from GitHub Actions" --allow-empty 43 | git push origin gh-pages 44 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | pull_request: 5 | workflow_dispatch: 6 | 7 | jobs: 8 | coverage: 9 | runs-on: ${{ matrix.os }} 10 | 11 | strategy: 12 | matrix: 13 | os: [ubuntu-latest, macos-latest] 14 | python-version: [3.11] 15 | max-parallel: 1 # This ensures jobs run sequentially, not concurrently 16 | 17 | name: Check coverage 18 | permissions: 19 | # Gives the action the necessary permissions for publishing new 20 | # comments in pull requests. 21 | pull-requests: write 22 | # Gives the action the necessary permissions for pushing data to the 23 | # python-coverage-comment-action branch, and for editing existing 24 | # comments (to avoid publishing multiple comments in the same PR) 25 | contents: write 26 | 27 | steps: 28 | - uses: actions/checkout@v3 29 | 30 | - name: Set up Python 31 | id: setup-python 32 | uses: actions/setup-python@v4 33 | with: 34 | python-version: ${{ matrix.python-version }} 35 | 36 | - name: Install deps 37 | run: | 38 | pip install -r requirements.txt 39 | pip install -r requirements-dev.txt 40 | 41 | - name: Launch tests & generate report 42 | run: coverage run -m pytest -m "not mainnet" 43 | 44 | - name: Coverage comment 45 | id: coverage_comment 46 | if: matrix.os == 'ubuntu-latest' # Run only on Ubuntu 47 | uses: py-cov-action/python-coverage-comment-action@v3 48 | with: 49 | GITHUB_TOKEN: ${{ github.token }} 50 | 51 | - name: Store Pull Request comment to be posted 52 | if: matrix.os == 'ubuntu-latest' && steps.coverage_comment.outputs.COMMENT_FILE_WRITTEN == 'true' # Run only on Ubuntu 53 | uses: actions/upload-artifact@v4 54 | with: 55 | name: python-coverage-comment-action 56 | path: python-coverage-comment-action.txt 57 | -------------------------------------------------------------------------------- /.github/workflows/coverage.yml: -------------------------------------------------------------------------------- 1 | name: Post coverage comment 2 | 3 | on: 4 | workflow_run: 5 | workflows: ["CI"] 6 | types: 7 | - completed 8 | 9 | jobs: 10 | test: 11 | name: Run tests & display coverage 12 | runs-on: ubuntu-latest 13 | if: github.event.workflow_run.event == 'pull_request' && github.event.workflow_run.conclusion == 'success' 14 | steps: 15 | 16 | - name: Post comment 17 | uses: py-cov-action/python-coverage-comment-action@v3 18 | with: 19 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 20 | GITHUB_PR_RUN_ID: ${{ github.event.workflow_run.id }} 21 | VERBOSE: true 22 | -------------------------------------------------------------------------------- /.github/workflows/linter-flake8.yml: -------------------------------------------------------------------------------- 1 | # See: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python 2 | 3 | name: Linter checks (flake8) 4 | on: [pull_request] 5 | jobs: 6 | run-linter: 7 | name: flake8 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v3 11 | - name: Set up Python 12 | uses: actions/setup-python@v4 13 | with: 14 | python-version: '3.x' 15 | - name: Install dependencies 16 | run: | 17 | python -m pip install --upgrade pip 18 | pip install -r requirements.txt 19 | pip install -r requirements-dev.txt 20 | - name: Lint with flake8 21 | run: | 22 | flake8 multiversx_sdk --exclude=transaction_pb2* 23 | continue-on-error: true 24 | -------------------------------------------------------------------------------- /.github/workflows/linter-pyright.yml: -------------------------------------------------------------------------------- 1 | # See: https://github.com/microsoft/pyright/blob/main/docs/ci-integration.md 2 | 3 | name: Linter checks (pyright) 4 | on: [pull_request] 5 | jobs: 6 | run-linter: 7 | name: pyright 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v3 11 | - name: Set up Python 12 | uses: actions/setup-python@v4 13 | with: 14 | python-version: '3.x' 15 | - name: Install dependencies 16 | run: | 17 | python -m pip install --upgrade pip 18 | pip install -r requirements.txt 19 | pip install -r requirements-dev.txt 20 | - uses: jakebailey/pyright-action@v1 21 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | # This workflow will upload a Python Package using Twine when a release is created 2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python#publishing-to-package-registries 3 | 4 | # This workflow uses actions that are not certified by GitHub. 5 | # They are provided by a third-party and are governed by 6 | # separate terms of service, privacy policy, and support 7 | # documentation. 8 | 9 | name: Upload Python Package 10 | 11 | on: 12 | workflow_dispatch: 13 | release: 14 | types: [published] 15 | 16 | permissions: 17 | contents: read 18 | 19 | jobs: 20 | deploy: 21 | 22 | runs-on: ubuntu-latest 23 | 24 | steps: 25 | - uses: actions/checkout@v3 26 | - name: Set up Python 27 | uses: actions/setup-python@v3 28 | with: 29 | python-version: '3.x' 30 | - name: Install dependencies 31 | run: | 32 | python -m pip install --upgrade pip 33 | pip install build 34 | - name: Build package 35 | run: python -m build 36 | - name: Publish package 37 | uses: pypa/gh-action-pypi-publish@27b31702a0e7fc50959f5ad993c78deac1bdfc29 38 | with: 39 | user: __token__ 40 | password: ${{ secrets.PYPI_API_TOKEN }} 41 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/pre-commit/pre-commit-hooks 3 | rev: v5.0.0 4 | hooks: 5 | - id: check-yaml 6 | - id: end-of-file-fixer 7 | - id: trailing-whitespace 8 | - repo: https://github.com/psf/black 9 | rev: 24.10.0 10 | hooks: 11 | - id: black 12 | args: [--line-length=120] 13 | - repo: https://github.com/pycqa/isort 14 | rev: 5.13.2 15 | hooks: 16 | - id: isort 17 | name: isort (python) 18 | args: ["--profile", "black", "--filter-files"] 19 | - repo: https://github.com/PyCQA/flake8 20 | rev: 7.1.1 21 | hooks: 22 | - id: flake8 23 | args: 24 | - "--config=.flake8" 25 | - repo: https://github.com/PyCQA/autoflake 26 | rev: v2.3.1 27 | hooks: 28 | - id: autoflake 29 | args: 30 | - --in-place 31 | - --remove-all-unused-imports 32 | - repo: https://github.com/RobertCraigie/pyright-python 33 | rev: v1.1.392 34 | hooks: 35 | - id: pyright 36 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "python.testing.pytestArgs": [ 3 | "multiversx_sdk" 4 | ], 5 | "python.testing.unittestEnabled": false, 6 | "python.testing.pytestEnabled": true, 7 | "editor.formatOnSave": true, 8 | "[python]": { 9 | "editor.defaultFormatter": "ms-python.black-formatter", 10 | "editor.formatOnSave": true, 11 | "editor.codeActionsOnSave": { 12 | "source.organizeImports": "explicit" 13 | }, 14 | }, 15 | "isort.args":["--profile", "black"], 16 | "editor.codeActionsOnSave": { 17 | "source.organizeImports": "explicit" 18 | }, 19 | "files.insertFinalNewline": true, 20 | "files.trimTrailingWhitespace": true, 21 | "python.languageServer": "Pylance", 22 | "black-formatter.args": [ 23 | "--line-length=120" 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 MultiversX 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # mx-sdk-py 2 | 3 | The Python SDK for interacting with MultiversX. It's an all in one sdk that can be used to create transactions (including smart contract calls and deployments), sign and broadcast transactions, create wallets and many more. 4 | 5 | ## Documentation 6 | 7 | - [Cookbook](./examples/v1.ipynb) 8 | - [Auto-generated documentation](https://multiversx.github.io/mx-sdk-py/) 9 | 10 | ## Development setup 11 | 12 | ### Virtual environment 13 | 14 | Create a virtual environment and install the dependencies: 15 | 16 | ``` 17 | python3 -m venv ./venv 18 | source ./venv/bin/activate 19 | pip install -r ./requirements.txt --upgrade 20 | ``` 21 | 22 | Install development dependencies, as well: 23 | 24 | ``` 25 | pip install -r ./requirements-dev.txt --upgrade 26 | ``` 27 | 28 | Allow `pre-commit` to automatically run on `git commit`: 29 | ``` 30 | pre-commit install 31 | ``` 32 | 33 | Above, `requirements.txt` should mirror the **dependencies** section of `pyproject.toml`. 34 | 35 | If using VSCode, restart it or follow these steps: 36 | - `Ctrl + Shift + P` 37 | - _Select Interpreter_ 38 | - Choose `./venv/bin/python`. 39 | 40 | ### Tests 41 | 42 | Run the tests as follows: 43 | 44 | This command runs all tests: 45 | ``` 46 | pytest . 47 | ``` 48 | 49 | If you want to skip network interaction tests run: 50 | ``` 51 | pytest -m "not networkInteraction" 52 | ``` 53 | 54 | We have some tests fetching mainnet transactions that are quite time consuming. To skip those, run this command: 55 | ``` 56 | pytest -m "not mainnet" 57 | ``` 58 | 59 | ### Generate test coverage report 60 | 61 | First, we run the tests using coverage: 62 | ```sh 63 | coverage run -m pytest . 64 | ``` 65 | 66 | Then, we can generate a report in the terminal using: 67 | ```sh 68 | coverage report 69 | ``` 70 | 71 | We can also generate a html report using: 72 | ```sh 73 | coverage html 74 | ``` 75 | 76 | ### Regenerating the docs 77 | 78 | Each time a new module/submodule is added it needs to be added to the docs, as well. To do so `cd` in the root directory then run the following command: 79 | ```bash 80 | sphinx-apidoc -f -o docs/ multiversx_sdk/ '*_test.py' '*constants.py' 81 | ``` 82 | 83 | This command will regenerate the `.rst` files for each module, excluding the tests and the `constants.py` files. 84 | 85 | Also, each time a new version is released, the [**conf.py**](/docs/conf.py) file should be updated accordingly. 86 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line, and also 5 | # from the environment for the first two. 6 | SPHINXOPTS ?= 7 | SPHINXBUILD ?= sphinx-build 8 | SOURCEDIR = . 9 | BUILDDIR = _build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # Configuration file for the Sphinx documentation builder. 2 | # 3 | # For the full list of built-in configuration values, see the documentation: 4 | # https://www.sphinx-doc.org/en/master/usage/configuration.html 5 | 6 | # -- Project information ----------------------------------------------------- 7 | # https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information 8 | 9 | import os 10 | import sys 11 | 12 | sys.path.insert(0, os.path.abspath("..")) 13 | 14 | project = "multiversx-sdk" 15 | copyright = "2024, MultiversX" 16 | author = "MultiversX" 17 | release = "1.5.3" 18 | 19 | # -- General configuration --------------------------------------------------- 20 | # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration 21 | 22 | extensions = ["sphinx.ext.todo", "sphinx.ext.viewcode", "sphinx.ext.autodoc"] 23 | 24 | templates_path = ["_templates"] 25 | exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] 26 | 27 | 28 | # -- Options for HTML output ------------------------------------------------- 29 | # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output 30 | 31 | html_theme = "sphinx_rtd_theme" 32 | html_static_path = ["_static"] 33 | autoclass_content = "both" 34 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. multiversx-sdk documentation master file, created by 2 | sphinx-quickstart on Wed Apr 3 14:19:32 2024. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to multiversx-sdk's documentation! 7 | ========================================== 8 | 9 | .. toctree:: 10 | :maxdepth: 2 11 | :caption: Contents: 12 | 13 | modules 14 | 15 | Indices and tables 16 | ================== 17 | 18 | * :ref:`genindex` 19 | * :ref:`modindex` 20 | * :ref:`search` 21 | -------------------------------------------------------------------------------- /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=. 11 | set BUILDDIR=_build 12 | 13 | %SPHINXBUILD% >NUL 2>NUL 14 | if errorlevel 9009 ( 15 | echo. 16 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 17 | echo.installed, then set the SPHINXBUILD environment variable to point 18 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 19 | echo.may add the Sphinx directory to PATH. 20 | echo. 21 | echo.If you don't have Sphinx installed, grab it from 22 | echo.https://www.sphinx-doc.org/ 23 | exit /b 1 24 | ) 25 | 26 | if "%1" == "" goto help 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /docs/modules.rst: -------------------------------------------------------------------------------- 1 | multiversx_sdk 2 | ============== 3 | 4 | .. toctree:: 5 | :maxdepth: 4 6 | 7 | multiversx_sdk 8 | -------------------------------------------------------------------------------- /docs/multiversx_sdk.account_management.rst: -------------------------------------------------------------------------------- 1 | multiversx\_sdk.account\_management package 2 | =========================================== 3 | 4 | Submodules 5 | ---------- 6 | 7 | multiversx\_sdk.account\_management.account\_controller module 8 | -------------------------------------------------------------- 9 | 10 | .. automodule:: multiversx_sdk.account_management.account_controller 11 | :members: 12 | :undoc-members: 13 | :show-inheritance: 14 | 15 | multiversx\_sdk.account\_management.account\_transactions\_factory module 16 | ------------------------------------------------------------------------- 17 | 18 | .. automodule:: multiversx_sdk.account_management.account_transactions_factory 19 | :members: 20 | :undoc-members: 21 | :show-inheritance: 22 | 23 | Module contents 24 | --------------- 25 | 26 | .. automodule:: multiversx_sdk.account_management 27 | :members: 28 | :undoc-members: 29 | :show-inheritance: 30 | -------------------------------------------------------------------------------- /docs/multiversx_sdk.accounts.rst: -------------------------------------------------------------------------------- 1 | multiversx\_sdk.accounts package 2 | ================================ 3 | 4 | Submodules 5 | ---------- 6 | 7 | multiversx\_sdk.accounts.account module 8 | --------------------------------------- 9 | 10 | .. automodule:: multiversx_sdk.accounts.account 11 | :members: 12 | :undoc-members: 13 | :show-inheritance: 14 | 15 | multiversx\_sdk.accounts.ledger\_account module 16 | ----------------------------------------------- 17 | 18 | .. automodule:: multiversx_sdk.accounts.ledger_account 19 | :members: 20 | :undoc-members: 21 | :show-inheritance: 22 | 23 | Module contents 24 | --------------- 25 | 26 | .. automodule:: multiversx_sdk.accounts 27 | :members: 28 | :undoc-members: 29 | :show-inheritance: 30 | -------------------------------------------------------------------------------- /docs/multiversx_sdk.adapters.rst: -------------------------------------------------------------------------------- 1 | multiversx\_sdk.adapters package 2 | ================================ 3 | 4 | Submodules 5 | ---------- 6 | 7 | multiversx\_sdk.adapters.query\_runner\_adapter module 8 | ------------------------------------------------------ 9 | 10 | .. automodule:: multiversx_sdk.adapters.query_runner_adapter 11 | :members: 12 | :undoc-members: 13 | :show-inheritance: 14 | 15 | Module contents 16 | --------------- 17 | 18 | .. automodule:: multiversx_sdk.adapters 19 | :members: 20 | :undoc-members: 21 | :show-inheritance: 22 | -------------------------------------------------------------------------------- /docs/multiversx_sdk.builders.rst: -------------------------------------------------------------------------------- 1 | multiversx\_sdk.builders package 2 | ================================ 3 | 4 | Submodules 5 | ---------- 6 | 7 | multiversx\_sdk.builders.token\_transfers\_data\_builder module 8 | --------------------------------------------------------------- 9 | 10 | .. automodule:: multiversx_sdk.builders.token_transfers_data_builder 11 | :members: 12 | :undoc-members: 13 | :show-inheritance: 14 | 15 | multiversx\_sdk.builders.transaction\_builder module 16 | ---------------------------------------------------- 17 | 18 | .. automodule:: multiversx_sdk.builders.transaction_builder 19 | :members: 20 | :undoc-members: 21 | :show-inheritance: 22 | 23 | Module contents 24 | --------------- 25 | 26 | .. automodule:: multiversx_sdk.builders 27 | :members: 28 | :undoc-members: 29 | :show-inheritance: 30 | -------------------------------------------------------------------------------- /docs/multiversx_sdk.controllers.rst: -------------------------------------------------------------------------------- 1 | multiversx\_sdk.controllers package 2 | =================================== 3 | 4 | Submodules 5 | ---------- 6 | 7 | multiversx\_sdk.controllers.account\_controller module 8 | ------------------------------------------------------ 9 | 10 | .. automodule:: multiversx_sdk.controllers.account_controller 11 | :members: 12 | :undoc-members: 13 | :show-inheritance: 14 | 15 | multiversx\_sdk.controllers.delegation\_controller module 16 | --------------------------------------------------------- 17 | 18 | .. automodule:: multiversx_sdk.controllers.delegation_controller 19 | :members: 20 | :undoc-members: 21 | :show-inheritance: 22 | 23 | multiversx\_sdk.controllers.interfaces module 24 | --------------------------------------------- 25 | 26 | .. automodule:: multiversx_sdk.controllers.interfaces 27 | :members: 28 | :undoc-members: 29 | :show-inheritance: 30 | 31 | multiversx\_sdk.controllers.relayed\_controller module 32 | ------------------------------------------------------ 33 | 34 | .. automodule:: multiversx_sdk.controllers.relayed_controller 35 | :members: 36 | :undoc-members: 37 | :show-inheritance: 38 | 39 | multiversx\_sdk.controllers.smart\_contract\_controller module 40 | -------------------------------------------------------------- 41 | 42 | .. automodule:: multiversx_sdk.controllers.smart_contract_controller 43 | :members: 44 | :undoc-members: 45 | :show-inheritance: 46 | 47 | multiversx\_sdk.controllers.token\_management\_controller module 48 | ---------------------------------------------------------------- 49 | 50 | .. automodule:: multiversx_sdk.controllers.token_management_controller 51 | :members: 52 | :undoc-members: 53 | :show-inheritance: 54 | 55 | multiversx\_sdk.controllers.transfers\_controller module 56 | -------------------------------------------------------- 57 | 58 | .. automodule:: multiversx_sdk.controllers.transfers_controller 59 | :members: 60 | :undoc-members: 61 | :show-inheritance: 62 | 63 | Module contents 64 | --------------- 65 | 66 | .. automodule:: multiversx_sdk.controllers 67 | :members: 68 | :undoc-members: 69 | :show-inheritance: 70 | -------------------------------------------------------------------------------- /docs/multiversx_sdk.converters.rst: -------------------------------------------------------------------------------- 1 | multiversx\_sdk.converters package 2 | ================================== 3 | 4 | Submodules 5 | ---------- 6 | 7 | multiversx\_sdk.converters.errors module 8 | ---------------------------------------- 9 | 10 | .. automodule:: multiversx_sdk.converters.errors 11 | :members: 12 | :undoc-members: 13 | :show-inheritance: 14 | 15 | multiversx\_sdk.converters.transactions\_converter module 16 | --------------------------------------------------------- 17 | 18 | .. automodule:: multiversx_sdk.converters.transactions_converter 19 | :members: 20 | :undoc-members: 21 | :show-inheritance: 22 | 23 | Module contents 24 | --------------- 25 | 26 | .. automodule:: multiversx_sdk.converters 27 | :members: 28 | :undoc-members: 29 | :show-inheritance: 30 | -------------------------------------------------------------------------------- /docs/multiversx_sdk.core.adapters.rst: -------------------------------------------------------------------------------- 1 | multiversx\_sdk.core.adapters package 2 | ===================================== 3 | 4 | Submodules 5 | ---------- 6 | 7 | multiversx\_sdk.core.adapters.query\_runner\_adapter module 8 | ----------------------------------------------------------- 9 | 10 | .. automodule:: multiversx_sdk.core.adapters.query_runner_adapter 11 | :members: 12 | :undoc-members: 13 | :show-inheritance: 14 | 15 | Module contents 16 | --------------- 17 | 18 | .. automodule:: multiversx_sdk.core.adapters 19 | :members: 20 | :undoc-members: 21 | :show-inheritance: 22 | -------------------------------------------------------------------------------- /docs/multiversx_sdk.core.proto.rst: -------------------------------------------------------------------------------- 1 | multiversx\_sdk.core.proto package 2 | ================================== 3 | 4 | Submodules 5 | ---------- 6 | 7 | multiversx\_sdk.core.proto.transaction\_pb2 module 8 | -------------------------------------------------- 9 | 10 | .. automodule:: multiversx_sdk.core.proto.transaction_pb2 11 | :members: 12 | :undoc-members: 13 | :show-inheritance: 14 | 15 | multiversx\_sdk.core.proto.transaction\_serializer module 16 | --------------------------------------------------------- 17 | 18 | .. automodule:: multiversx_sdk.core.proto.transaction_serializer 19 | :members: 20 | :undoc-members: 21 | :show-inheritance: 22 | 23 | Module contents 24 | --------------- 25 | 26 | .. automodule:: multiversx_sdk.core.proto 27 | :members: 28 | :undoc-members: 29 | :show-inheritance: 30 | -------------------------------------------------------------------------------- /docs/multiversx_sdk.core.transaction_parsers.rst: -------------------------------------------------------------------------------- 1 | multiversx\_sdk.core.transaction\_parsers package 2 | ================================================= 3 | 4 | Submodules 5 | ---------- 6 | 7 | multiversx\_sdk.core.transaction\_parsers.interfaces module 8 | ----------------------------------------------------------- 9 | 10 | .. automodule:: multiversx_sdk.core.transaction_parsers.interfaces 11 | :members: 12 | :undoc-members: 13 | :show-inheritance: 14 | 15 | multiversx\_sdk.core.transaction\_parsers.token\_operations\_outcome\_parser module 16 | ----------------------------------------------------------------------------------- 17 | 18 | .. automodule:: multiversx_sdk.core.transaction_parsers.token_operations_outcome_parser 19 | :members: 20 | :undoc-members: 21 | :show-inheritance: 22 | 23 | multiversx\_sdk.core.transaction\_parsers.token\_operations\_outcome\_parser\_types module 24 | ------------------------------------------------------------------------------------------ 25 | 26 | .. automodule:: multiversx_sdk.core.transaction_parsers.token_operations_outcome_parser_types 27 | :members: 28 | :undoc-members: 29 | :show-inheritance: 30 | 31 | multiversx\_sdk.core.transaction\_parsers.transaction\_on\_network\_wrapper module 32 | ---------------------------------------------------------------------------------- 33 | 34 | .. automodule:: multiversx_sdk.core.transaction_parsers.transaction_on_network_wrapper 35 | :members: 36 | :undoc-members: 37 | :show-inheritance: 38 | 39 | Module contents 40 | --------------- 41 | 42 | .. automodule:: multiversx_sdk.core.transaction_parsers 43 | :members: 44 | :undoc-members: 45 | :show-inheritance: 46 | -------------------------------------------------------------------------------- /docs/multiversx_sdk.delegation.rst: -------------------------------------------------------------------------------- 1 | multiversx\_sdk.delegation package 2 | ================================== 3 | 4 | Submodules 5 | ---------- 6 | 7 | multiversx\_sdk.delegation.delegation\_controller module 8 | -------------------------------------------------------- 9 | 10 | .. automodule:: multiversx_sdk.delegation.delegation_controller 11 | :members: 12 | :undoc-members: 13 | :show-inheritance: 14 | 15 | multiversx\_sdk.delegation.delegation\_transactions\_factory module 16 | ------------------------------------------------------------------- 17 | 18 | .. automodule:: multiversx_sdk.delegation.delegation_transactions_factory 19 | :members: 20 | :undoc-members: 21 | :show-inheritance: 22 | 23 | multiversx\_sdk.delegation.delegation\_transactions\_outcome\_parser module 24 | --------------------------------------------------------------------------- 25 | 26 | .. automodule:: multiversx_sdk.delegation.delegation_transactions_outcome_parser 27 | :members: 28 | :undoc-members: 29 | :show-inheritance: 30 | 31 | multiversx\_sdk.delegation.delegation\_transactions\_outcome\_parser\_types module 32 | ---------------------------------------------------------------------------------- 33 | 34 | .. automodule:: multiversx_sdk.delegation.delegation_transactions_outcome_parser_types 35 | :members: 36 | :undoc-members: 37 | :show-inheritance: 38 | 39 | multiversx\_sdk.delegation.errors module 40 | ---------------------------------------- 41 | 42 | .. automodule:: multiversx_sdk.delegation.errors 43 | :members: 44 | :undoc-members: 45 | :show-inheritance: 46 | 47 | Module contents 48 | --------------- 49 | 50 | .. automodule:: multiversx_sdk.delegation 51 | :members: 52 | :undoc-members: 53 | :show-inheritance: 54 | -------------------------------------------------------------------------------- /docs/multiversx_sdk.entrypoints.rst: -------------------------------------------------------------------------------- 1 | multiversx\_sdk.entrypoints package 2 | =================================== 3 | 4 | Submodules 5 | ---------- 6 | 7 | multiversx\_sdk.entrypoints.config module 8 | ----------------------------------------- 9 | 10 | .. automodule:: multiversx_sdk.entrypoints.config 11 | :members: 12 | :undoc-members: 13 | :show-inheritance: 14 | 15 | multiversx\_sdk.entrypoints.entrypoints module 16 | ---------------------------------------------- 17 | 18 | .. automodule:: multiversx_sdk.entrypoints.entrypoints 19 | :members: 20 | :undoc-members: 21 | :show-inheritance: 22 | 23 | multiversx\_sdk.entrypoints.errors module 24 | ----------------------------------------- 25 | 26 | .. automodule:: multiversx_sdk.entrypoints.errors 27 | :members: 28 | :undoc-members: 29 | :show-inheritance: 30 | 31 | Module contents 32 | --------------- 33 | 34 | .. automodule:: multiversx_sdk.entrypoints 35 | :members: 36 | :undoc-members: 37 | :show-inheritance: 38 | -------------------------------------------------------------------------------- /docs/multiversx_sdk.ledger.rst: -------------------------------------------------------------------------------- 1 | multiversx\_sdk.ledger package 2 | ============================== 3 | 4 | Submodules 5 | ---------- 6 | 7 | multiversx\_sdk.ledger.config module 8 | ------------------------------------ 9 | 10 | .. automodule:: multiversx_sdk.ledger.config 11 | :members: 12 | :undoc-members: 13 | :show-inheritance: 14 | 15 | multiversx\_sdk.ledger.errors module 16 | ------------------------------------ 17 | 18 | .. automodule:: multiversx_sdk.ledger.errors 19 | :members: 20 | :undoc-members: 21 | :show-inheritance: 22 | 23 | multiversx\_sdk.ledger.ledger\_app module 24 | ----------------------------------------- 25 | 26 | .. automodule:: multiversx_sdk.ledger.ledger_app 27 | :members: 28 | :undoc-members: 29 | :show-inheritance: 30 | 31 | Module contents 32 | --------------- 33 | 34 | .. automodule:: multiversx_sdk.ledger 35 | :members: 36 | :undoc-members: 37 | :show-inheritance: 38 | -------------------------------------------------------------------------------- /docs/multiversx_sdk.native_auth.rst: -------------------------------------------------------------------------------- 1 | multiversx\_sdk.native\_auth package 2 | ==================================== 3 | 4 | Submodules 5 | ---------- 6 | 7 | multiversx\_sdk.native\_auth.config module 8 | ------------------------------------------ 9 | 10 | .. automodule:: multiversx_sdk.native_auth.config 11 | :members: 12 | :undoc-members: 13 | :show-inheritance: 14 | 15 | multiversx\_sdk.native\_auth.errors module 16 | ------------------------------------------ 17 | 18 | .. automodule:: multiversx_sdk.native_auth.errors 19 | :members: 20 | :undoc-members: 21 | :show-inheritance: 22 | 23 | multiversx\_sdk.native\_auth.native\_auth\_client module 24 | -------------------------------------------------------- 25 | 26 | .. automodule:: multiversx_sdk.native_auth.native_auth_client 27 | :members: 28 | :undoc-members: 29 | :show-inheritance: 30 | 31 | multiversx\_sdk.native\_auth.native\_auth\_server module 32 | -------------------------------------------------------- 33 | 34 | .. automodule:: multiversx_sdk.native_auth.native_auth_server 35 | :members: 36 | :undoc-members: 37 | :show-inheritance: 38 | 39 | multiversx\_sdk.native\_auth.resources module 40 | --------------------------------------------- 41 | 42 | .. automodule:: multiversx_sdk.native_auth.resources 43 | :members: 44 | :undoc-members: 45 | :show-inheritance: 46 | 47 | Module contents 48 | --------------- 49 | 50 | .. automodule:: multiversx_sdk.native_auth 51 | :members: 52 | :undoc-members: 53 | :show-inheritance: 54 | -------------------------------------------------------------------------------- /docs/multiversx_sdk.relayed.rst: -------------------------------------------------------------------------------- 1 | multiversx\_sdk.relayed package 2 | =============================== 3 | 4 | Submodules 5 | ---------- 6 | 7 | multiversx\_sdk.relayed.errors module 8 | ------------------------------------- 9 | 10 | .. automodule:: multiversx_sdk.relayed.errors 11 | :members: 12 | :undoc-members: 13 | :show-inheritance: 14 | 15 | multiversx\_sdk.relayed.relayed\_controller module 16 | -------------------------------------------------- 17 | 18 | .. automodule:: multiversx_sdk.relayed.relayed_controller 19 | :members: 20 | :undoc-members: 21 | :show-inheritance: 22 | 23 | multiversx\_sdk.relayed.relayed\_transactions\_factory module 24 | ------------------------------------------------------------- 25 | 26 | .. automodule:: multiversx_sdk.relayed.relayed_transactions_factory 27 | :members: 28 | :undoc-members: 29 | :show-inheritance: 30 | 31 | Module contents 32 | --------------- 33 | 34 | .. automodule:: multiversx_sdk.relayed 35 | :members: 36 | :undoc-members: 37 | :show-inheritance: 38 | -------------------------------------------------------------------------------- /docs/multiversx_sdk.rst: -------------------------------------------------------------------------------- 1 | multiversx\_sdk package 2 | ======================= 3 | 4 | Subpackages 5 | ----------- 6 | 7 | .. toctree:: 8 | :maxdepth: 4 9 | 10 | multiversx_sdk.abi 11 | multiversx_sdk.account_management 12 | multiversx_sdk.accounts 13 | multiversx_sdk.builders 14 | multiversx_sdk.core 15 | multiversx_sdk.delegation 16 | multiversx_sdk.entrypoints 17 | multiversx_sdk.ledger 18 | multiversx_sdk.native_auth 19 | multiversx_sdk.network_providers 20 | multiversx_sdk.relayed 21 | multiversx_sdk.smart_contracts 22 | multiversx_sdk.token_management 23 | multiversx_sdk.transfers 24 | multiversx_sdk.validators 25 | multiversx_sdk.wallet 26 | 27 | Module contents 28 | --------------- 29 | 30 | .. automodule:: multiversx_sdk 31 | :members: 32 | :undoc-members: 33 | :show-inheritance: 34 | -------------------------------------------------------------------------------- /docs/multiversx_sdk.smart_contracts.rst: -------------------------------------------------------------------------------- 1 | multiversx\_sdk.smart\_contracts package 2 | ======================================== 3 | 4 | Submodules 5 | ---------- 6 | 7 | multiversx\_sdk.smart\_contracts.errors module 8 | ---------------------------------------------- 9 | 10 | .. automodule:: multiversx_sdk.smart_contracts.errors 11 | :members: 12 | :undoc-members: 13 | :show-inheritance: 14 | 15 | multiversx\_sdk.smart\_contracts.smart\_contract\_controller module 16 | ------------------------------------------------------------------- 17 | 18 | .. automodule:: multiversx_sdk.smart_contracts.smart_contract_controller 19 | :members: 20 | :undoc-members: 21 | :show-inheritance: 22 | 23 | multiversx\_sdk.smart\_contracts.smart\_contract\_query module 24 | -------------------------------------------------------------- 25 | 26 | .. automodule:: multiversx_sdk.smart_contracts.smart_contract_query 27 | :members: 28 | :undoc-members: 29 | :show-inheritance: 30 | 31 | multiversx\_sdk.smart\_contracts.smart\_contract\_transactions\_factory module 32 | ------------------------------------------------------------------------------ 33 | 34 | .. automodule:: multiversx_sdk.smart_contracts.smart_contract_transactions_factory 35 | :members: 36 | :undoc-members: 37 | :show-inheritance: 38 | 39 | multiversx\_sdk.smart\_contracts.smart\_contract\_transactions\_outcome\_parser module 40 | -------------------------------------------------------------------------------------- 41 | 42 | .. automodule:: multiversx_sdk.smart_contracts.smart_contract_transactions_outcome_parser 43 | :members: 44 | :undoc-members: 45 | :show-inheritance: 46 | 47 | multiversx\_sdk.smart\_contracts.smart\_contract\_transactions\_outcome\_parser\_types module 48 | --------------------------------------------------------------------------------------------- 49 | 50 | .. automodule:: multiversx_sdk.smart_contracts.smart_contract_transactions_outcome_parser_types 51 | :members: 52 | :undoc-members: 53 | :show-inheritance: 54 | 55 | Module contents 56 | --------------- 57 | 58 | .. automodule:: multiversx_sdk.smart_contracts 59 | :members: 60 | :undoc-members: 61 | :show-inheritance: 62 | -------------------------------------------------------------------------------- /docs/multiversx_sdk.token_management.rst: -------------------------------------------------------------------------------- 1 | multiversx\_sdk.token\_management package 2 | ========================================= 3 | 4 | Submodules 5 | ---------- 6 | 7 | multiversx\_sdk.token\_management.token\_management\_controller module 8 | ---------------------------------------------------------------------- 9 | 10 | .. automodule:: multiversx_sdk.token_management.token_management_controller 11 | :members: 12 | :undoc-members: 13 | :show-inheritance: 14 | 15 | multiversx\_sdk.token\_management.token\_management\_transactions\_factory module 16 | --------------------------------------------------------------------------------- 17 | 18 | .. automodule:: multiversx_sdk.token_management.token_management_transactions_factory 19 | :members: 20 | :undoc-members: 21 | :show-inheritance: 22 | 23 | multiversx\_sdk.token\_management.token\_management\_transactions\_outcome\_parser module 24 | ----------------------------------------------------------------------------------------- 25 | 26 | .. automodule:: multiversx_sdk.token_management.token_management_transactions_outcome_parser 27 | :members: 28 | :undoc-members: 29 | :show-inheritance: 30 | 31 | multiversx\_sdk.token\_management.token\_management\_transactions\_outcome\_parser\_types module 32 | ------------------------------------------------------------------------------------------------ 33 | 34 | .. automodule:: multiversx_sdk.token_management.token_management_transactions_outcome_parser_types 35 | :members: 36 | :undoc-members: 37 | :show-inheritance: 38 | 39 | Module contents 40 | --------------- 41 | 42 | .. automodule:: multiversx_sdk.token_management 43 | :members: 44 | :undoc-members: 45 | :show-inheritance: 46 | -------------------------------------------------------------------------------- /docs/multiversx_sdk.transfers.rst: -------------------------------------------------------------------------------- 1 | multiversx\_sdk.transfers package 2 | ================================= 3 | 4 | Submodules 5 | ---------- 6 | 7 | multiversx\_sdk.transfers.transfer\_transactions\_factory module 8 | ---------------------------------------------------------------- 9 | 10 | .. automodule:: multiversx_sdk.transfers.transfer_transactions_factory 11 | :members: 12 | :undoc-members: 13 | :show-inheritance: 14 | 15 | multiversx\_sdk.transfers.transfers\_controller module 16 | ------------------------------------------------------ 17 | 18 | .. automodule:: multiversx_sdk.transfers.transfers_controller 19 | :members: 20 | :undoc-members: 21 | :show-inheritance: 22 | 23 | Module contents 24 | --------------- 25 | 26 | .. automodule:: multiversx_sdk.transfers 27 | :members: 28 | :undoc-members: 29 | :show-inheritance: 30 | -------------------------------------------------------------------------------- /docs/multiversx_sdk.validators.rst: -------------------------------------------------------------------------------- 1 | multiversx\_sdk.validators package 2 | ================================== 3 | 4 | Submodules 5 | ---------- 6 | 7 | multiversx\_sdk.validators.validators\_controller module 8 | -------------------------------------------------------- 9 | 10 | .. automodule:: multiversx_sdk.validators.validators_controller 11 | :members: 12 | :undoc-members: 13 | :show-inheritance: 14 | 15 | multiversx\_sdk.validators.validators\_signers module 16 | ----------------------------------------------------- 17 | 18 | .. automodule:: multiversx_sdk.validators.validators_signers 19 | :members: 20 | :undoc-members: 21 | :show-inheritance: 22 | 23 | multiversx\_sdk.validators.validators\_transactions\_factory module 24 | ------------------------------------------------------------------- 25 | 26 | .. automodule:: multiversx_sdk.validators.validators_transactions_factory 27 | :members: 28 | :undoc-members: 29 | :show-inheritance: 30 | 31 | Module contents 32 | --------------- 33 | 34 | .. automodule:: multiversx_sdk.validators 35 | :members: 36 | :undoc-members: 37 | :show-inheritance: 38 | -------------------------------------------------------------------------------- /docs/multiversx_sdk.wallet.crypto.rst: -------------------------------------------------------------------------------- 1 | multiversx\_sdk.wallet.crypto package 2 | ===================================== 3 | 4 | Submodules 5 | ---------- 6 | 7 | multiversx\_sdk.wallet.crypto.decryptor module 8 | ---------------------------------------------- 9 | 10 | .. automodule:: multiversx_sdk.wallet.crypto.decryptor 11 | :members: 12 | :undoc-members: 13 | :show-inheritance: 14 | 15 | multiversx\_sdk.wallet.crypto.encrypted\_data module 16 | ---------------------------------------------------- 17 | 18 | .. automodule:: multiversx_sdk.wallet.crypto.encrypted_data 19 | :members: 20 | :undoc-members: 21 | :show-inheritance: 22 | 23 | multiversx\_sdk.wallet.crypto.encryptor module 24 | ---------------------------------------------- 25 | 26 | .. automodule:: multiversx_sdk.wallet.crypto.encryptor 27 | :members: 28 | :undoc-members: 29 | :show-inheritance: 30 | 31 | multiversx\_sdk.wallet.crypto.randomness module 32 | ----------------------------------------------- 33 | 34 | .. automodule:: multiversx_sdk.wallet.crypto.randomness 35 | :members: 36 | :undoc-members: 37 | :show-inheritance: 38 | 39 | Module contents 40 | --------------- 41 | 42 | .. automodule:: multiversx_sdk.wallet.crypto 43 | :members: 44 | :undoc-members: 45 | :show-inheritance: 46 | -------------------------------------------------------------------------------- /docs/multiversx_sdk.wallet.libraries.rst: -------------------------------------------------------------------------------- 1 | multiversx\_sdk.wallet.libraries package 2 | ======================================== 3 | 4 | Submodules 5 | ---------- 6 | 7 | multiversx\_sdk.wallet.libraries.bls\_facade module 8 | --------------------------------------------------- 9 | 10 | .. automodule:: multiversx_sdk.wallet.libraries.bls_facade 11 | :members: 12 | :undoc-members: 13 | :show-inheritance: 14 | 15 | multiversx\_sdk.wallet.libraries.libbls module 16 | ---------------------------------------------- 17 | 18 | .. automodule:: multiversx_sdk.wallet.libraries.libbls 19 | :members: 20 | :undoc-members: 21 | :show-inheritance: 22 | 23 | Module contents 24 | --------------- 25 | 26 | .. automodule:: multiversx_sdk.wallet.libraries 27 | :members: 28 | :undoc-members: 29 | :show-inheritance: 30 | -------------------------------------------------------------------------------- /examples/contracts/adder.abi.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Adder", 3 | "constructor": { 4 | "inputs": [ 5 | { 6 | "name": "initial_value", 7 | "type": "BigUint" 8 | } 9 | ], 10 | "outputs": [] 11 | }, 12 | "upgradeConstructor": { 13 | "inputs": [ 14 | { 15 | "name": "initial_value", 16 | "type": "BigUint" 17 | } 18 | ], 19 | "outputs": [] 20 | }, 21 | "endpoints": [ 22 | { 23 | "name": "getSum", 24 | "mutability": "readonly", 25 | "inputs": [], 26 | "outputs": [ 27 | { 28 | "type": "BigUint" 29 | } 30 | ] 31 | }, 32 | { 33 | "docs": ["Add desired amount to the storage variable."], 34 | "name": "add", 35 | "mutability": "mutable", 36 | "inputs": [ 37 | { 38 | "name": "value", 39 | "type": "BigUint" 40 | } 41 | ], 42 | "outputs": [] 43 | } 44 | ], 45 | "esdtAttributes": [], 46 | "hasCallback": false, 47 | "types": {} 48 | } 49 | -------------------------------------------------------------------------------- /examples/contracts/adder.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/multiversx/mx-sdk-py/e7994cfd135a4e88ab4282ce07b41d7524ddc34e/examples/contracts/adder.wasm -------------------------------------------------------------------------------- /multiversx_sdk/abi/address_value.py: -------------------------------------------------------------------------------- 1 | import io 2 | from typing import Any, Protocol, cast 3 | 4 | from multiversx_sdk.abi.shared import read_bytes_exactly 5 | from multiversx_sdk.core.address import PUBKEY_LENGTH, Address 6 | 7 | 8 | class IAddress(Protocol): 9 | """ 10 | For internal use only. 11 | """ 12 | 13 | def get_public_key(self) -> bytes: ... 14 | 15 | 16 | class AddressValue: 17 | def __init__(self, value: bytes = b"") -> None: 18 | self.value = value 19 | 20 | @classmethod 21 | def new_from_address(cls, address: IAddress) -> "AddressValue": 22 | return cls(address.get_public_key()) 23 | 24 | def encode_nested(self, writer: io.BytesIO): 25 | self._check_pub_key_length(self.value) 26 | writer.write(self.value) 27 | 28 | def encode_top_level(self, writer: io.BytesIO): 29 | self.encode_nested(writer) 30 | 31 | def decode_nested(self, reader: io.BytesIO): 32 | data = read_bytes_exactly(reader, PUBKEY_LENGTH) 33 | self.value = data 34 | 35 | def decode_top_level(self, data: bytes): 36 | self._check_pub_key_length(data) 37 | self.value = data 38 | 39 | def _check_pub_key_length(self, pubkey: bytes) -> None: 40 | if len(pubkey) != PUBKEY_LENGTH: 41 | raise ValueError(f"public key (address) has invalid length: {len(pubkey)}") 42 | 43 | def set_payload(self, value: Any): 44 | if isinstance(value, dict): 45 | value = cast(dict[str, str], value) 46 | pubkey = self._extract_pubkey_from_dict(value) 47 | elif isinstance(value, str): 48 | address = Address.new_from_bech32(value) 49 | pubkey = address.get_public_key() 50 | else: 51 | pubkey = bytes(value) 52 | 53 | self._check_pub_key_length(pubkey) 54 | self.value = pubkey 55 | 56 | def _extract_pubkey_from_dict(self, value: dict[str, str]) -> bytes: 57 | bech32_address = value.get("bech32", None) 58 | if bech32_address: 59 | return Address.new_from_bech32(bech32_address).get_public_key() 60 | 61 | hex_address = value.get("hex", None) 62 | if hex_address: 63 | return bytes.fromhex(hex_address) 64 | 65 | raise ValueError("cannot extract pubkey from dictionary: missing 'bech32' or 'hex' keys") 66 | 67 | def get_payload(self) -> Any: 68 | return self.value 69 | 70 | def __eq__(self, other: Any) -> bool: 71 | return isinstance(other, AddressValue) and self.value == other.value 72 | 73 | def __bytes__(self) -> bytes: 74 | return self.value 75 | -------------------------------------------------------------------------------- /multiversx_sdk/abi/address_value_test.py: -------------------------------------------------------------------------------- 1 | import re 2 | from types import SimpleNamespace 3 | 4 | import pytest 5 | 6 | from multiversx_sdk.abi.address_value import AddressValue 7 | from multiversx_sdk.core.address import Address 8 | 9 | 10 | def test_set_payload_and_get_payload(): 11 | # Simple 12 | pubkey = bytes.fromhex("0139472eff6886771a982f3083da5d421f24c29181e63888228dc81ca60d69e1") 13 | value = AddressValue() 14 | value.set_payload(pubkey) 15 | assert value.get_payload() == pubkey 16 | 17 | # Simple (from Address) 18 | address = Address.new_from_bech32("erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th") 19 | value = AddressValue() 20 | value.set_payload(address) 21 | assert value.get_payload() == address.get_public_key() 22 | 23 | # Simple (from bech32) 24 | address = Address.new_from_bech32("erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th") 25 | value = AddressValue() 26 | value.set_payload(address.to_bech32()) 27 | assert value.get_payload() == address.get_public_key() 28 | 29 | # From dict using a bech32 address 30 | address = Address.new_from_bech32("erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th") 31 | value = AddressValue() 32 | value.set_payload({"bech32": address.to_bech32()}) 33 | assert value.get_payload() == address.get_public_key() 34 | 35 | # From dict using a hex address 36 | address = Address.new_from_bech32("erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th") 37 | value = AddressValue() 38 | value.set_payload({"hex": address.to_hex()}) 39 | assert value.get_payload() == address.get_public_key() 40 | 41 | value = AddressValue.new_from_address(address) 42 | assert value.get_payload() == address.get_public_key() 43 | 44 | # With errors 45 | with pytest.raises(ValueError, match=re.escape("public key (address) has invalid length: 3")): 46 | AddressValue().set_payload(bytes([1, 2, 3])) 47 | 48 | # With errors 49 | with pytest.raises(TypeError, match="cannot convert 'types.SimpleNamespace' object to bytes"): 50 | AddressValue().set_payload(SimpleNamespace(a=1, b=2, c=3)) 51 | 52 | # With errors 53 | with pytest.raises( 54 | ValueError, 55 | match="cannot extract pubkey from dictionary: missing 'bech32' or 'hex' keys", 56 | ): 57 | AddressValue().set_payload({}) 58 | -------------------------------------------------------------------------------- /multiversx_sdk/abi/bigint_value.py: -------------------------------------------------------------------------------- 1 | import io 2 | from typing import Any 3 | 4 | from multiversx_sdk.abi.shared import decode_length, encode_length, read_bytes_exactly 5 | 6 | 7 | class BigIntValue: 8 | def __init__(self, value: int = 0) -> None: 9 | self.value = value 10 | 11 | def encode_nested(self, writer: io.BytesIO): 12 | data = self._signed_to_bytes() 13 | encode_length(writer, len(data)) 14 | writer.write(data) 15 | 16 | def encode_top_level(self, writer: io.BytesIO): 17 | data = self._signed_to_bytes() 18 | writer.write(data) 19 | 20 | def decode_nested(self, reader: io.BytesIO): 21 | length = decode_length(reader) 22 | data = read_bytes_exactly(reader, length) 23 | self.value = self._signed_from_bytes(data) 24 | 25 | def decode_top_level(self, data: bytes): 26 | self.value = self._signed_from_bytes(data) 27 | 28 | def _signed_to_bytes(self) -> bytes: 29 | value = self.value 30 | 31 | if value == 0: 32 | return b"" 33 | 34 | length = ((value + (value < 0)).bit_length() + 7 + 1) // 8 35 | data = value.to_bytes(length, byteorder="big", signed=True) 36 | return data 37 | 38 | def _signed_from_bytes(self, data: bytes) -> int: 39 | return int.from_bytes(data, byteorder="big", signed=True) 40 | 41 | def set_payload(self, value: Any): 42 | self.value = int(value) 43 | 44 | def get_payload(self) -> Any: 45 | return self.value 46 | 47 | def __eq__(self, other: Any) -> bool: 48 | return isinstance(other, BigIntValue) and self.value == other.value 49 | 50 | def __int__(self): 51 | return self.value 52 | -------------------------------------------------------------------------------- /multiversx_sdk/abi/biguint_value.py: -------------------------------------------------------------------------------- 1 | import io 2 | from typing import Any 3 | 4 | from multiversx_sdk.abi.shared import decode_length, encode_length, read_bytes_exactly 5 | from multiversx_sdk.core.constants import INTEGER_MAX_NUM_BYTES 6 | 7 | 8 | class BigUIntValue: 9 | def __init__(self, value: int = 0) -> None: 10 | self.value = value 11 | 12 | def encode_nested(self, writer: io.BytesIO): 13 | data = self._unsigned_to_bytes() 14 | encode_length(writer, len(data)) 15 | writer.write(data) 16 | 17 | def encode_top_level(self, writer: io.BytesIO): 18 | data = self._unsigned_to_bytes() 19 | writer.write(data) 20 | 21 | def decode_nested(self, reader: io.BytesIO): 22 | length = decode_length(reader) 23 | data = read_bytes_exactly(reader, length) 24 | self.value = self._unsigned_from_bytes(data) 25 | 26 | def decode_top_level(self, data: bytes): 27 | self.value = self._unsigned_from_bytes(data) 28 | 29 | def _unsigned_to_bytes(self) -> bytes: 30 | value = self.value 31 | 32 | if value == 0: 33 | return b"" 34 | 35 | data = value.to_bytes(INTEGER_MAX_NUM_BYTES, byteorder="big", signed=False) 36 | data = data.lstrip(bytes([0])) 37 | return data 38 | 39 | def _unsigned_from_bytes(self, data: bytes) -> int: 40 | return int.from_bytes(data, byteorder="big", signed=False) 41 | 42 | def set_payload(self, value: Any): 43 | self.value = int(value) 44 | 45 | def get_payload(self) -> Any: 46 | return self.value 47 | 48 | def __eq__(self, other: Any) -> bool: 49 | return isinstance(other, BigUIntValue) and self.value == other.value 50 | 51 | def __int__(self): 52 | return self.value 53 | -------------------------------------------------------------------------------- /multiversx_sdk/abi/bool_value.py: -------------------------------------------------------------------------------- 1 | import io 2 | from typing import Any 3 | 4 | from multiversx_sdk.abi.constants import FALS_AS_BYTE, TRUE_AS_BYTE 5 | from multiversx_sdk.abi.shared import read_bytes_exactly 6 | 7 | 8 | class BoolValue: 9 | def __init__(self, value: bool = False) -> None: 10 | self.value = value 11 | 12 | def encode_nested(self, writer: io.BytesIO): 13 | if self.value: 14 | writer.write(bytes([TRUE_AS_BYTE])) 15 | return 16 | 17 | writer.write(bytes([FALS_AS_BYTE])) 18 | 19 | def encode_top_level(self, writer: io.BytesIO): 20 | if self.value: 21 | writer.write(bytes([TRUE_AS_BYTE])) 22 | 23 | # For "false", write nothing. 24 | 25 | def decode_nested(self, reader: io.BytesIO): 26 | data = read_bytes_exactly(reader, 1) 27 | self.value = self._byte_to_bool(data[0]) 28 | 29 | def decode_top_level(self, data: bytes): 30 | if len(data) == 0: 31 | self.value = False 32 | return 33 | 34 | if len(data) == 1: 35 | self.value = self._byte_to_bool(data[0]) 36 | return 37 | 38 | raise ValueError(f"unexpected boolean value: {data}") 39 | 40 | def _byte_to_bool(self, data: int) -> bool: 41 | if data == TRUE_AS_BYTE: 42 | return True 43 | 44 | if data == FALS_AS_BYTE: 45 | return False 46 | 47 | raise ValueError(f"unexpected boolean value: {data}") 48 | 49 | def set_payload(self, value: Any): 50 | self.value = bool(value) 51 | 52 | def get_payload(self) -> Any: 53 | return self.value 54 | 55 | def __eq__(self, other: Any) -> bool: 56 | return isinstance(other, BoolValue) and self.value == other.value 57 | 58 | def __bool__(self) -> bool: 59 | return self.value 60 | -------------------------------------------------------------------------------- /multiversx_sdk/abi/bytes_value.py: -------------------------------------------------------------------------------- 1 | import io 2 | from typing import Any, cast 3 | 4 | from multiversx_sdk.abi.shared import decode_length, encode_length, read_bytes_exactly 5 | 6 | 7 | class BytesValue: 8 | def __init__(self, value: bytes = b"") -> None: 9 | self.value = value 10 | 11 | def encode_nested(self, writer: io.BytesIO): 12 | encode_length(writer, len(self.value)) 13 | writer.write(self.value) 14 | 15 | def encode_top_level(self, writer: io.BytesIO): 16 | writer.write(self.value) 17 | 18 | def decode_nested(self, reader: io.BytesIO): 19 | length = decode_length(reader) 20 | data = read_bytes_exactly(reader, length) 21 | self.value = data 22 | 23 | def decode_top_level(self, data: bytes): 24 | self.value = data 25 | 26 | def set_payload(self, value: Any): 27 | if isinstance(value, str): 28 | self.value = bytes(value, "utf-8") 29 | elif isinstance(value, dict): 30 | value = cast(dict[str, str], value) 31 | self.value = self._extract_value_from_dict(value) 32 | else: 33 | self.value = bytes(value) 34 | 35 | def _extract_value_from_dict(self, value: dict[str, str]) -> bytes: 36 | hex_value = value.get("hex", None) 37 | 38 | if not hex_value: 39 | raise ValueError("cannot get value from dictionary: missing 'hex' key") 40 | 41 | return bytes.fromhex(hex_value) 42 | 43 | def get_payload(self) -> Any: 44 | return self.value 45 | 46 | def __eq__(self, other: Any) -> bool: 47 | return isinstance(other, BytesValue) and self.value == other.value 48 | 49 | def __bytes__(self) -> bytes: 50 | return self.value 51 | -------------------------------------------------------------------------------- /multiversx_sdk/abi/bytes_value_test.py: -------------------------------------------------------------------------------- 1 | from types import SimpleNamespace 2 | 3 | import pytest 4 | 5 | from multiversx_sdk.abi.bytes_value import BytesValue 6 | 7 | 8 | def test_set_payload_and_get_payload(): 9 | # Simple 10 | value = BytesValue() 11 | value.set_payload("hello") 12 | assert value.get_payload() == b"hello" 13 | 14 | # Simple 15 | value = BytesValue() 16 | value.set_payload(b"hello") 17 | assert value.get_payload() == b"hello" 18 | 19 | # From dictionary 20 | value = BytesValue() 21 | value.set_payload({"hex": "68656c6c6f"}) 22 | assert value.get_payload() == b"hello" 23 | 24 | # With errors 25 | with pytest.raises(TypeError, match="cannot convert 'types.SimpleNamespace' object to bytes"): 26 | BytesValue().set_payload(SimpleNamespace(a=1, b=2, c=3)) 27 | 28 | with pytest.raises(ValueError, match="cannot get value from dictionary: missing 'hex' key"): 29 | BytesValue().set_payload({}) 30 | -------------------------------------------------------------------------------- /multiversx_sdk/abi/code_metadata_value.py: -------------------------------------------------------------------------------- 1 | import io 2 | from typing import Any, cast 3 | 4 | from multiversx_sdk.abi.shared import read_bytes_exactly 5 | from multiversx_sdk.core.code_metadata import CODE_METADATA_LENGTH, CodeMetadata 6 | 7 | 8 | class CodeMetadataValue: 9 | def __init__(self, value: bytes = b"") -> None: 10 | self.value = value 11 | 12 | @classmethod 13 | def new_from_code_metadata(cls, code_metadata: CodeMetadata) -> "CodeMetadataValue": 14 | return cls(code_metadata.serialize()) 15 | 16 | def encode_nested(self, writer: io.BytesIO): 17 | writer.write(self.value) 18 | 19 | def encode_top_level(self, writer: io.BytesIO): 20 | writer.write(self.value) 21 | 22 | def decode_nested(self, reader: io.BytesIO): 23 | length = CODE_METADATA_LENGTH 24 | data = read_bytes_exactly(reader, length) 25 | self.value = data 26 | 27 | def decode_top_level(self, data: bytes): 28 | self.value = data 29 | 30 | def set_payload(self, value: Any): 31 | if isinstance(value, bytes): 32 | self.value = CodeMetadata.new_from_bytes(value).serialize() 33 | elif isinstance(value, CodeMetadata): 34 | self.value = value.serialize() 35 | elif isinstance(value, dict): 36 | value = cast(dict[str, str], value) 37 | self.value = self._extract_value_from_dict(value) 38 | else: 39 | raise ValueError( 40 | f"cannot set payload for code metadata (should be either a CodeMetadata, bytes or dict, but got: {type(value)})" 41 | ) 42 | 43 | def _extract_value_from_dict(self, value: dict[str, str]) -> bytes: 44 | hex_value = value.get("hex", None) 45 | 46 | if not hex_value: 47 | raise ValueError("cannot get value from dictionary: missing 'hex' key") 48 | 49 | return bytes.fromhex(hex_value) 50 | 51 | def get_payload(self) -> Any: 52 | return self.value 53 | 54 | def __eq__(self, other: Any) -> bool: 55 | return isinstance(other, CodeMetadataValue) and self.value == other.value 56 | 57 | def __bytes__(self) -> bytes: 58 | return self.value 59 | -------------------------------------------------------------------------------- /multiversx_sdk/abi/code_metadata_value_test.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | import pytest 4 | 5 | from multiversx_sdk.abi.code_metadata_value import CodeMetadataValue 6 | from multiversx_sdk.core.code_metadata import CodeMetadata 7 | 8 | 9 | def test_new_from_code_metadata(): 10 | value = CodeMetadataValue.new_from_code_metadata(CodeMetadata()) 11 | assert value.get_payload() == bytes([0x05, 0x00]) 12 | 13 | value = CodeMetadataValue.new_from_code_metadata( 14 | CodeMetadata(upgradeable=True, readable=True, payable=True, payable_by_contract=True) 15 | ) 16 | assert value.get_payload() == bytes([0x05, 0x06]) 17 | 18 | 19 | def test_set_payload_and_get_payload(): 20 | # Simple 21 | value = CodeMetadataValue() 22 | value.set_payload(bytes([0x05, 0x00])) 23 | assert value.get_payload() == bytes([0x05, 0x00]) 24 | 25 | # With CodeMetadata 26 | value = CodeMetadataValue() 27 | value.set_payload(CodeMetadata(upgradeable=True, readable=True, payable=True, payable_by_contract=True)) 28 | assert value.get_payload() == bytes([0x05, 0x06]) 29 | 30 | # From dictionary 31 | value = CodeMetadataValue() 32 | value.set_payload({"hex": "0500"}) 33 | assert value.get_payload() == bytes([0x05, 0x00]) 34 | 35 | # With errors 36 | with pytest.raises( 37 | ValueError, 38 | match=re.escape( 39 | "cannot set payload for code metadata (should be either a CodeMetadata, bytes or dict, but got: )" 40 | ), 41 | ): 42 | CodeMetadataValue().set_payload(5) 43 | 44 | with pytest.raises(ValueError, match="code metadata buffer has length 4, expected 2"): 45 | CodeMetadataValue().set_payload(bytes([0, 1, 2, 3])) 46 | -------------------------------------------------------------------------------- /multiversx_sdk/abi/codec.py: -------------------------------------------------------------------------------- 1 | import io 2 | 3 | from multiversx_sdk.abi.interface import ISingleValue 4 | 5 | 6 | class Codec: 7 | def __init__(self) -> None: 8 | pass 9 | 10 | def encode_nested(self, value: ISingleValue) -> bytes: 11 | buffer = io.BytesIO() 12 | value.encode_nested(buffer) 13 | return buffer.getvalue() 14 | 15 | def encode_top_level(self, value: ISingleValue) -> bytes: 16 | buffer = io.BytesIO() 17 | value.encode_top_level(buffer) 18 | return buffer.getvalue() 19 | 20 | def decode_nested(self, data: bytes, value: ISingleValue) -> None: 21 | reader = io.BytesIO(data) 22 | 23 | try: 24 | value.decode_nested(reader) 25 | except ValueError as e: 26 | raise ValueError(f"cannot decode (nested) {type(value)}, because of: {e}") 27 | 28 | def decode_top_level(self, data: bytes, value: ISingleValue) -> None: 29 | try: 30 | value.decode_top_level(data) 31 | except ValueError as e: 32 | raise ValueError(f"cannot decode (top-level) {type(value).__name__}, because of: {e}") 33 | -------------------------------------------------------------------------------- /multiversx_sdk/abi/constants.py: -------------------------------------------------------------------------------- 1 | TRUE_AS_BYTE = 1 2 | FALS_AS_BYTE = 0 3 | OPTION_MARKER_FOR_ABSENT_VALUE = 0 4 | OPTION_MARKER_FOR_PRESENT_VALUE = 1 5 | NUM_BYTES_IN_64_BITS = 8 6 | INTEGER_MAX_NUM_BYTES = 64 7 | STRUCT_PACKING_FORMAT_FOR_UINT32 = ">I" 8 | ENUM_DISCRIMINANT_FIELD_NAME = "__discriminant__" 9 | ENUM_NAME_FIELD_NAME = "__name__" 10 | U32_SIZE_IN_BYTES = 4 11 | LOCAL_CONTEXT_PRECISION_FOR_DECIMAL = 256 12 | -------------------------------------------------------------------------------- /multiversx_sdk/abi/counted_variadic_values.py: -------------------------------------------------------------------------------- 1 | from typing import Any, Callable, Optional, Union 2 | 3 | from multiversx_sdk.abi.interface import IPayloadHolder, ISingleValue 4 | from multiversx_sdk.abi.multi_value import MultiValue 5 | from multiversx_sdk.abi.shared import convert_native_value_to_list 6 | 7 | 8 | class CountedVariadicValues(IPayloadHolder): 9 | def __init__( 10 | self, 11 | items: Optional[list[Union[ISingleValue, MultiValue]]] = None, 12 | item_creator: Optional[Callable[[], Union[ISingleValue, MultiValue]]] = None, 13 | ) -> None: 14 | self.items = items or [] 15 | self.length = len(self.items) 16 | 17 | self.item_creator = item_creator 18 | 19 | def set_payload(self, value: Any): 20 | if not self.item_creator: 21 | raise ValueError("populating variadic values from a native object requires the item creator to be set") 22 | 23 | native_items, _ = convert_native_value_to_list(value) 24 | self.length = len(native_items) 25 | 26 | self.items.clear() 27 | 28 | for native_item in native_items: 29 | item = self.item_creator() 30 | item.set_payload(native_item) 31 | self.items.append(item) 32 | 33 | def get_payload(self) -> Any: 34 | return [item.get_payload() for item in self.items] 35 | 36 | def __eq__(self, other: Any) -> bool: 37 | return ( 38 | isinstance(other, CountedVariadicValues) 39 | and self.items == other.items 40 | and self.item_creator == other.item_creator 41 | ) 42 | 43 | def __iter__(self) -> Any: 44 | return iter(self.items) 45 | -------------------------------------------------------------------------------- /multiversx_sdk/abi/counted_variadic_values_test.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | import pytest 4 | 5 | from multiversx_sdk.abi.counted_variadic_values import CountedVariadicValues 6 | from multiversx_sdk.abi.multi_value import MultiValue 7 | from multiversx_sdk.abi.small_int_values import U32Value 8 | from multiversx_sdk.abi.string_value import StringValue 9 | 10 | 11 | def test_set_payload_and_get_payload(): 12 | # Simple 13 | values = CountedVariadicValues(item_creator=lambda: U32Value()) 14 | values.set_payload([1, 2, 3]) 15 | 16 | assert values.length == 3 17 | assert values.items == [U32Value(1), U32Value(2), U32Value(3)] 18 | assert values.get_payload() == [1, 2, 3] 19 | 20 | # Nested 21 | values = CountedVariadicValues(item_creator=lambda: MultiValue([U32Value(), StringValue()])) 22 | values.set_payload([[42, "hello"], [43, "world"]]) 23 | 24 | assert values.length == 2 25 | assert values.items == [ 26 | MultiValue([U32Value(42), StringValue("hello")]), 27 | MultiValue([U32Value(43), StringValue("world")]), 28 | ] 29 | 30 | assert values.get_payload() == [[42, "hello"], [43, "world"]] 31 | 32 | # With errors 33 | with pytest.raises( 34 | ValueError, 35 | match="populating variadic values from a native object requires the item creator to be set", 36 | ): 37 | CountedVariadicValues().set_payload([1, 2, 3]) 38 | 39 | # With errors 40 | with pytest.raises(ValueError, match=re.escape("invalid literal for int() with base 10: 'foo'")): 41 | values = CountedVariadicValues(item_creator=lambda: U32Value()) 42 | values.set_payload(["foo"]) 43 | -------------------------------------------------------------------------------- /multiversx_sdk/abi/explicit_enum_value.py: -------------------------------------------------------------------------------- 1 | from typing import Any 2 | 3 | from multiversx_sdk.abi.string_value import StringValue 4 | 5 | 6 | class ExplicitEnumValue(StringValue): 7 | def __init__(self, value: str = "") -> None: 8 | self.value = value 9 | 10 | def __eq__(self, other: Any) -> bool: 11 | return isinstance(other, ExplicitEnumValue) and self.value == other.value 12 | -------------------------------------------------------------------------------- /multiversx_sdk/abi/fields.py: -------------------------------------------------------------------------------- 1 | import io 2 | from typing import Any 3 | 4 | from multiversx_sdk.abi.interface import ISingleValue 5 | 6 | 7 | class Field: 8 | def __init__(self, name: str, value: ISingleValue) -> None: 9 | self.name = name 10 | self.value = value 11 | 12 | def set_payload(self, value: Any): 13 | self.value.set_payload(value) 14 | 15 | def get_payload(self) -> Any: 16 | return self.value.get_payload() 17 | 18 | def __eq__(self, other: Any) -> bool: 19 | return isinstance(other, Field) and self.name == other.name and self.value == other.value 20 | 21 | 22 | def encode_fields_nested(fields: list[Field], writer: io.BytesIO): 23 | for field in fields: 24 | try: 25 | field.value.encode_nested(writer) 26 | except Exception as e: 27 | raise Exception(f"cannot encode field '{field.name}', because of: {e}") 28 | 29 | 30 | def decode_fields_nested(fields: list[Field], reader: io.BytesIO): 31 | for field in fields: 32 | try: 33 | field.value.decode_nested(reader) 34 | except Exception as e: 35 | raise Exception(f"cannot decode field '{field.name}', because of: {e}") 36 | 37 | 38 | def set_fields_from_dictionary(fields: list[Field], dictionary: dict[str, Any]): 39 | for field in fields: 40 | if field.name not in dictionary: 41 | raise ValueError(f"the dictionary is missing the key '{field.name}'") 42 | 43 | field_payload = dictionary[field.name] 44 | 45 | try: 46 | field.set_payload(field_payload) 47 | except Exception as error: 48 | raise ValueError(f"cannot set payload for field '{field.name}', because of: {error}") 49 | 50 | 51 | def set_fields_from_list(fields: list[Field], items: list[Any]): 52 | if len(fields) != len(items): 53 | raise ValueError( 54 | f"the number of fields ({len(fields)}) does not match the number of provided items ({len(items)})" 55 | ) 56 | 57 | for index, field in enumerate(fields): 58 | field_payload = items[index] 59 | 60 | try: 61 | field.set_payload(field_payload) 62 | except Exception as error: 63 | raise ValueError(f"cannot set payload for field '{field.name}', because of: {error}") 64 | -------------------------------------------------------------------------------- /multiversx_sdk/abi/fields_test.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | import pytest 4 | 5 | from multiversx_sdk.abi.fields import ( 6 | Field, 7 | set_fields_from_dictionary, 8 | set_fields_from_list, 9 | ) 10 | from multiversx_sdk.abi.small_int_values import U32Value 11 | 12 | 13 | def test_set_fields_from_dictionary(): 14 | # Basic 15 | fields = [Field("a", U32Value()), Field("b", U32Value()), Field("c", U32Value())] 16 | 17 | set_fields_from_dictionary(fields, {"a": 1, "b": 2, "c": 3}) 18 | 19 | assert fields[0].value.get_payload() == 1 20 | assert fields[1].value.get_payload() == 2 21 | assert fields[2].value.get_payload() == 3 22 | 23 | # Missing field 24 | with pytest.raises(ValueError, match="the dictionary is missing the key 'c'"): 25 | set_fields_from_dictionary(fields, {"a": 1, "b": 2}) 26 | 27 | # Bad type 28 | with pytest.raises( 29 | ValueError, 30 | match=re.escape( 31 | "cannot set payload for field 'a', because of: invalid literal for int() with base 10: 'foobar'" 32 | ), 33 | ): 34 | set_fields_from_dictionary(fields, {"a": "foobar", "b": 2, "c": 3}) 35 | 36 | 37 | def test_set_fields_from_list(): 38 | # Basic 39 | fields = [Field("a", U32Value()), Field("b", U32Value()), Field("c", U32Value())] 40 | 41 | set_fields_from_list(fields, [1, 2, 3]) 42 | 43 | assert fields[0].value.get_payload() == 1 44 | assert fields[1].value.get_payload() == 2 45 | assert fields[2].value.get_payload() == 3 46 | 47 | # Missing field 48 | with pytest.raises( 49 | ValueError, 50 | match=re.escape("the number of fields (3) does not match the number of provided items (2)"), 51 | ): 52 | set_fields_from_list(fields, [1, 2]) 53 | 54 | # Bad type 55 | with pytest.raises( 56 | ValueError, 57 | match=re.escape( 58 | "cannot set payload for field 'a', because of: invalid literal for int() with base 10: 'foobar'" 59 | ), 60 | ): 61 | set_fields_from_list(fields, ["foobar", 2, 3]) 62 | -------------------------------------------------------------------------------- /multiversx_sdk/abi/interface.py: -------------------------------------------------------------------------------- 1 | import io 2 | from typing import Any, Protocol, runtime_checkable 3 | 4 | 5 | class IPayloadHolder(Protocol): 6 | def set_payload(self, value: Any): ... 7 | 8 | def get_payload(self) -> Any: ... 9 | 10 | 11 | @runtime_checkable 12 | class ISingleValue(IPayloadHolder, Protocol): 13 | def encode_nested(self, writer: io.BytesIO): ... 14 | 15 | def encode_top_level(self, writer: io.BytesIO): ... 16 | 17 | def decode_nested(self, reader: io.BytesIO): ... 18 | 19 | def decode_top_level(self, data: bytes): ... 20 | -------------------------------------------------------------------------------- /multiversx_sdk/abi/list_value.py: -------------------------------------------------------------------------------- 1 | import io 2 | from typing import Any, Callable, Optional 3 | 4 | from multiversx_sdk.abi.interface import ISingleValue 5 | from multiversx_sdk.abi.shared import ( 6 | convert_native_value_to_list, 7 | decode_length, 8 | encode_length, 9 | ) 10 | 11 | 12 | class ListValue: 13 | def __init__( 14 | self, 15 | items: Optional[list[ISingleValue]] = None, 16 | item_creator: Optional[Callable[[], ISingleValue]] = None, 17 | ) -> None: 18 | self.items = items or [] 19 | self.item_creator = item_creator 20 | 21 | def encode_nested(self, writer: io.BytesIO): 22 | encode_length(writer, len(self.items)) 23 | self._encode_list_items(writer) 24 | 25 | def encode_top_level(self, writer: io.BytesIO): 26 | self._encode_list_items(writer) 27 | 28 | def decode_nested(self, reader: io.BytesIO): 29 | length = decode_length(reader) 30 | 31 | self.items = [] 32 | for _ in range(length): 33 | self._decode_list_item(reader) 34 | 35 | def decode_top_level(self, data: bytes): 36 | reader = io.BytesIO(data) 37 | self.items = [] 38 | 39 | while reader.tell() < len(data): 40 | self._decode_list_item(reader) 41 | 42 | def _encode_list_items(self, writer: io.BytesIO): 43 | for item in self.items: 44 | item.encode_nested(writer) 45 | 46 | def _decode_list_item(self, reader: io.BytesIO): 47 | if self.item_creator is None: 48 | raise Exception("cannot decode list: item creator is None") 49 | 50 | new_item = self.item_creator() 51 | new_item.decode_nested(reader) 52 | self.items.append(new_item) 53 | 54 | def set_payload(self, value: Any): 55 | if not self.item_creator: 56 | raise ValueError("populating a list from a native object requires the item creator to be set") 57 | 58 | native_items, _ = convert_native_value_to_list(value) 59 | 60 | self.items.clear() 61 | 62 | for native_item in native_items: 63 | item = self.item_creator() 64 | item.set_payload(native_item) 65 | self.items.append(item) 66 | 67 | def get_payload(self) -> Any: 68 | return [item.get_payload() for item in self.items] 69 | 70 | def __eq__(self, other: Any) -> bool: 71 | return isinstance(other, ListValue) and self.items == other.items and self.item_creator == other.item_creator 72 | 73 | def __iter__(self) -> Any: 74 | return iter(self.items) 75 | -------------------------------------------------------------------------------- /multiversx_sdk/abi/list_value_test.py: -------------------------------------------------------------------------------- 1 | from types import SimpleNamespace 2 | 3 | import pytest 4 | 5 | from multiversx_sdk.abi.biguint_value import BigUIntValue 6 | from multiversx_sdk.abi.fields import Field 7 | from multiversx_sdk.abi.list_value import ListValue 8 | from multiversx_sdk.abi.small_int_values import U32Value 9 | from multiversx_sdk.abi.struct_value import StructValue 10 | 11 | 12 | def test_set_payload_and_get_payload(): 13 | # Simple 14 | value = ListValue(item_creator=lambda: U32Value()) 15 | value.set_payload([1, 2, 3]) 16 | assert value.items == [U32Value(1), U32Value(2), U32Value(3)] 17 | assert value.get_payload() == [1, 2, 3] 18 | 19 | # Simple 20 | value = ListValue(item_creator=lambda: BigUIntValue()) 21 | value.set_payload(range(4, 7)) 22 | assert value.items == [BigUIntValue(4), BigUIntValue(5), BigUIntValue(6)] 23 | assert value.get_payload() == [4, 5, 6] 24 | 25 | # Nested (with recursion) 26 | value = ListValue(item_creator=lambda: StructValue([Field("a", U32Value()), Field("b", BigUIntValue())])) 27 | 28 | value.set_payload([{"a": 1, "b": 2}, {"a": 3, "b": 4}, {"a": 5, "b": 6}]) 29 | 30 | assert value.items == [ 31 | StructValue([Field("a", U32Value(1)), Field("b", BigUIntValue(2))]), 32 | StructValue([Field("a", U32Value(3)), Field("b", BigUIntValue(4))]), 33 | StructValue([Field("a", U32Value(5)), Field("b", BigUIntValue(6))]), 34 | ] 35 | 36 | assert value.get_payload() == [ 37 | SimpleNamespace(a=1, b=2), 38 | SimpleNamespace(a=3, b=4), 39 | SimpleNamespace(a=5, b=6), 40 | ] 41 | 42 | # With errors 43 | with pytest.raises( 44 | ValueError, 45 | match="populating a list from a native object requires the item creator to be set", 46 | ): 47 | value = ListValue() 48 | value.set_payload([1, 2, 3]) 49 | 50 | # With errors 51 | with pytest.raises( 52 | ValueError, 53 | match="cannot convert native value to list, because of: 'int' object is not iterable", 54 | ): 55 | value = ListValue(item_creator=lambda: U32Value()) 56 | value.set_payload(42) 57 | -------------------------------------------------------------------------------- /multiversx_sdk/abi/multi_value.py: -------------------------------------------------------------------------------- 1 | from typing import Any, Union 2 | 3 | from multiversx_sdk.abi.interface import IPayloadHolder, ISingleValue 4 | from multiversx_sdk.abi.shared import convert_native_value_to_list 5 | 6 | 7 | class MultiValue(IPayloadHolder): 8 | def __init__(self, items: list[Union[ISingleValue, "MultiValue"]]): 9 | self.items = items 10 | 11 | def set_payload(self, value: Any): 12 | native_items, _ = convert_native_value_to_list(value) 13 | 14 | if len(value) != len(self.items): 15 | raise ValueError(f"for multi-value, expected {len(self.items)} items, got {len(value)}") 16 | 17 | for item, native_item in zip(self.items, native_items): 18 | item.set_payload(native_item) 19 | 20 | def get_payload(self) -> Any: 21 | return [item.get_payload() for item in self.items] 22 | 23 | def __eq__(self, other: Any) -> bool: 24 | return isinstance(other, MultiValue) and self.items == other.items 25 | 26 | def __iter__(self) -> Any: 27 | return iter(self.items) 28 | -------------------------------------------------------------------------------- /multiversx_sdk/abi/multi_value_test.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from multiversx_sdk.abi.multi_value import MultiValue 4 | from multiversx_sdk.abi.small_int_values import U32Value 5 | from multiversx_sdk.abi.string_value import StringValue 6 | 7 | 8 | def test_set_payload_and_get_payload(): 9 | # Simple 10 | value = MultiValue( 11 | [ 12 | U32Value(), 13 | StringValue(), 14 | ] 15 | ) 16 | 17 | value.set_payload([1, "hello"]) 18 | 19 | assert value.items == [U32Value(1), StringValue("hello")] 20 | assert value.get_payload() == [1, "hello"] 21 | 22 | # Nested 23 | value = MultiValue( 24 | [ 25 | StringValue(), 26 | StringValue(), 27 | MultiValue( 28 | [ 29 | U32Value(), 30 | StringValue(), 31 | ] 32 | ), 33 | ] 34 | ) 35 | 36 | value.set_payload(["a", "b", [42, "hello"]]) 37 | 38 | assert value.items == [ 39 | StringValue("a"), 40 | StringValue("b"), 41 | MultiValue([U32Value(42), StringValue("hello")]), 42 | ] 43 | 44 | assert value.get_payload() == ["a", "b", [42, "hello"]] 45 | 46 | # With errors 47 | with pytest.raises( 48 | ValueError, 49 | match="cannot convert native value to list, because of: 'int' object is not iterable", 50 | ): 51 | value = MultiValue([U32Value(), StringValue()]) 52 | value.set_payload(42) 53 | 54 | # With errors 55 | with pytest.raises(ValueError, match="for multi-value, expected 2 items, got 3"): 56 | value = MultiValue([U32Value(), StringValue()]) 57 | value.set_payload([42, "hello", "world"]) 58 | -------------------------------------------------------------------------------- /multiversx_sdk/abi/option_value_test.py: -------------------------------------------------------------------------------- 1 | from types import SimpleNamespace 2 | 3 | import pytest 4 | 5 | from multiversx_sdk.abi.biguint_value import BigUIntValue 6 | from multiversx_sdk.abi.fields import Field 7 | from multiversx_sdk.abi.option_value import OptionValue 8 | from multiversx_sdk.abi.small_int_values import U32Value 9 | from multiversx_sdk.abi.struct_value import StructValue 10 | 11 | 12 | def test_set_payload_and_get_payload(): 13 | # Simple (provided) 14 | value = OptionValue(U32Value()) 15 | value.set_payload(42) 16 | assert value.get_payload() == 42 17 | 18 | # Simple (missing) 19 | value = OptionValue(U32Value()) 20 | value.set_payload(None) 21 | assert value.get_payload() is None 22 | 23 | # Nested 24 | value = OptionValue(StructValue([Field("a", U32Value()), Field("b", BigUIntValue())])) 25 | 26 | value.set_payload({"a": 41, "b": 42}) 27 | assert value.get_payload() == SimpleNamespace(a=41, b=42) 28 | 29 | # With errors 30 | with pytest.raises( 31 | ValueError, 32 | match="placeholder value of option should be set before calling set_payload", 33 | ): 34 | OptionValue().set_payload(42) 35 | -------------------------------------------------------------------------------- /multiversx_sdk/abi/optional_value.py: -------------------------------------------------------------------------------- 1 | from typing import Any, Optional, Union 2 | 3 | from multiversx_sdk.abi.interface import IPayloadHolder, ISingleValue 4 | from multiversx_sdk.abi.multi_value import MultiValue 5 | 6 | 7 | class OptionalValue(IPayloadHolder): 8 | def __init__(self, value: Optional[Union[ISingleValue, MultiValue]] = None): 9 | self.value = value 10 | 11 | def set_payload(self, value: Any): 12 | if value is None: 13 | self.value = None 14 | return 15 | 16 | if self.value is None: 17 | raise ValueError("placeholder value of optional should be set before calling set_payload") 18 | 19 | if isinstance(value, OptionalValue): 20 | self.value = value.value 21 | return 22 | 23 | self.value.set_payload(value) 24 | 25 | def get_payload(self) -> Any: 26 | if self.value is None: 27 | return None 28 | 29 | return self.value.get_payload() 30 | 31 | def __eq__(self, other: Any) -> bool: 32 | return isinstance(other, OptionalValue) and self.value == other.value 33 | -------------------------------------------------------------------------------- /multiversx_sdk/abi/optional_value_test.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from multiversx_sdk.abi.multi_value import MultiValue 4 | from multiversx_sdk.abi.optional_value import OptionalValue 5 | from multiversx_sdk.abi.small_int_values import U32Value 6 | from multiversx_sdk.abi.string_value import StringValue 7 | 8 | 9 | def test_set_payload_and_get_payload(): 10 | # Simple 11 | values = OptionalValue(U32Value()) 12 | values.set_payload(42) 13 | 14 | assert values.value == U32Value(42) 15 | assert values.get_payload() == 42 16 | 17 | # Nested 18 | values = OptionalValue(MultiValue([U32Value(), StringValue()])) 19 | values.set_payload([42, "hello"]) 20 | 21 | assert values.value == MultiValue([U32Value(42), StringValue("hello")]) 22 | assert values.get_payload() == [42, "hello"] 23 | 24 | # With errors 25 | with pytest.raises( 26 | ValueError, 27 | match="placeholder value of optional should be set before calling set_payload", 28 | ): 29 | OptionalValue().set_payload(42) 30 | -------------------------------------------------------------------------------- /multiversx_sdk/abi/shared.py: -------------------------------------------------------------------------------- 1 | import io 2 | import struct 3 | from typing import Any, Tuple 4 | 5 | from multiversx_sdk.abi.constants import STRUCT_PACKING_FORMAT_FOR_UINT32 6 | 7 | 8 | def encode_length(writer: io.BytesIO, length: int): 9 | bytes = struct.pack(STRUCT_PACKING_FORMAT_FOR_UINT32, length) 10 | writer.write(bytes) 11 | 12 | 13 | def decode_length(reader: io.BytesIO) -> int: 14 | bytes = read_bytes_exactly(reader, 4) 15 | (length,) = struct.unpack(STRUCT_PACKING_FORMAT_FOR_UINT32, bytes) 16 | return length 17 | 18 | 19 | def read_bytes_exactly(reader: io.BytesIO, num_bytes: int): 20 | if num_bytes == 0: 21 | return b"" 22 | 23 | data = reader.read(num_bytes) 24 | if len(data) != num_bytes: 25 | raise ValueError(f"cannot read exactly {num_bytes} bytes") 26 | 27 | return data 28 | 29 | 30 | def convert_native_value_to_dictionary(obj: Any, raise_on_failure: bool = True) -> Tuple[dict[str, Any], bool]: 31 | try: 32 | return dict(obj), True 33 | except Exception as error: 34 | error_on_dict_constructor = error 35 | 36 | try: 37 | return obj.__dict__, True 38 | except Exception as error: 39 | error_on_dict_attribute = error 40 | 41 | if raise_on_failure: 42 | raise ValueError( 43 | f"cannot convert native value to dictionary, because of: {error_on_dict_constructor} and {error_on_dict_attribute}" 44 | ) 45 | 46 | return {}, False 47 | 48 | 49 | def convert_native_value_to_list(obj: Any, raise_on_failure: bool = True) -> Tuple[list[Any], bool]: 50 | if isinstance(obj, dict): 51 | raise ValueError("cannot properly convert dictionary to list") 52 | 53 | try: 54 | return list(obj), True 55 | except Exception as error: 56 | if raise_on_failure: 57 | raise ValueError(f"cannot convert native value to list, because of: {error}") 58 | 59 | return [], False 60 | -------------------------------------------------------------------------------- /multiversx_sdk/abi/shared_test.py: -------------------------------------------------------------------------------- 1 | from types import SimpleNamespace 2 | 3 | import pytest 4 | 5 | from multiversx_sdk.abi.shared import ( 6 | convert_native_value_to_dictionary, 7 | convert_native_value_to_list, 8 | ) 9 | 10 | 11 | def test_convert_native_value_to_dictionary(): 12 | # Simple namespace 13 | dictionary, ok = convert_native_value_to_dictionary(SimpleNamespace(a=1, b=2, c=3)) 14 | assert dictionary == {"a": 1, "b": 2, "c": 3} 15 | assert ok 16 | 17 | # Dictionary 18 | dictionary, ok = convert_native_value_to_dictionary({"a": 1, "b": 2, "c": 3}) 19 | assert dictionary == {"a": 1, "b": 2, "c": 3} 20 | assert ok 21 | 22 | # With errors 23 | dictionary, ok = convert_native_value_to_dictionary(42, raise_on_failure=False) 24 | assert dictionary == {} 25 | assert not ok 26 | 27 | # With errors (raise on failure) 28 | with pytest.raises(ValueError, match="cannot convert native value to dictionary"): 29 | dictionary, ok = convert_native_value_to_dictionary(42) 30 | 31 | with pytest.raises(ValueError, match="cannot convert native value to dictionary"): 32 | dictionary, ok = convert_native_value_to_dictionary([42, 43]) 33 | 34 | 35 | def test_convert_native_value_to_list(): 36 | # List 37 | items, ok = convert_native_value_to_list([1, 2, 3]) 38 | assert items == [1, 2, 3] 39 | assert ok 40 | 41 | # With errors 42 | items, ok = convert_native_value_to_list(42, raise_on_failure=False) 43 | assert items == [] 44 | assert not ok 45 | 46 | # With errors (raise on failure) 47 | with pytest.raises(ValueError, match="cannot properly convert dictionary to list"): 48 | items, ok = convert_native_value_to_list({"a": 1, "b": 2, "c": 3}) 49 | 50 | with pytest.raises(ValueError, match="cannot convert native value to list"): 51 | items, ok = convert_native_value_to_list(42) 52 | 53 | with pytest.raises(ValueError, match="cannot convert native value to list"): 54 | items, ok = convert_native_value_to_list(SimpleNamespace(a=1, b=2, c=3)) 55 | -------------------------------------------------------------------------------- /multiversx_sdk/abi/string_value.py: -------------------------------------------------------------------------------- 1 | import io 2 | from typing import Any 3 | 4 | from multiversx_sdk.abi.shared import decode_length, encode_length, read_bytes_exactly 5 | 6 | 7 | class StringValue: 8 | def __init__(self, value: str = "") -> None: 9 | self.value = value 10 | 11 | def encode_nested(self, writer: io.BytesIO): 12 | encode_length(writer, len(self.value)) 13 | writer.write(self.value.encode("utf-8")) 14 | 15 | def encode_top_level(self, writer: io.BytesIO): 16 | writer.write(self.value.encode("utf-8")) 17 | 18 | def decode_nested(self, reader: io.BytesIO): 19 | length = decode_length(reader) 20 | data = read_bytes_exactly(reader, length) 21 | self.value = data.decode("utf-8") 22 | 23 | def decode_top_level(self, data: bytes): 24 | self.value = data.decode("utf-8") 25 | 26 | def set_payload(self, value: Any): 27 | if isinstance(value, bytes): 28 | self.value = value.decode("utf-8") 29 | elif isinstance(value, str): 30 | self.value = value 31 | elif isinstance(value, StringValue): 32 | self.value = value.value 33 | else: 34 | raise ValueError( 35 | f"cannot set payload for string (should be either a string or bytes, but got: {type(value)})" 36 | ) 37 | 38 | def get_payload(self) -> Any: 39 | return self.value 40 | 41 | def __eq__(self, other: Any) -> bool: 42 | return isinstance(other, StringValue) and self.value == other.value 43 | 44 | def __str__(self) -> str: 45 | return self.value 46 | 47 | def __bytes__(self) -> bytes: 48 | return self.value.encode("utf-8") 49 | -------------------------------------------------------------------------------- /multiversx_sdk/abi/string_value_test.py: -------------------------------------------------------------------------------- 1 | import re 2 | from types import SimpleNamespace 3 | 4 | import pytest 5 | 6 | from multiversx_sdk.abi.string_value import StringValue 7 | 8 | 9 | def test_set_payload_and_get_payload(): 10 | # Simple (string) 11 | value = StringValue() 12 | value.set_payload("hello") 13 | assert value.get_payload() == "hello" 14 | 15 | # Simple (bytes) 16 | value = StringValue() 17 | value.set_payload(b"hello") 18 | assert value.get_payload() == "hello" 19 | 20 | # Simple (StringValue) 21 | value = StringValue() 22 | value.set_payload(StringValue("hello")) 23 | assert value.get_payload() == "hello" 24 | 25 | # With errors 26 | with pytest.raises( 27 | ValueError, 28 | match=re.escape( 29 | "cannot set payload for string (should be either a string or bytes, but got: )" 30 | ), 31 | ): 32 | StringValue().set_payload(SimpleNamespace(a=1, b=2, c=3)) 33 | -------------------------------------------------------------------------------- /multiversx_sdk/abi/struct_value.py: -------------------------------------------------------------------------------- 1 | import io 2 | from types import SimpleNamespace 3 | from typing import Any 4 | 5 | from multiversx_sdk.abi.fields import ( 6 | Field, 7 | decode_fields_nested, 8 | encode_fields_nested, 9 | set_fields_from_dictionary, 10 | set_fields_from_list, 11 | ) 12 | from multiversx_sdk.abi.shared import ( 13 | convert_native_value_to_dictionary, 14 | convert_native_value_to_list, 15 | ) 16 | 17 | 18 | class StructValue: 19 | def __init__(self, fields: list[Field]) -> None: 20 | self.fields = fields 21 | 22 | def encode_nested(self, writer: io.BytesIO): 23 | encode_fields_nested(self.fields, writer) 24 | 25 | def encode_top_level(self, writer: io.BytesIO): 26 | self.encode_nested(writer) 27 | 28 | def decode_nested(self, reader: io.BytesIO): 29 | decode_fields_nested(self.fields, reader) 30 | 31 | def decode_top_level(self, data: bytes): 32 | reader = io.BytesIO(data) 33 | self.decode_nested(reader) 34 | 35 | def set_payload(self, value: Any): 36 | native_dictionary, ok = convert_native_value_to_dictionary(value, raise_on_failure=False) 37 | if ok: 38 | set_fields_from_dictionary(self.fields, native_dictionary) 39 | return 40 | 41 | native_list, ok = convert_native_value_to_list(value, raise_on_failure=False) 42 | if ok: 43 | set_fields_from_list(self.fields, native_list) 44 | return 45 | 46 | raise ValueError("cannot set payload for struct (should be either a dictionary or a list)") 47 | 48 | def get_payload(self) -> Any: 49 | obj = SimpleNamespace() 50 | 51 | for field in self.fields: 52 | setattr(obj, field.name, field.get_payload()) 53 | 54 | return obj 55 | 56 | def __eq__(self, other: Any) -> bool: 57 | return isinstance(other, StructValue) and self.fields == other.fields 58 | 59 | def __iter__(self): 60 | for field in self.fields: 61 | yield (field.name, field.value) 62 | -------------------------------------------------------------------------------- /multiversx_sdk/abi/struct_value_test.py: -------------------------------------------------------------------------------- 1 | import re 2 | from types import SimpleNamespace 3 | 4 | import pytest 5 | 6 | from multiversx_sdk.abi.biguint_value import BigUIntValue 7 | from multiversx_sdk.abi.fields import Field 8 | from multiversx_sdk.abi.small_int_values import U32Value 9 | from multiversx_sdk.abi.struct_value import StructValue 10 | 11 | 12 | def test_set_payload_and_get_payload(): 13 | # With errors 14 | with pytest.raises( 15 | ValueError, 16 | match=re.escape("cannot set payload for struct (should be either a dictionary or a list)"), 17 | ): 18 | StructValue([]).set_payload(42) 19 | 20 | value = StructValue([Field("a", U32Value()), Field("b", BigUIntValue())]) 21 | 22 | # From list 23 | value.set_payload([39, 40]) 24 | assert value.fields == [Field("a", U32Value(39)), Field("b", BigUIntValue(40))] 25 | assert value.get_payload() == SimpleNamespace(a=39, b=40) 26 | 27 | # From SimpleNamespace 28 | value.set_payload(SimpleNamespace(a=41, b=42)) 29 | assert value.fields == [Field("a", U32Value(41)), Field("b", BigUIntValue(42))] 30 | assert value.get_payload() == SimpleNamespace(a=41, b=42) 31 | 32 | class Payload: 33 | def __init__(self, a: int, b: int): 34 | self.a = a 35 | self.b = b 36 | 37 | # Then, from object 38 | value.set_payload(Payload(43, 44)) 39 | assert value.fields == [Field("a", U32Value(43)), Field("b", BigUIntValue(44))] 40 | assert value.get_payload() == SimpleNamespace(a=43, b=44) 41 | 42 | # Then, from dictionary 43 | value.set_payload({"a": 45, "b": 46}) 44 | assert value.fields == [Field("a", U32Value(45)), Field("b", BigUIntValue(46))] 45 | assert value.get_payload() == SimpleNamespace(a=45, b=46) 46 | -------------------------------------------------------------------------------- /multiversx_sdk/abi/token_identifier_value.py: -------------------------------------------------------------------------------- 1 | from typing import Any 2 | 3 | from multiversx_sdk.abi.string_value import StringValue 4 | 5 | 6 | class TokenIdentifierValue(StringValue): 7 | def __init__(self, value: str = "") -> None: 8 | self.value = value 9 | 10 | def __eq__(self, other: Any) -> bool: 11 | return isinstance(other, TokenIdentifierValue) and self.value == other.value 12 | -------------------------------------------------------------------------------- /multiversx_sdk/abi/tuple_value.py: -------------------------------------------------------------------------------- 1 | import io 2 | from typing import Any 3 | 4 | from multiversx_sdk.abi.interface import ISingleValue 5 | from multiversx_sdk.abi.shared import convert_native_value_to_list 6 | 7 | 8 | class TupleValue: 9 | def __init__(self, fields: list[ISingleValue]) -> None: 10 | self.fields = fields 11 | 12 | def encode_nested(self, writer: io.BytesIO): 13 | for i, field in enumerate(self.fields): 14 | try: 15 | field.encode_nested(writer) 16 | except Exception as e: 17 | raise Exception(f"cannot encode field '{i}' of tuple, because of: {e}") 18 | 19 | def encode_top_level(self, writer: io.BytesIO): 20 | self.encode_nested(writer) 21 | 22 | def decode_nested(self, reader: io.BytesIO): 23 | for i, field in enumerate(self.fields): 24 | try: 25 | field.decode_nested(reader) 26 | except Exception as e: 27 | raise Exception(f"cannot decode field '{i}' of tuple, because of: {e}") 28 | 29 | def decode_top_level(self, data: bytes): 30 | reader = io.BytesIO(data) 31 | self.decode_nested(reader) 32 | 33 | def set_payload(self, value: Any): 34 | native_list, ok = convert_native_value_to_list(value, raise_on_failure=False) 35 | if ok: 36 | if len(self.fields) != len(native_list): 37 | raise ValueError( 38 | f"the number of fields ({len(self.fields)}) does not match the number of provided native values ({len(native_list)})" 39 | ) 40 | 41 | for i, field in enumerate(self.fields): 42 | field.set_payload(native_list[i]) 43 | 44 | return 45 | 46 | raise ValueError("cannot set payload for tuple (should be either a tuple or a list)") 47 | 48 | def get_payload(self) -> Any: 49 | native_values = [field.get_payload() for field in self.fields] 50 | return tuple(native_values) 51 | 52 | def __eq__(self, other: Any) -> bool: 53 | return isinstance(other, TupleValue) and self.fields == other.fields 54 | -------------------------------------------------------------------------------- /multiversx_sdk/abi/tuple_value_test.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | import pytest 4 | 5 | from multiversx_sdk.abi.small_int_values import U32Value 6 | from multiversx_sdk.abi.string_value import StringValue 7 | from multiversx_sdk.abi.tuple_value import TupleValue 8 | 9 | 10 | def test_set_payload_and_get_payload(): 11 | # With errors 12 | with pytest.raises( 13 | ValueError, 14 | match=re.escape("cannot set payload for tuple (should be either a tuple or a list)"), 15 | ): 16 | TupleValue([]).set_payload(42) 17 | 18 | value = TupleValue( 19 | [ 20 | U32Value(), 21 | StringValue(), 22 | ] 23 | ) 24 | 25 | # From tuple 26 | value.set_payload((41, "hello")) 27 | assert value.fields == [U32Value(41), StringValue("hello")] 28 | assert value.get_payload() == (41, "hello") 29 | 30 | # From list 31 | value.set_payload([42, "world"]) 32 | assert value.fields == [U32Value(42), StringValue("world")] 33 | assert value.get_payload() == (42, "world") 34 | -------------------------------------------------------------------------------- /multiversx_sdk/abi/type_formula.py: -------------------------------------------------------------------------------- 1 | class TypeFormula: 2 | def __init__(self, name: str, type_parameters: list["TypeFormula"]) -> None: 3 | self.name: str = name 4 | self.type_parameters: list[TypeFormula] = type_parameters 5 | 6 | def __str__(self) -> str: 7 | if self.type_parameters: 8 | type_parameters = ", ".join([str(type_parameter) for type_parameter in self.type_parameters]) 9 | return f"{self.name}<{type_parameters}>" 10 | else: 11 | return self.name 12 | -------------------------------------------------------------------------------- /multiversx_sdk/abi/type_formula_parser_test.py: -------------------------------------------------------------------------------- 1 | from multiversx_sdk.abi.type_formula_parser import TypeFormulaParser 2 | 3 | 4 | def test_parse_expression(): 5 | parser = TypeFormulaParser() 6 | 7 | test_vectors = [ 8 | ("i64", "i64"), 9 | (" i64 ", "i64"), 10 | ("utf-8 string", "utf-8 string"), 11 | ( 12 | "MultiResultVec>", 13 | "MultiResultVec>", 14 | ), 15 | ("tuple3>", "tuple3>"), 16 | ("tuple2", "tuple2"), 17 | ("tuple2 ", "tuple2"), 18 | ("tuple, List>", "tuple, List>"), 19 | ] 20 | 21 | for input_expression, expected_expression in test_vectors: 22 | type_formula = parser.parse_expression(input_expression) 23 | output_expression = str(type_formula) 24 | 25 | assert output_expression == expected_expression 26 | -------------------------------------------------------------------------------- /multiversx_sdk/abi/typesystem.py: -------------------------------------------------------------------------------- 1 | from typing import Any, Sequence 2 | 3 | from multiversx_sdk.abi.interface import ISingleValue 4 | from multiversx_sdk.abi.multi_value import MultiValue 5 | from multiversx_sdk.abi.optional_value import OptionalValue 6 | from multiversx_sdk.abi.variadic_values import VariadicValues 7 | 8 | 9 | def is_list_of_bytes(values: Sequence[Any]) -> bool: 10 | return all(is_bytes(value) for value in values) 11 | 12 | 13 | def is_bytes(value: Any) -> bool: 14 | return isinstance(value, bytes) 15 | 16 | 17 | def is_list_of_typed_values(values: Sequence[Any]) -> bool: 18 | return all(is_typed_value(value) for value in values) 19 | 20 | 21 | def is_typed_value(value: Any) -> bool: 22 | return is_multi_value(value) or is_single_value(value) 23 | 24 | 25 | def is_single_value(value: Any) -> bool: 26 | return isinstance(value, ISingleValue) 27 | 28 | 29 | def is_multi_value(value: Any) -> bool: 30 | return isinstance(value, MultiValue) or isinstance(value, VariadicValues) or isinstance(value, OptionalValue) 31 | -------------------------------------------------------------------------------- /multiversx_sdk/abi/typesystem_test.py: -------------------------------------------------------------------------------- 1 | from multiversx_sdk.abi.list_value import ListValue 2 | from multiversx_sdk.abi.multi_value import MultiValue 3 | from multiversx_sdk.abi.optional_value import OptionalValue 4 | from multiversx_sdk.abi.small_int_values import U32Value, U64Value 5 | from multiversx_sdk.abi.typesystem import ( 6 | is_bytes, 7 | is_list_of_bytes, 8 | is_list_of_typed_values, 9 | is_multi_value, 10 | is_single_value, 11 | is_typed_value, 12 | ) 13 | from multiversx_sdk.abi.variadic_values import VariadicValues 14 | 15 | 16 | def test_is_list_of_bytes(): 17 | assert is_list_of_bytes([b"a", b"b", b"c"]) 18 | assert is_list_of_bytes([bytes([1, 2, 3]), bytes([4, 5, 6])]) 19 | 20 | assert not is_list_of_bytes([b"a", b"b", "c"]) 21 | assert not is_list_of_bytes([b"a", b"b", [1, 2, 3]]) 22 | 23 | 24 | def test_is_bytes(): 25 | assert is_bytes(b"hello") 26 | assert is_bytes(bytes([1, 2, 3])) 27 | 28 | assert not is_bytes("hello") 29 | assert not is_bytes([1, 2, 3]) 30 | 31 | 32 | def test_is_list_of_typed_values(): 33 | assert is_list_of_typed_values([U64Value(), U64Value()]) 34 | assert is_list_of_typed_values([U32Value(), VariadicValues([U64Value(), U64Value()])]) 35 | 36 | assert not is_list_of_typed_values([U64Value(), "hello"]) 37 | assert not is_list_of_typed_values([U64Value(), [1, 2, 3]]) 38 | 39 | 40 | def test_is_typed_value(): 41 | assert is_typed_value(U64Value()) 42 | assert is_typed_value(ListValue([])) 43 | assert is_typed_value(MultiValue([])) 44 | assert is_typed_value(VariadicValues([])) 45 | assert is_typed_value(OptionalValue(U64Value())) 46 | 47 | assert not is_typed_value("hello") 48 | 49 | 50 | def test_is_single_value(): 51 | assert is_single_value(U64Value()) 52 | assert is_single_value(ListValue([])) 53 | 54 | assert not is_single_value(MultiValue([])) 55 | assert not is_single_value(VariadicValues([])) 56 | assert not is_single_value("hello") 57 | 58 | 59 | def test_is_multi_value(): 60 | assert is_multi_value(MultiValue([])) 61 | assert is_multi_value(VariadicValues([])) 62 | 63 | assert not is_multi_value(U64Value()) 64 | assert not is_multi_value(ListValue([])) 65 | assert not is_multi_value("hello") 66 | -------------------------------------------------------------------------------- /multiversx_sdk/abi/variadic_values.py: -------------------------------------------------------------------------------- 1 | from typing import Any, Callable, Optional, Union 2 | 3 | from multiversx_sdk.abi.interface import IPayloadHolder, ISingleValue 4 | from multiversx_sdk.abi.multi_value import MultiValue 5 | from multiversx_sdk.abi.shared import convert_native_value_to_list 6 | 7 | 8 | class VariadicValues(IPayloadHolder): 9 | def __init__( 10 | self, 11 | items: Optional[list[Union[ISingleValue, MultiValue]]] = None, 12 | item_creator: Optional[Callable[[], Union[ISingleValue, MultiValue]]] = None, 13 | ) -> None: 14 | self.items = items or [] 15 | self.item_creator = item_creator 16 | 17 | def set_payload(self, value: Any): 18 | if not self.item_creator: 19 | raise ValueError("populating variadic values from a native object requires the item creator to be set") 20 | 21 | native_items, _ = convert_native_value_to_list(value) 22 | 23 | self.items.clear() 24 | 25 | for native_item in native_items: 26 | item = self.item_creator() 27 | item.set_payload(native_item) 28 | self.items.append(item) 29 | 30 | def get_payload(self) -> Any: 31 | return [item.get_payload() for item in self.items] 32 | 33 | def __eq__(self, other: Any) -> bool: 34 | return ( 35 | isinstance(other, VariadicValues) and self.items == other.items and self.item_creator == other.item_creator 36 | ) 37 | 38 | def __iter__(self) -> Any: 39 | return iter(self.items) 40 | -------------------------------------------------------------------------------- /multiversx_sdk/abi/variadic_values_test.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | import pytest 4 | 5 | from multiversx_sdk.abi.multi_value import MultiValue 6 | from multiversx_sdk.abi.small_int_values import U32Value 7 | from multiversx_sdk.abi.string_value import StringValue 8 | from multiversx_sdk.abi.variadic_values import VariadicValues 9 | 10 | 11 | def test_set_payload_and_get_payload(): 12 | # Simple 13 | values = VariadicValues(item_creator=lambda: U32Value()) 14 | values.set_payload([1, 2, 3]) 15 | 16 | assert values.items == [U32Value(1), U32Value(2), U32Value(3)] 17 | assert values.get_payload() == [1, 2, 3] 18 | 19 | # Nested 20 | values = VariadicValues(item_creator=lambda: MultiValue([U32Value(), StringValue()])) 21 | values.set_payload([[42, "hello"], [43, "world"]]) 22 | 23 | assert values.items == [ 24 | MultiValue([U32Value(42), StringValue("hello")]), 25 | MultiValue([U32Value(43), StringValue("world")]), 26 | ] 27 | 28 | assert values.get_payload() == [[42, "hello"], [43, "world"]] 29 | 30 | # With errors 31 | with pytest.raises( 32 | ValueError, 33 | match="populating variadic values from a native object requires the item creator to be set", 34 | ): 35 | VariadicValues().set_payload([1, 2, 3]) 36 | 37 | # With errors 38 | with pytest.raises(ValueError, match=re.escape("invalid literal for int() with base 10: 'foo'")): 39 | values = VariadicValues(item_creator=lambda: U32Value()) 40 | values.set_payload(["foo"]) 41 | -------------------------------------------------------------------------------- /multiversx_sdk/account_management/__init__.py: -------------------------------------------------------------------------------- 1 | from multiversx_sdk.account_management.account_controller import AccountController 2 | from multiversx_sdk.account_management.account_transactions_factory import ( 3 | AccountTransactionsFactory, 4 | ) 5 | 6 | __all__ = ["AccountTransactionsFactory", "AccountController"] 7 | -------------------------------------------------------------------------------- /multiversx_sdk/accounts/__init__.py: -------------------------------------------------------------------------------- 1 | from multiversx_sdk.accounts.account import Account 2 | from multiversx_sdk.accounts.ledger_account import LedgerAccount 3 | 4 | __all__ = ["Account", "LedgerAccount"] 5 | -------------------------------------------------------------------------------- /multiversx_sdk/accounts/ledger_account_test.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from multiversx_sdk.accounts.ledger_account import LedgerAccount 4 | from multiversx_sdk.core.message import Message, MessageComputer 5 | from multiversx_sdk.wallet.user_keys import UserPublicKey 6 | from multiversx_sdk.wallet.user_verifer import UserVerifier 7 | 8 | 9 | @pytest.mark.skip("Requires Ledger Device") 10 | def test_sign_message(): 11 | account = LedgerAccount() 12 | address = account.address 13 | 14 | message = Message(data=b"this is a test message") 15 | signature = account.sign_message(message) 16 | 17 | message_computer = MessageComputer() 18 | verifier = UserVerifier(UserPublicKey(address.get_public_key())) 19 | is_signed_by_account = verifier.verify( 20 | data=message_computer.compute_bytes_for_verifying(message), signature=signature 21 | ) 22 | 23 | assert is_signed_by_account 24 | -------------------------------------------------------------------------------- /multiversx_sdk/builders/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/multiversx/mx-sdk-py/e7994cfd135a4e88ab4282ce07b41d7524ddc34e/multiversx_sdk/builders/__init__.py -------------------------------------------------------------------------------- /multiversx_sdk/builders/token_transfers_data_builder.py: -------------------------------------------------------------------------------- 1 | from multiversx_sdk.abi import BigUIntValue, Serializer 2 | from multiversx_sdk.abi.string_value import StringValue 3 | from multiversx_sdk.core import Address, TokenComputer, TokenTransfer 4 | 5 | 6 | class TokenTransfersDataBuilder: 7 | """ 8 | **FOR INTERNAL USE ONLY.** 9 | Used for the transactions factories. 10 | """ 11 | 12 | def __init__(self, token_computer: TokenComputer) -> None: 13 | self.token_computer = token_computer 14 | self.serializer = Serializer() 15 | 16 | def build_args_for_esdt_transfer(self, transfer: TokenTransfer) -> list[str]: 17 | args = ["ESDTTransfer"] 18 | 19 | serialized_args = self.serializer.serialize_to_parts( 20 | [StringValue(transfer.token.identifier), BigUIntValue(transfer.amount)] 21 | ) 22 | args.extend([arg.hex() for arg in serialized_args]) 23 | 24 | return args 25 | 26 | def build_args_for_single_esdt_nft_transfer(self, transfer: TokenTransfer, receiver: Address) -> list[str]: 27 | args = ["ESDTNFTTransfer"] 28 | token = transfer.token 29 | identifier = self.token_computer.extract_identifier_from_extended_identifier(token.identifier) 30 | 31 | serialized_args = self.serializer.serialize_to_parts( 32 | [ 33 | StringValue(identifier), 34 | BigUIntValue(token.nonce), 35 | BigUIntValue(transfer.amount), 36 | ] 37 | ) 38 | args.extend([arg.hex() for arg in serialized_args]) 39 | args.append(receiver.to_hex()) 40 | 41 | return args 42 | 43 | def build_args_for_multi_esdt_nft_transfer(self, receiver: Address, transfers: list[TokenTransfer]) -> list[str]: 44 | serialized_num_of_transfers = self.serializer.serialize([BigUIntValue(len(transfers))]) 45 | args = ["MultiESDTNFTTransfer", receiver.to_hex(), serialized_num_of_transfers] 46 | 47 | for transfer in transfers: 48 | identifier = self.token_computer.extract_identifier_from_extended_identifier(transfer.token.identifier) 49 | serialized_args = self.serializer.serialize_to_parts( 50 | [ 51 | StringValue(identifier), 52 | BigUIntValue(transfer.token.nonce), 53 | BigUIntValue(transfer.amount), 54 | ] 55 | ) 56 | args.extend([arg.hex() for arg in serialized_args]) 57 | 58 | return args 59 | -------------------------------------------------------------------------------- /multiversx_sdk/builders/transaction_builder.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from multiversx_sdk.core import Address, Transaction 4 | from multiversx_sdk.core.constants import ARGS_SEPARATOR 5 | from multiversx_sdk.core.transactions_factory_config import TransactionsFactoryConfig 6 | 7 | 8 | class TransactionBuilder: 9 | """ 10 | **FOR INTERNAL USE ONLY.** 11 | Used for the transactions factories. 12 | """ 13 | 14 | def __init__( 15 | self, 16 | config: TransactionsFactoryConfig, 17 | sender: Address, 18 | receiver: Address, 19 | data_parts: list[str], 20 | gas_limit: int, 21 | add_data_movement_gas: bool, 22 | amount: Optional[int] = None, 23 | ) -> None: 24 | self.config = config 25 | self.sender = sender 26 | self.receiver = receiver 27 | self.data_parts = data_parts 28 | self.provided_gas_limit = gas_limit 29 | self.add_data_movement_gas = add_data_movement_gas 30 | self.amount = amount 31 | 32 | def compute_gas_limit(self, payload: bytes) -> int: 33 | if not self.add_data_movement_gas: 34 | return self.provided_gas_limit 35 | 36 | data_movement_gas = self.config.min_gas_limit + self.config.gas_limit_per_byte * len(payload) 37 | gas = data_movement_gas + self.provided_gas_limit 38 | 39 | return gas 40 | 41 | def build_transaction_payload(self, parts: list[str]) -> bytes: 42 | data = ARGS_SEPARATOR.join(parts) 43 | return data.encode("utf-8") 44 | 45 | def build(self) -> Transaction: 46 | data = self.build_transaction_payload(self.data_parts) 47 | gas_limit = self.compute_gas_limit(data) 48 | 49 | transaction = Transaction( 50 | sender=self.sender, 51 | receiver=self.receiver, 52 | gas_limit=gas_limit, 53 | chain_id=self.config.chain_id, 54 | data=data, 55 | value=self.amount, 56 | ) 57 | 58 | return transaction 59 | -------------------------------------------------------------------------------- /multiversx_sdk/core/__init__.py: -------------------------------------------------------------------------------- 1 | from multiversx_sdk.core.address import Address, AddressComputer, AddressFactory 2 | from multiversx_sdk.core.code_metadata import CodeMetadata 3 | from multiversx_sdk.core.config import LibraryConfig 4 | from multiversx_sdk.core.message import Message, MessageComputer 5 | from multiversx_sdk.core.tokens import ( 6 | Token, 7 | TokenComputer, 8 | TokenIdentifierParts, 9 | TokenTransfer, 10 | ) 11 | from multiversx_sdk.core.transaction import Transaction 12 | from multiversx_sdk.core.transaction_computer import TransactionComputer 13 | from multiversx_sdk.core.transaction_events_parser import TransactionEventsParser 14 | from multiversx_sdk.core.transaction_on_network import ( 15 | SmartContractResult, 16 | TransactionEvent, 17 | TransactionLogs, 18 | TransactionOnNetwork, 19 | TransactionStatus, 20 | find_events_by_first_topic, 21 | find_events_by_identifier, 22 | ) 23 | from multiversx_sdk.core.transactions_factory_config import TransactionsFactoryConfig 24 | 25 | __all__ = [ 26 | "Address", 27 | "AddressFactory", 28 | "AddressComputer", 29 | "Transaction", 30 | "TransactionComputer", 31 | "Message", 32 | "MessageComputer", 33 | "CodeMetadata", 34 | "Token", 35 | "TokenComputer", 36 | "TokenTransfer", 37 | "TokenIdentifierParts", 38 | "SmartContractResult", 39 | "TransactionEvent", 40 | "TransactionLogs", 41 | "TransactionOnNetwork", 42 | "TransactionStatus", 43 | "TransactionsFactoryConfig", 44 | "find_events_by_identifier", 45 | "find_events_by_first_topic", 46 | "TransactionEventsParser", 47 | "LibraryConfig", 48 | ] 49 | -------------------------------------------------------------------------------- /multiversx_sdk/core/base_controller.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from multiversx_sdk.core.constants import ( 4 | EXTRA_GAS_LIMIT_FOR_GUARDED_TRANSACTIONS, 5 | EXTRA_GAS_LIMIT_FOR_RELAYED_TRANSACTIONS, 6 | ) 7 | from multiversx_sdk.core.interfaces import IAccount 8 | from multiversx_sdk.core.transaction import Transaction 9 | from multiversx_sdk.core.transaction_computer import TransactionComputer 10 | 11 | 12 | class BaseController: 13 | """This is the base class for all controllers. **Internal use only.**""" 14 | 15 | def _set_version_and_options_for_hash_signing(self, sender: IAccount, transaction: Transaction): 16 | """If the Account has the `use_hash_signing` flag set to `True`, this method will set the correct `version` and `options` properties of `Transaction`.""" 17 | if sender.use_hash_signing: 18 | transaction_computer = TransactionComputer() 19 | transaction_computer.apply_options_for_hash_signing(transaction) 20 | 21 | def _add_extra_gas_limit_if_required(self, transaction: Transaction): 22 | """In case of guarded or relayed transactions, extra gas limit is added.""" 23 | if transaction.guardian: 24 | transaction.gas_limit += EXTRA_GAS_LIMIT_FOR_GUARDED_TRANSACTIONS 25 | 26 | if transaction.relayer: 27 | transaction.gas_limit += EXTRA_GAS_LIMIT_FOR_RELAYED_TRANSACTIONS 28 | 29 | def _set_transaction_gas_options( 30 | self, 31 | transaction: Transaction, 32 | gas_limit: Optional[int] = None, 33 | gas_price: Optional[int] = None, 34 | ): 35 | if gas_price: 36 | transaction.gas_price = gas_price 37 | 38 | if gas_limit: 39 | transaction.gas_limit = gas_limit 40 | else: 41 | self._add_extra_gas_limit_if_required(transaction) 42 | 43 | def _set_version_and_options_for_guardian(self, transaction: Transaction): 44 | if transaction.guardian: 45 | transaction_computer = TransactionComputer() 46 | transaction_computer.apply_guardian(transaction, transaction.guardian) 47 | -------------------------------------------------------------------------------- /multiversx_sdk/core/code_metadata.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | CODE_METADATA_LENGTH = 2 4 | 5 | 6 | class ByteZero(Enum): 7 | Upgradeable = 1 8 | Reserved2 = 2 9 | Readable = 4 10 | 11 | 12 | class ByteOne(Enum): 13 | Reserved1 = 1 14 | Payable = 2 15 | PayableByContract = 4 16 | 17 | 18 | class CodeMetadata: 19 | def __init__( 20 | self, 21 | upgradeable: bool = True, 22 | readable: bool = True, 23 | payable: bool = False, 24 | payable_by_contract: bool = False, 25 | ): 26 | self.upgradeable = upgradeable 27 | self.readable = readable 28 | self.payable = payable 29 | self.payable_by_contract = payable_by_contract 30 | 31 | @classmethod 32 | def new_from_bytes(cls, data: bytes) -> "CodeMetadata": 33 | if len(data) != CODE_METADATA_LENGTH: 34 | raise ValueError(f"code metadata buffer has length {len(data)}, expected {CODE_METADATA_LENGTH}") 35 | 36 | byte_zero = data[0] 37 | byte_one = data[1] 38 | 39 | upgradeable = (byte_zero & ByteZero.Upgradeable.value) != 0 40 | readable = (byte_zero & ByteZero.Readable.value) != 0 41 | payable = (byte_one & ByteOne.Payable.value) != 0 42 | payable_by_contract = (byte_one & ByteOne.PayableByContract.value) != 0 43 | 44 | return cls(upgradeable, readable, payable, payable_by_contract) 45 | 46 | def serialize(self) -> bytes: 47 | data = bytearray([0, 0]) 48 | 49 | if self.upgradeable: 50 | data[0] |= ByteZero.Upgradeable.value 51 | if self.readable: 52 | data[0] |= ByteZero.Readable.value 53 | if self.payable: 54 | data[1] |= ByteOne.Payable.value 55 | if self.payable_by_contract: 56 | data[1] |= ByteOne.PayableByContract.value 57 | 58 | return bytes(data) 59 | 60 | def __str__(self) -> str: 61 | return self.serialize().hex() 62 | -------------------------------------------------------------------------------- /multiversx_sdk/core/code_metadata_test.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from multiversx_sdk.core.code_metadata import CodeMetadata 4 | 5 | 6 | def test_code_metadata_new_from_bytes(): 7 | metadata = CodeMetadata.new_from_bytes(bytes([0x05, 0x00])) 8 | assert metadata.upgradeable 9 | assert metadata.readable 10 | assert metadata.payable is False 11 | assert metadata.payable_by_contract is False 12 | 13 | metadata = CodeMetadata.new_from_bytes(bytes([0x05, 0x06])) 14 | assert metadata.upgradeable 15 | assert metadata.readable 16 | assert metadata.payable 17 | assert metadata.payable_by_contract 18 | 19 | metadata = CodeMetadata.new_from_bytes(bytes([0x05, 0x04])) 20 | assert metadata.upgradeable 21 | assert metadata.readable 22 | assert metadata.payable is False 23 | assert metadata.payable_by_contract 24 | 25 | metadata = CodeMetadata.new_from_bytes(bytes([0x00, 0x00])) 26 | assert metadata.upgradeable is False 27 | assert metadata.readable is False 28 | assert metadata.payable is False 29 | assert metadata.payable_by_contract is False 30 | 31 | with pytest.raises(ValueError, match="code metadata buffer has length 4, expected 2"): 32 | CodeMetadata.new_from_bytes(bytes([0x00, 0x01, 0x02, 0x03])) 33 | 34 | 35 | def test_code_metadata_serialize(): 36 | assert CodeMetadata().serialize() == bytes([0x05, 0x00]) 37 | assert CodeMetadata(upgradeable=True, readable=True).serialize() == bytes([0x05, 0x00]) 38 | assert CodeMetadata(upgradeable=True, readable=True, payable=True, payable_by_contract=True).serialize() == bytes( 39 | [0x05, 0x06] 40 | ) 41 | assert CodeMetadata(upgradeable=True, readable=True, payable=False, payable_by_contract=True).serialize() == bytes( 42 | [0x05, 0x04] 43 | ) 44 | assert CodeMetadata( 45 | upgradeable=False, readable=False, payable=False, payable_by_contract=False 46 | ).serialize() == bytes([0x00, 0x00]) 47 | -------------------------------------------------------------------------------- /multiversx_sdk/core/config.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | 4 | @dataclass 5 | class LibraryConfig: 6 | """ 7 | Global configuration of the library. 8 | 9 | Generally speaking, this configuration should only be altered in exotic use cases. 10 | It can be seen as a collection of constants or, more precisely, variables that are rarely changed and used throughout the library. 11 | 12 | Never alter the configuration within a library! 13 | Only alter the configuration, if needed, within a final application that uses this library. 14 | """ 15 | 16 | # The human-readable part of the bech32 addresses 17 | default_address_hrp: str = "erd" 18 | -------------------------------------------------------------------------------- /multiversx_sdk/core/constants.py: -------------------------------------------------------------------------------- 1 | TRANSACTION_MIN_GAS_PRICE = 1000000000 2 | TRANSACTION_VERSION_DEFAULT = 2 3 | TRANSACTION_OPTIONS_DEFAULT = 0 4 | ARGS_SEPARATOR = "@" 5 | METACHAIN_ID = 4294967295 6 | VM_TYPE_WASM_VM = bytes([0x05, 0x00]) 7 | 8 | # 64 bytes = 512 bits 9 | INTEGER_MAX_NUM_BYTES = 64 10 | 11 | EGLD_TOKEN_IDENTIFIER = "EGLD" 12 | EGLD_NUM_DECIMALS = 18 13 | 14 | DELEGATION_MANAGER_SC_ADDRESS_HEX = "000000000000000000010000000000000000000000000000000000000004ffff" 15 | CONTRACT_DEPLOY_ADDRESS_HEX = "0000000000000000000000000000000000000000000000000000000000000000" 16 | ESDT_CONTRACT_ADDRESS_HEX = "000000000000000000010000000000000000000000000000000000000002ffff" 17 | STAKING_SMART_CONTRACT_ADDRESS_HEX = "000000000000000000010000000000000000000000000000000000000001ffff" 18 | 19 | TRANSACTION_OPTIONS_TX_GUARDED = 0b0010 20 | TRANSACTION_OPTIONS_TX_HASH_SIGN = 0b0001 21 | 22 | DIGEST_SIZE = 32 23 | 24 | TOKEN_RANDOM_SEQUENCE_LENGTH = 6 25 | 26 | DEFAULT_MESSAGE_VERSION = 1 27 | BECH32_ADDRESS_LENGTH = 62 28 | 29 | HEX_ADDRESS_LENGTH = 64 30 | 31 | MIN_TRANSACTION_VERSION_THAT_SUPPORTS_OPTIONS = 2 32 | SDK_PY_SIGNER = "sdk-py" 33 | UNKNOWN_SIGNER = "unknown" 34 | 35 | EGLD_IDENTIFIER_FOR_MULTI_ESDTNFT_TRANSFER = "EGLD-000000" 36 | 37 | EXTRA_GAS_LIMIT_FOR_GUARDED_TRANSACTIONS = 50_000 38 | EXTRA_GAS_LIMIT_FOR_RELAYED_TRANSACTIONS = 50_000 39 | -------------------------------------------------------------------------------- /multiversx_sdk/core/errors.py: -------------------------------------------------------------------------------- 1 | from typing import Any 2 | 3 | 4 | class BadPubkeyLengthError(Exception): 5 | def __init__(self, actual: int, expected: int) -> None: 6 | super().__init__(f"Bad pubkey length: actual = {actual}, expected = {expected}") 7 | 8 | 9 | class BadAddressError(Exception): 10 | def __init__(self, address: Any) -> None: 11 | super().__init__(f"Bad address: {address}") 12 | 13 | 14 | class NotEnoughGasError(Exception): 15 | def __init__(self, gas_limit: int) -> None: 16 | super().__init__(f"Not enough gas provided: {gas_limit}") 17 | 18 | 19 | class BadUsageError(Exception): 20 | def __init__(self, message: str) -> None: 21 | super().__init__(message) 22 | 23 | 24 | class InvalidTokenIdentifierError(Exception): 25 | def __init__(self, message: str) -> None: 26 | super().__init__(message) 27 | 28 | 29 | class ParseTransactionOnNetworkError(Exception): 30 | def __init__(self, message: str) -> None: 31 | super().__init__(message) 32 | -------------------------------------------------------------------------------- /multiversx_sdk/core/interfaces.py: -------------------------------------------------------------------------------- 1 | from typing import Protocol 2 | 3 | from multiversx_sdk.core.address import Address 4 | from multiversx_sdk.core.transaction import Transaction 5 | 6 | 7 | class INetworkConfig(Protocol): 8 | min_gas_limit: int 9 | gas_per_data_byte: int 10 | gas_price_modifier: float 11 | 12 | 13 | # fmt: off 14 | class IAccount(Protocol): 15 | @property 16 | def use_hash_signing(self) -> bool: 17 | ... 18 | 19 | @property 20 | def address(self) -> Address: 21 | ... 22 | 23 | def sign_transaction(self, transaction: Transaction) -> bytes: 24 | ... 25 | # fmt: on 26 | -------------------------------------------------------------------------------- /multiversx_sdk/core/proto/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/multiversx/mx-sdk-py/e7994cfd135a4e88ab4282ce07b41d7524ddc34e/multiversx_sdk/core/proto/__init__.py -------------------------------------------------------------------------------- /multiversx_sdk/core/proto/transaction.proto: -------------------------------------------------------------------------------- 1 | 2 | // This file holds the transaction data structure that will be used for computing hashes 3 | syntax = "proto3"; 4 | 5 | package proto; 6 | 7 | // Transaction holds all the data needed for a value transfer or SC call 8 | message Transaction { 9 | uint64 Nonce = 1; 10 | bytes Value = 2; 11 | bytes RcvAddr = 3; 12 | bytes RcvUserName = 4; 13 | bytes SndAddr = 5; 14 | bytes SndUserName = 6; 15 | uint64 GasPrice = 7; 16 | uint64 GasLimit = 8; 17 | bytes Data = 9; 18 | bytes ChainID = 10; 19 | uint32 Version = 11; 20 | bytes Signature = 12; 21 | uint32 Options = 13; 22 | bytes GuardAddr = 14; 23 | bytes GuardSignature = 15; 24 | bytes Relayer = 16; 25 | bytes RelayerSignature = 17; 26 | } 27 | -------------------------------------------------------------------------------- /multiversx_sdk/core/proto/transaction_pb2.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by the protocol buffer compiler. DO NOT EDIT! 3 | # source: transaction.proto 4 | """Generated protocol buffer code.""" 5 | from google.protobuf.internal import builder as _builder 6 | from google.protobuf import descriptor as _descriptor 7 | from google.protobuf import descriptor_pool as _descriptor_pool 8 | from google.protobuf import symbol_database as _symbol_database 9 | # @@protoc_insertion_point(imports) 10 | 11 | _sym_db = _symbol_database.Default() 12 | 13 | 14 | 15 | 16 | DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x11transaction.proto\x12\x05proto\"\xc5\x02\n\x0bTransaction\x12\r\n\x05Nonce\x18\x01 \x01(\x04\x12\r\n\x05Value\x18\x02 \x01(\x0c\x12\x0f\n\x07RcvAddr\x18\x03 \x01(\x0c\x12\x13\n\x0bRcvUserName\x18\x04 \x01(\x0c\x12\x0f\n\x07SndAddr\x18\x05 \x01(\x0c\x12\x13\n\x0bSndUserName\x18\x06 \x01(\x0c\x12\x10\n\x08GasPrice\x18\x07 \x01(\x04\x12\x10\n\x08GasLimit\x18\x08 \x01(\x04\x12\x0c\n\x04\x44\x61ta\x18\t \x01(\x0c\x12\x0f\n\x07\x43hainID\x18\n \x01(\x0c\x12\x0f\n\x07Version\x18\x0b \x01(\r\x12\x11\n\tSignature\x18\x0c \x01(\x0c\x12\x0f\n\x07Options\x18\r \x01(\r\x12\x11\n\tGuardAddr\x18\x0e \x01(\x0c\x12\x16\n\x0eGuardSignature\x18\x0f \x01(\x0c\x12\x0f\n\x07Relayer\x18\x10 \x01(\x0c\x12\x18\n\x10RelayerSignature\x18\x11 \x01(\x0c\x62\x06proto3') 17 | 18 | _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals()) 19 | _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'transaction_pb2', globals()) 20 | if _descriptor._USE_C_DESCRIPTORS == False: 21 | 22 | DESCRIPTOR._options = None 23 | _TRANSACTION._serialized_start=29 # pyright: ignore[reportUndefinedVariable] 24 | _TRANSACTION._serialized_end=354 # pyright: ignore[reportUndefinedVariable] 25 | # @@protoc_insertion_point(module_scope) 26 | -------------------------------------------------------------------------------- /multiversx_sdk/core/proto/transaction_pb2.pyi: -------------------------------------------------------------------------------- 1 | from google.protobuf import descriptor as _descriptor 2 | from google.protobuf import message as _message 3 | from typing import ClassVar as _ClassVar, Optional as _Optional 4 | 5 | DESCRIPTOR: _descriptor.FileDescriptor 6 | 7 | class Transaction(_message.Message): 8 | __slots__ = ["ChainID", "Data", "GasLimit", "GasPrice", "GuardAddr", "GuardSignature", "Nonce", "Options", "RcvAddr", "RcvUserName", "Relayer", "RelayerSignature", "Signature", "SndAddr", "SndUserName", "Value", "Version"] 9 | CHAINID_FIELD_NUMBER: _ClassVar[int] 10 | ChainID: bytes 11 | DATA_FIELD_NUMBER: _ClassVar[int] 12 | Data: bytes 13 | GASLIMIT_FIELD_NUMBER: _ClassVar[int] 14 | GASPRICE_FIELD_NUMBER: _ClassVar[int] 15 | GUARDADDR_FIELD_NUMBER: _ClassVar[int] 16 | GUARDSIGNATURE_FIELD_NUMBER: _ClassVar[int] 17 | GasLimit: int 18 | GasPrice: int 19 | GuardAddr: bytes 20 | GuardSignature: bytes 21 | NONCE_FIELD_NUMBER: _ClassVar[int] 22 | Nonce: int 23 | OPTIONS_FIELD_NUMBER: _ClassVar[int] 24 | Options: int 25 | RCVADDR_FIELD_NUMBER: _ClassVar[int] 26 | RCVUSERNAME_FIELD_NUMBER: _ClassVar[int] 27 | RELAYERSIGNATURE_FIELD_NUMBER: _ClassVar[int] 28 | RELAYER_FIELD_NUMBER: _ClassVar[int] 29 | RcvAddr: bytes 30 | RcvUserName: bytes 31 | Relayer: bytes 32 | RelayerSignature: bytes 33 | SIGNATURE_FIELD_NUMBER: _ClassVar[int] 34 | SNDADDR_FIELD_NUMBER: _ClassVar[int] 35 | SNDUSERNAME_FIELD_NUMBER: _ClassVar[int] 36 | Signature: bytes 37 | SndAddr: bytes 38 | SndUserName: bytes 39 | VALUE_FIELD_NUMBER: _ClassVar[int] 40 | VERSION_FIELD_NUMBER: _ClassVar[int] 41 | Value: bytes 42 | Version: int 43 | def __init__(self, Nonce: _Optional[int] = ..., Value: _Optional[bytes] = ..., RcvAddr: _Optional[bytes] = ..., RcvUserName: _Optional[bytes] = ..., SndAddr: _Optional[bytes] = ..., SndUserName: _Optional[bytes] = ..., GasPrice: _Optional[int] = ..., GasLimit: _Optional[int] = ..., Data: _Optional[bytes] = ..., ChainID: _Optional[bytes] = ..., Version: _Optional[int] = ..., Signature: _Optional[bytes] = ..., Options: _Optional[int] = ..., GuardAddr: _Optional[bytes] = ..., GuardSignature: _Optional[bytes] = ..., Relayer: _Optional[bytes] = ..., RelayerSignature: _Optional[bytes] = ...) -> None: ... 44 | -------------------------------------------------------------------------------- /multiversx_sdk/core/transaction_events_parser.py: -------------------------------------------------------------------------------- 1 | from types import SimpleNamespace 2 | 3 | from multiversx_sdk.abi.abi import Abi 4 | from multiversx_sdk.core.transaction_on_network import TransactionEvent 5 | 6 | 7 | class TransactionEventsParser: 8 | def __init__(self, abi: Abi, first_topic_as_identifier: bool = True) -> None: 9 | self.abi = abi 10 | # By default, we consider that the first topic is the event identifier. 11 | # This is true for log entries emitted by smart contracts: 12 | # https://github.com/multiversx/mx-chain-vm-go/blob/v1.5.27/vmhost/contexts/output.go#L270 13 | # https://github.com/multiversx/mx-chain-vm-go/blob/v1.5.27/vmhost/contexts/output.go#L283 14 | self.first_topic_as_identifier = first_topic_as_identifier 15 | 16 | def parse_events(self, events: list[TransactionEvent]) -> list[SimpleNamespace]: 17 | return [self.parse_event(event) for event in events] 18 | 19 | def parse_event(self, event: TransactionEvent) -> SimpleNamespace: 20 | first_topic = event.topics[0].decode() if len(event.topics) else "" 21 | abi_identifier = first_topic if first_topic and self.first_topic_as_identifier else event.identifier 22 | topics = event.topics 23 | if self.first_topic_as_identifier: 24 | topics = topics[1:] 25 | return self.abi.decode_event( 26 | event_name=abi_identifier, 27 | topics=topics, 28 | additional_data=event.additional_data, 29 | ) 30 | -------------------------------------------------------------------------------- /multiversx_sdk/core/transaction_status.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | 4 | @dataclass 5 | class TransactionStatus: 6 | status: str 7 | is_completed: bool 8 | is_successful: bool 9 | is_failed: bool 10 | 11 | def __init__(self, status: str): 12 | self.status = status.lower() 13 | self.is_completed = self._is_status_completed() 14 | self.is_successful = self._is_status_successful() 15 | self.is_failed = self._is_status_failed() 16 | 17 | def _is_status_completed(self) -> bool: 18 | return self._is_status_successful() or self._is_status_failed() 19 | 20 | def _is_status_successful(self) -> bool: 21 | return self.status == "executed" or self.status == "success" or self.status == "successful" 22 | 23 | def _is_status_failed(self) -> bool: 24 | return ( 25 | self.status == "fail" 26 | or self.status == "failed" 27 | or self.status == "unsuccessful" 28 | or self.status == "invalid" 29 | ) 30 | -------------------------------------------------------------------------------- /multiversx_sdk/delegation/__init__.py: -------------------------------------------------------------------------------- 1 | from multiversx_sdk.delegation.delegation_controller import DelegationController 2 | from multiversx_sdk.delegation.delegation_transactions_factory import ( 3 | DelegationTransactionsFactory, 4 | ) 5 | from multiversx_sdk.delegation.delegation_transactions_outcome_parser import ( 6 | DelegationTransactionsOutcomeParser, 7 | ) 8 | from multiversx_sdk.delegation.delegation_transactions_outcome_parser_types import ( 9 | CreateNewDelegationContractOutcome, 10 | ) 11 | 12 | __all__ = [ 13 | "DelegationController", 14 | "DelegationTransactionsFactory", 15 | "DelegationTransactionsOutcomeParser", 16 | "CreateNewDelegationContractOutcome", 17 | ] 18 | -------------------------------------------------------------------------------- /multiversx_sdk/delegation/delegation_transactions_outcome_parser_types.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | from multiversx_sdk.core.address import Address 4 | 5 | 6 | @dataclass 7 | class CreateNewDelegationContractOutcome: 8 | contract_address: Address 9 | 10 | 11 | @dataclass 12 | class ClaimRewardsOutcome: 13 | amount: int 14 | 15 | 16 | @dataclass 17 | class DelegateOutcome: 18 | amount: int 19 | 20 | 21 | @dataclass 22 | class RedelegateRewardsOutcome: 23 | amount: int 24 | 25 | 26 | @dataclass 27 | class UndelegateOutcome: 28 | amount: int 29 | -------------------------------------------------------------------------------- /multiversx_sdk/delegation/errors.py: -------------------------------------------------------------------------------- 1 | class ListsLengthMismatchError(Exception): 2 | def __init__(self, message: str) -> None: 3 | super().__init__(message) 4 | -------------------------------------------------------------------------------- /multiversx_sdk/entrypoints/__init__.py: -------------------------------------------------------------------------------- 1 | from multiversx_sdk.entrypoints.entrypoints import ( 2 | DevnetEntrypoint, 3 | LocalnetEntrypoint, 4 | MainnetEntrypoint, 5 | NetworkEntrypoint, 6 | TestnetEntrypoint, 7 | ) 8 | 9 | __all__ = [ 10 | "DevnetEntrypoint", 11 | "MainnetEntrypoint", 12 | "NetworkEntrypoint", 13 | "TestnetEntrypoint", 14 | "LocalnetEntrypoint", 15 | ] 16 | -------------------------------------------------------------------------------- /multiversx_sdk/entrypoints/config.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | 4 | @dataclass 5 | class TestnetEntrypointConfig: 6 | network_provider_url = "https://testnet-api.multiversx.com" 7 | network_provider_kind = "api" 8 | chain_id = "T" 9 | 10 | 11 | @dataclass 12 | class DevnetEntrypointConfig: 13 | network_provider_url = "https://devnet-api.multiversx.com" 14 | network_provider_kind = "api" 15 | chain_id = "D" 16 | 17 | 18 | @dataclass 19 | class MainnetEntrypointConfig: 20 | network_provider_url = "https://api.multiversx.com" 21 | network_provider_kind = "api" 22 | chain_id = "1" 23 | 24 | 25 | @dataclass 26 | class LocalnetEntrypointConfig: 27 | network_provider_url = "http://localhost:7950" 28 | network_provider_kind = "proxy" 29 | chain_id = "localnet" 30 | -------------------------------------------------------------------------------- /multiversx_sdk/entrypoints/errors.py: -------------------------------------------------------------------------------- 1 | class InvalidNetworkProviderKindError(Exception): 2 | def __init__(self) -> None: 3 | super().__init__("Invalid network provider kind. Choose between `api` and `proxy`.") 4 | -------------------------------------------------------------------------------- /multiversx_sdk/ledger/__init__.py: -------------------------------------------------------------------------------- 1 | from multiversx_sdk.ledger.ledger_app import LedgerApp 2 | 3 | __all__ = ["LedgerApp"] 4 | -------------------------------------------------------------------------------- /multiversx_sdk/ledger/config.py: -------------------------------------------------------------------------------- 1 | class LedgerAppConfiguration: 2 | data_activated: bool 3 | account_index: int 4 | address_index: int 5 | version: str 6 | -------------------------------------------------------------------------------- /multiversx_sdk/ledger/errors.py: -------------------------------------------------------------------------------- 1 | class LedgerError(Exception): 2 | def __init__(self, message: str): 3 | super().__init__("Ledger error: " + message) 4 | -------------------------------------------------------------------------------- /multiversx_sdk/native_auth/__init__.py: -------------------------------------------------------------------------------- 1 | from multiversx_sdk.native_auth.config import ( 2 | NativeAuthClientConfig, 3 | NativeAuthServerConfig, 4 | ) 5 | from multiversx_sdk.native_auth.native_auth_client import NativeAuthClient 6 | from multiversx_sdk.native_auth.native_auth_server import NativeAuthServer 7 | 8 | __all__ = ["NativeAuthClient", "NativeAuthClientConfig", "NativeAuthServerConfig", "NativeAuthServer"] 9 | -------------------------------------------------------------------------------- /multiversx_sdk/native_auth/constants.py: -------------------------------------------------------------------------------- 1 | MAX_EXPIRY_SECONDS = 86400 2 | ONE_HOUR_IN_SECONDS = 3600 3 | DEFAULT_EXPIRY_TIME_IN_SECONDS = 60 * 60 * 2 4 | DEFAULT_API_URL = "https://api.multiversx.com" 5 | ACCESS_TOKEN_COMPONENTS_SEPARATOR = "." 6 | CACHE_VALUE_IMPERSONATED = 1 7 | CACHE_VALUE_NOT_IMPERSONATED = 0 8 | ONE_ROUND_IN_SECONDS = 6 9 | MAX_ACCEPTED_WILDCARD_ORIGINS = 1000 10 | -------------------------------------------------------------------------------- /multiversx_sdk/native_auth/errors.py: -------------------------------------------------------------------------------- 1 | class NativeAuthClientError(Exception): 2 | def __init__(self, message: str): 3 | super().__init__(message) 4 | 5 | 6 | class NativeAuthInvalidConfigError(Exception): 7 | def __init__(self, message: str): 8 | super().__init__(message) 9 | 10 | 11 | class NativeAuthInvalidWildcardOriginError(Exception): 12 | def __init__(self, origin: str): 13 | super().__init__(f"Invalid wildcard origin: {origin}") 14 | 15 | 16 | class NativeAuthInvalidTokenError(Exception): 17 | def __init__(self): 18 | super().__init__("The provided token in not a NativeAuth token.") 19 | 20 | 21 | class NativeAuthInvalidTokenTtlError(Exception): 22 | def __init__(self, current_ttl: int, max_ttl: int): 23 | super().__init__(f"The provided TTL: {current_ttl} is larger than the maximum allowed TTL: {max_ttl}.") 24 | 25 | 26 | class NativeAuthOriginNotAcceptedError(Exception): 27 | def __init__(self, origin: str): 28 | super().__init__(f"The origin: {origin} is not accepted.") 29 | 30 | 31 | class NativeAuthInvalidBlockHashError(Exception): 32 | def __init__(self, block_hash: str): 33 | super().__init__(f"Invalid block hash: {block_hash}") 34 | 35 | 36 | class NativeAuthTokenExpiredError(Exception): 37 | def __init__(self): 38 | super().__init__("The provided token has expired.") 39 | 40 | 41 | class NativeAuthInvalidSignatureError(Exception): 42 | def __init__(self): 43 | super().__init__("The provided signature is invalid.") 44 | 45 | 46 | class NativeAuthInvalidImpersonateError(Exception): 47 | def __init__(self): 48 | super().__init__("Invalid impersonate.") 49 | -------------------------------------------------------------------------------- /multiversx_sdk/native_auth/resources.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass, field 2 | from typing import Any 3 | 4 | from multiversx_sdk.core.address import Address 5 | 6 | 7 | @dataclass 8 | class WildcardOrigin: 9 | protocol: str = "" 10 | domain: str = "" 11 | 12 | 13 | @dataclass 14 | class NativeAuthDecoded: 15 | ttl: int = 0 16 | origin: str = "" 17 | address: Address = field(default_factory=Address.empty) 18 | signature: bytes = b"" 19 | block_hash: str = "" 20 | body: str = "" 21 | extra_info: Any = None 22 | 23 | 24 | @dataclass 25 | class NativeAuthValidateResult: 26 | issued: int = 0 27 | expires: int = 0 28 | address: Address = field(default_factory=Address.empty) 29 | signer_address: Address = field(default_factory=Address.empty) 30 | origin: str = "" 31 | extra_info: dict[str, str] = field(default_factory=dict) 32 | -------------------------------------------------------------------------------- /multiversx_sdk/network_providers/__init__.py: -------------------------------------------------------------------------------- 1 | from multiversx_sdk.network_providers.account_awaiter import AccountAwaiter 2 | from multiversx_sdk.network_providers.api_network_provider import ApiNetworkProvider 3 | from multiversx_sdk.network_providers.config import ( 4 | NetworkProviderConfig, 5 | RequestsRetryOptions, 6 | ) 7 | from multiversx_sdk.network_providers.errors import NetworkProviderError 8 | from multiversx_sdk.network_providers.proxy_network_provider import ProxyNetworkProvider 9 | from multiversx_sdk.network_providers.resources import ( 10 | AccountOnNetwork, 11 | AccountStorage, 12 | AccountStorageEntry, 13 | AwaitingOptions, 14 | BlockCoordinates, 15 | BlockOnNetwork, 16 | FungibleTokenMetadata, 17 | GenericResponse, 18 | NetworkConfig, 19 | NetworkStatus, 20 | TokenAmountOnNetwork, 21 | TokensCollectionMetadata, 22 | TransactionCostResponse, 23 | ) 24 | from multiversx_sdk.network_providers.transaction_awaiter import TransactionAwaiter 25 | from multiversx_sdk.network_providers.transaction_decoder import ( 26 | TransactionDecoder, 27 | TransactionMetadata, 28 | ) 29 | 30 | __all__ = [ 31 | "NetworkProviderError", 32 | "GenericResponse", 33 | "ApiNetworkProvider", 34 | "ProxyNetworkProvider", 35 | "TransactionAwaiter", 36 | "TransactionDecoder", 37 | "TransactionMetadata", 38 | "NetworkProviderConfig", 39 | "AccountOnNetwork", 40 | "AccountStorage", 41 | "AccountStorageEntry", 42 | "AwaitingOptions", 43 | "BlockCoordinates", 44 | "BlockOnNetwork", 45 | "FungibleTokenMetadata", 46 | "NetworkConfig", 47 | "NetworkStatus", 48 | "TokenAmountOnNetwork", 49 | "TokensCollectionMetadata", 50 | "TransactionCostResponse", 51 | "AccountAwaiter", 52 | "RequestsRetryOptions", 53 | ] 54 | -------------------------------------------------------------------------------- /multiversx_sdk/network_providers/config.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass, field 2 | from http.client import ( 3 | BAD_GATEWAY, 4 | GATEWAY_TIMEOUT, 5 | INTERNAL_SERVER_ERROR, 6 | SERVICE_UNAVAILABLE, 7 | ) 8 | from typing import Any, Optional 9 | 10 | 11 | @dataclass 12 | class RequestsRetryOptions: 13 | retries: int = 3 14 | backoff_factor: float = 1 15 | status_forcelist: list[int] = field( 16 | default_factory=lambda: [ 17 | INTERNAL_SERVER_ERROR, 18 | BAD_GATEWAY, 19 | SERVICE_UNAVAILABLE, 20 | GATEWAY_TIMEOUT, 21 | ] 22 | ) 23 | 24 | 25 | class NetworkProviderConfig: 26 | def __init__( 27 | self, 28 | client_name: Optional[str] = None, 29 | requests_options: Optional[dict[str, Any]] = None, 30 | requests_retry_options: Optional[RequestsRetryOptions] = None, 31 | ) -> None: 32 | self.client_name = client_name 33 | self.requests_options = requests_options or {} 34 | self.requests_options.setdefault("timeout", 5) 35 | self.requests_options.setdefault("auth", tuple()) 36 | self.requests_retry_options = requests_retry_options if requests_retry_options else RequestsRetryOptions() 37 | -------------------------------------------------------------------------------- /multiversx_sdk/network_providers/constants.py: -------------------------------------------------------------------------------- 1 | DEFAULT_TRANSACTION_AWAITING_POLLING_TIMEOUT_IN_MILLISECONDS = 6000 2 | DEFAULT_TRANSACTION_AWAITING_TIMEOUT_IN_MILLISECONDS = 15 * DEFAULT_TRANSACTION_AWAITING_POLLING_TIMEOUT_IN_MILLISECONDS 3 | DEFAULT_TRANSACTION_AWAITING_PATIENCE_IN_MILLISECONDS = 3000 4 | 5 | DEFAULT_ACCOUNT_AWAITING_POLLING_TIMEOUT_IN_MILLISECONDS = 6000 6 | DEFAULT_ACCOUNT_AWAITING_TIMEOUT_IN_MILLISECONDS = 15 * DEFAULT_TRANSACTION_AWAITING_POLLING_TIMEOUT_IN_MILLISECONDS 7 | DEFAULT_ACCOUNT_AWAITING_PATIENCE_IN_MILLISECONDS = 0 8 | 9 | BASE_USER_AGENT = "multiversx-sdk-py" 10 | UNKNOWN_CLIENT_NAME = "unknown" 11 | ONE_SECOND_IN_MILLISECONDS = 1000 12 | -------------------------------------------------------------------------------- /multiversx_sdk/network_providers/errors.py: -------------------------------------------------------------------------------- 1 | from typing import Any 2 | 3 | 4 | class NetworkProviderError(Exception): 5 | def __init__(self, url: str, data: Any): 6 | super().__init__(f"Url = [{url}], error = {data}") 7 | self.url = url 8 | self.data = data 9 | 10 | 11 | class ExpectedTransactionStatusNotReachedError(Exception): 12 | def __init__(self) -> None: 13 | super().__init__("The expected transaction status was not reached") 14 | 15 | 16 | class ExpectedAccountConditionNotReachedError(Exception): 17 | def __init__(self) -> None: 18 | super().__init__("The expected account condition was not reached") 19 | 20 | 21 | class TransactionFetchingError(NetworkProviderError): 22 | def __init__(self, url: str, error: Any): 23 | super().__init__(url, error) 24 | -------------------------------------------------------------------------------- /multiversx_sdk/network_providers/shared.py: -------------------------------------------------------------------------------- 1 | from typing import Any, Union 2 | 3 | 4 | def convert_tx_hash_to_string(tx_hash: Union[bytes, str]) -> str: 5 | if isinstance(tx_hash, bytes): 6 | return tx_hash.hex() 7 | return tx_hash 8 | 9 | 10 | def convert_boolean_query_params_to_lowercase(query_params: dict[str, Any]) -> dict[str, Any]: 11 | return {key: str(value).lower() if isinstance(value, bool) else value for key, value in query_params.items()} 12 | -------------------------------------------------------------------------------- /multiversx_sdk/network_providers/user_agent.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from typing import Any 3 | 4 | from multiversx_sdk.network_providers.config import NetworkProviderConfig 5 | from multiversx_sdk.network_providers.constants import UNKNOWN_CLIENT_NAME 6 | 7 | logger = logging.getLogger("user_agent") 8 | 9 | 10 | def extend_user_agent(user_agent_prefix: str, config: NetworkProviderConfig): 11 | if config.client_name is None: 12 | logger.info( 13 | "We recommend providing the `client_name` when instantiating a NetworkProvider (e.g. ProxyNetworkProvider, ApiNetworkProvider). This information will be used for metrics collection and improving our services." 14 | ) 15 | 16 | headers: dict[str, Any] = config.requests_options.setdefault("headers", {}) 17 | resolved_client_name = config.client_name or UNKNOWN_CLIENT_NAME 18 | 19 | current_user_agent = headers.get("User-Agent", "") 20 | new_user_agent = ( 21 | f"{current_user_agent} {user_agent_prefix}/{resolved_client_name}" 22 | if current_user_agent 23 | else f"{user_agent_prefix}/{resolved_client_name}" 24 | ) 25 | 26 | headers["User-Agent"] = new_user_agent 27 | -------------------------------------------------------------------------------- /multiversx_sdk/py.typed: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/multiversx/mx-sdk-py/e7994cfd135a4e88ab4282ce07b41d7524ddc34e/multiversx_sdk/py.typed -------------------------------------------------------------------------------- /multiversx_sdk/relayed/__init__.py: -------------------------------------------------------------------------------- 1 | from multiversx_sdk.relayed.relayed_controller import RelayedController 2 | from multiversx_sdk.relayed.relayed_transactions_factory import ( 3 | RelayedTransactionsFactory, 4 | ) 5 | 6 | __all__ = ["RelayedTransactionsFactory", "RelayedController"] 7 | -------------------------------------------------------------------------------- /multiversx_sdk/relayed/errors.py: -------------------------------------------------------------------------------- 1 | class InvalidInnerTransactionError(Exception): 2 | def __init__(self, message: str) -> None: 3 | super().__init__(message) 4 | -------------------------------------------------------------------------------- /multiversx_sdk/relayed/relayed_controller.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from multiversx_sdk.core import Transaction 4 | from multiversx_sdk.core.base_controller import BaseController 5 | from multiversx_sdk.core.interfaces import IAccount 6 | from multiversx_sdk.core.transactions_factory_config import TransactionsFactoryConfig 7 | from multiversx_sdk.relayed.relayed_transactions_factory import ( 8 | RelayedTransactionsFactory, 9 | ) 10 | 11 | logger = logging.getLogger("relayed_controller") 12 | 13 | 14 | class RelayedController(BaseController): 15 | """ 16 | The Relayed Transactions V1 and V2 will soon be deprecated from the network. Please use Relayed Transactions V3 instead. 17 | The transactions are created from the perspective of the relayer. The 'sender' represents the relayer. 18 | """ 19 | 20 | def __init__(self, chain_id: str) -> None: 21 | logger.warning("RelayedController is deprecated. Please use Relayed Transactions V3 instead.") 22 | self.factory = RelayedTransactionsFactory(TransactionsFactoryConfig(chain_id)) 23 | 24 | def create_relayed_v1_transaction( 25 | self, sender: IAccount, nonce: int, inner_transaction: Transaction 26 | ) -> Transaction: 27 | transaction = self.factory.create_relayed_v1_transaction( 28 | inner_transaction=inner_transaction, relayer_address=sender.address 29 | ) 30 | 31 | transaction.nonce = nonce 32 | 33 | self._set_version_and_options_for_hash_signing(sender, transaction) 34 | self._add_extra_gas_limit_if_required(transaction) 35 | self._set_version_and_options_for_guardian(transaction) 36 | transaction.signature = sender.sign_transaction(transaction) 37 | 38 | return transaction 39 | 40 | def create_relayed_v2_transaction( 41 | self, 42 | sender: IAccount, 43 | nonce: int, 44 | inner_transaction: Transaction, 45 | inner_transaction_gas_limit: int, 46 | ) -> Transaction: 47 | transaction = self.factory.create_relayed_v2_transaction( 48 | inner_transaction=inner_transaction, 49 | inner_transaction_gas_limit=inner_transaction_gas_limit, 50 | relayer_address=sender.address, 51 | ) 52 | 53 | transaction.nonce = nonce 54 | 55 | self._set_version_and_options_for_hash_signing(sender, transaction) 56 | self._add_extra_gas_limit_if_required(transaction) 57 | self._set_version_and_options_for_guardian(transaction) 58 | transaction.signature = sender.sign_transaction(transaction) 59 | 60 | return transaction 61 | -------------------------------------------------------------------------------- /multiversx_sdk/smart_contracts/__init__.py: -------------------------------------------------------------------------------- 1 | from multiversx_sdk.smart_contracts.smart_contract_controller import ( 2 | SmartContractController, 3 | ) 4 | from multiversx_sdk.smart_contracts.smart_contract_query import ( 5 | SmartContractQuery, 6 | SmartContractQueryResponse, 7 | ) 8 | from multiversx_sdk.smart_contracts.smart_contract_transactions_factory import ( 9 | SmartContractTransactionsFactory, 10 | ) 11 | from multiversx_sdk.smart_contracts.smart_contract_transactions_outcome_parser import ( 12 | SmartContractTransactionsOutcomeParser, 13 | ) 14 | from multiversx_sdk.smart_contracts.smart_contract_transactions_outcome_parser_types import ( 15 | DeployedSmartContract, 16 | ParsedSmartContractCallOutcome, 17 | SmartContractDeployOutcome, 18 | ) 19 | 20 | __all__ = [ 21 | "SmartContractQuery", 22 | "SmartContractQueryResponse", 23 | "SmartContractTransactionsFactory", 24 | "SmartContractController", 25 | "SmartContractTransactionsOutcomeParser", 26 | "DeployedSmartContract", 27 | "ParsedSmartContractCallOutcome", 28 | "SmartContractDeployOutcome", 29 | ] 30 | -------------------------------------------------------------------------------- /multiversx_sdk/smart_contracts/errors.py: -------------------------------------------------------------------------------- 1 | class SmartContractQueryError(Exception): 2 | def __init__(self, return_code: str, message: str) -> None: 3 | super().__init__(message) 4 | self.return_code = return_code 5 | 6 | 7 | class ArgumentSerializationError(Exception): 8 | def __init__( 9 | self, 10 | message: str = "Unable to encode arguments: unsupported format or missing ABI file", 11 | ) -> None: 12 | super().__init__(message) 13 | -------------------------------------------------------------------------------- /multiversx_sdk/smart_contracts/smart_contract_query.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from multiversx_sdk.core.address import Address 4 | 5 | 6 | class SmartContractQuery: 7 | def __init__( 8 | self, 9 | contract: Address, 10 | function: str, 11 | arguments: list[bytes], 12 | caller: Optional[Address] = None, 13 | value: Optional[int] = None, 14 | ) -> None: 15 | self.contract = contract 16 | self.function = function 17 | self.arguments = arguments 18 | self.caller = caller 19 | self.value = value 20 | 21 | def __eq__(self, other: object) -> bool: 22 | if not isinstance(other, SmartContractQuery): 23 | return False 24 | 25 | return self.__dict__ == other.__dict__ 26 | 27 | 28 | class SmartContractQueryResponse: 29 | def __init__( 30 | self, 31 | function: str, 32 | return_code: str, 33 | return_message: str, 34 | return_data_parts: list[bytes], 35 | ) -> None: 36 | self.function = function 37 | self.return_code = return_code 38 | self.return_message = return_message 39 | self.return_data_parts = return_data_parts 40 | 41 | def __eq__(self, other: object) -> bool: 42 | if not isinstance(other, SmartContractQueryResponse): 43 | return False 44 | 45 | return self.__dict__ == other.__dict__ 46 | -------------------------------------------------------------------------------- /multiversx_sdk/smart_contracts/smart_contract_transactions_outcome_parser_devnet_test.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from multiversx_sdk.network_providers.config import NetworkProviderConfig 4 | from multiversx_sdk.network_providers.proxy_network_provider import ProxyNetworkProvider 5 | from multiversx_sdk.smart_contracts.smart_contract_transactions_outcome_parser import ( 6 | SmartContractTransactionsOutcomeParser, 7 | ) 8 | 9 | 10 | @pytest.mark.networkInteraction 11 | class TestSmartContractDeployDevnet: 12 | parser = SmartContractTransactionsOutcomeParser() 13 | network_config = NetworkProviderConfig(client_name="mx-sdk-py/tests") 14 | provider = ProxyNetworkProvider(url="https://devnet-gateway.multiversx.com", config=network_config) 15 | 16 | def test_parse_deploy_transaction_1(self): 17 | tx_hash = "5d2ff2af8eb3fe7f2acb7e29c0436854b4c6c44de02878b6afff582888024a55" 18 | transaction = self.provider.get_transaction(tx_hash) 19 | parsed = self.parser.parse_deploy(transaction) 20 | 21 | assert parsed.return_code == "ok" 22 | assert len(parsed.contracts) == 1 23 | 24 | contract = parsed.contracts[0] 25 | assert contract.address.to_bech32() == "erd1qqqqqqqqqqqqqpgqpayq2es08gq8798xhnpr0kzgn7495qt5q6uqd7lpwf" 26 | assert contract.owner_address.to_bech32() == "erd1tn62hjp72rznp8vq0lplva5csav6rccpqqdungpxtqz0g2hcq6uq9k4cc6" 27 | assert contract.code_hash == bytes.fromhex("c876625ec34a04445cfd99067777ebe488afdbc6899cd958f4c1d36107ca02d9") 28 | 29 | def test_parse_deploy_transaction_2(self): 30 | tx_hash = "76683e926dad142fc9651afca208487f2a80d327fc87e5c876eec9d028196352" 31 | transaction = self.provider.get_transaction(tx_hash) 32 | parsed = self.parser.parse_deploy(transaction) 33 | 34 | assert parsed.return_code == "execution failed" 35 | assert len(parsed.contracts) == 0 36 | -------------------------------------------------------------------------------- /multiversx_sdk/smart_contracts/smart_contract_transactions_outcome_parser_types.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from typing import Any 3 | 4 | from multiversx_sdk.core import Address 5 | 6 | 7 | @dataclass 8 | class DeployedSmartContract: 9 | address: Address 10 | owner_address: Address 11 | code_hash: bytes 12 | 13 | def __repr__(self) -> str: 14 | return f"DeployedSmartContract(address={self.address.to_bech32()}, owner_address={self.owner_address.to_bech32()}, code_hash={self.code_hash.hex()})" 15 | 16 | 17 | @dataclass 18 | class SmartContractDeployOutcome: 19 | return_code: str 20 | return_message: str 21 | contracts: list[DeployedSmartContract] 22 | 23 | 24 | @dataclass 25 | class ParsedSmartContractCallOutcome: 26 | values: list[Any] 27 | return_code: str 28 | return_message: str 29 | -------------------------------------------------------------------------------- /multiversx_sdk/testutils/mock_transaction_on_network.py: -------------------------------------------------------------------------------- 1 | from multiversx_sdk.core.address import Address 2 | from multiversx_sdk.core.transaction_on_network import ( 3 | SmartContractResult, 4 | TransactionLogs, 5 | TransactionOnNetwork, 6 | ) 7 | from multiversx_sdk.core.transaction_status import TransactionStatus 8 | 9 | 10 | def get_empty_transaction_on_network() -> TransactionOnNetwork: 11 | """ 12 | Returns an 'empty' TransactionOnNetwork. All fields are set to the default values. 13 | **Only** used in tests. 14 | """ 15 | return TransactionOnNetwork( 16 | raw={}, 17 | sender=Address.empty(), 18 | receiver=Address.empty(), 19 | hash=b"", 20 | nonce=-1, 21 | round=-1, 22 | epoch=-1, 23 | timestamp=0, 24 | block_hash=b"", 25 | miniblock_hash=b"", 26 | sender_shard=-1, 27 | receiver_shard=-1, 28 | value=0, 29 | gas_limit=0, 30 | gas_price=0, 31 | function="", 32 | data=b"", 33 | version=-1, 34 | options=-1, 35 | signature=b"", 36 | status=TransactionStatus(""), 37 | smart_contract_results=[get_empty_smart_contract_result()], 38 | logs=get_empty_transaction_logs(), 39 | ) 40 | 41 | 42 | def get_empty_smart_contract_result() -> SmartContractResult: 43 | """ 44 | Returns an 'empty' SmartContractResult. All fields are set to the default values. 45 | **Only** used in tests. 46 | """ 47 | return SmartContractResult( 48 | raw={}, 49 | sender=Address.empty(), 50 | receiver=Address.empty(), 51 | data=b"", 52 | logs=get_empty_transaction_logs(), 53 | ) 54 | 55 | 56 | def get_empty_transaction_logs() -> TransactionLogs: 57 | """ 58 | Returns an 'empty' TransactionLogs. All fields are set to the default values. 59 | **Only** used in tests. 60 | """ 61 | return TransactionLogs(address=Address.empty(), events=[]) 62 | -------------------------------------------------------------------------------- /multiversx_sdk/testutils/testdata/adder.abi.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Adder", 3 | "constructor": { 4 | "inputs": [ 5 | { 6 | "name": "initial_value", 7 | "type": "BigUint" 8 | } 9 | ], 10 | "outputs": [] 11 | }, 12 | "upgradeConstructor": { 13 | "inputs": [ 14 | { 15 | "name": "initial_value", 16 | "type": "BigUint" 17 | } 18 | ], 19 | "outputs": [] 20 | }, 21 | "endpoints": [ 22 | { 23 | "name": "getSum", 24 | "title": "Get Sum", 25 | "mutability": "readonly", 26 | "inputs": [], 27 | "outputs": [ 28 | { 29 | "type": "BigUint" 30 | } 31 | ] 32 | }, 33 | { 34 | "docs": ["Add desired amount to the storage variable."], 35 | "name": "add", 36 | "mutability": "mutable", 37 | "inputs": [ 38 | { 39 | "name": "value", 40 | "type": "BigUint" 41 | } 42 | ], 43 | "outputs": [] 44 | } 45 | ], 46 | "esdtAttributes": [], 47 | "hasCallback": false, 48 | "types": {} 49 | } 50 | -------------------------------------------------------------------------------- /multiversx_sdk/testutils/testdata/adder.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/multiversx/mx-sdk-py/e7994cfd135a4e88ab4282ce07b41d7524ddc34e/multiversx_sdk/testutils/testdata/adder.wasm -------------------------------------------------------------------------------- /multiversx_sdk/testutils/testdata/answer.abi.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "answer", 3 | "constructor": { 4 | "inputs": [], 5 | "outputs": [] 6 | }, 7 | "endpoints": [ 8 | { 9 | "name": "getUltimateAnswer", 10 | "inputs": [], 11 | "outputs": [ 12 | { 13 | "type": "i32" 14 | } 15 | ] 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /multiversx_sdk/testutils/testdata/basic-features.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/multiversx/mx-sdk-py/e7994cfd135a4e88ab4282ce07b41d7524ddc34e/multiversx_sdk/testutils/testdata/basic-features.wasm -------------------------------------------------------------------------------- /multiversx_sdk/testutils/testdata/counted-variadic.abi.json: -------------------------------------------------------------------------------- 1 | { 2 | "endpoints": [ 3 | { 4 | "name": "foo", 5 | "inputs": [ 6 | { 7 | "type": "counted-variadic" 8 | } 9 | ], 10 | "outputs": [] 11 | }, 12 | { 13 | "name": "bar", 14 | "inputs": [ 15 | { 16 | "type": "counted-variadic" 17 | }, 18 | { 19 | "type": "counted-variadic" 20 | } 21 | ], 22 | "outputs": [ 23 | { 24 | "type": "counted-variadic" 25 | }, 26 | { 27 | "type": "counted-variadic" 28 | } 29 | ] 30 | } 31 | ], 32 | "types": { 33 | "Dummy": { 34 | "type": "struct", 35 | "fields": [ 36 | { 37 | "name": "a", 38 | "type": "u32" 39 | } 40 | ] 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /multiversx_sdk/testutils/testwallets/alice.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 4, 3 | "kind": "secretKey", 4 | "id": "0dc10c02-b59b-4bac-9710-6b2cfa4284ba", 5 | "address": "0139472eff6886771a982f3083da5d421f24c29181e63888228dc81ca60d69e1", 6 | "bech32": "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th", 7 | "crypto": { 8 | "ciphertext": "4c41ef6fdfd52c39b1585a875eb3c86d30a315642d0e35bb8205b6372c1882f135441099b11ff76345a6f3a930b5665aaf9f7325a32c8ccd60081c797aa2d538", 9 | "cipherparams": { 10 | "iv": "033182afaa1ebaafcde9ccc68a5eac31" 11 | }, 12 | "cipher": "aes-128-ctr", 13 | "kdf": "scrypt", 14 | "kdfparams": { 15 | "dklen": 32, 16 | "salt": "4903bd0e7880baa04fc4f886518ac5c672cdc745a6bd13dcec2b6c12e9bffe8d", 17 | "n": 4096, 18 | "r": 8, 19 | "p": 1 20 | }, 21 | "mac": "5b4a6f14ab74ba7ca23db6847e28447f0e6a7724ba9664cf425df707a84f5a8b" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /multiversx_sdk/testutils/testwallets/alice.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY for erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th----- 2 | NDEzZjQyNTc1ZjdmMjZmYWQzMzE3YTc3ODc3MTIxMmZkYjgwMjQ1ODUwOTgxZTQ4 3 | YjU4YTRmMjVlMzQ0ZThmOTAxMzk0NzJlZmY2ODg2NzcxYTk4MmYzMDgzZGE1ZDQy 4 | MWYyNGMyOTE4MWU2Mzg4ODIyOGRjODFjYTYwZDY5ZTE= 5 | -----END PRIVATE KEY for erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th----- -------------------------------------------------------------------------------- /multiversx_sdk/testutils/testwallets/bob.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 4, 3 | "kind": "secretKey", 4 | "id": "85fdc8a7-7119-479d-b7fb-ab4413ed038d", 5 | "address": "8049d639e5a6980d1cd2392abcce41029cda74a1563523a202f09641cc2618f8", 6 | "bech32": "erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx", 7 | "crypto": { 8 | "ciphertext": "c2664a31350aaf6a00525560db75c254d0aea65dc466441356c1dd59253cceb9e83eb05730ef3f42a11573c9a0e33dd952d488f00535b35357bb41d127b1eb82", 9 | "cipherparams": { 10 | "iv": "18378411e31f6c4e99f1435d9ab82831" 11 | }, 12 | "cipher": "aes-128-ctr", 13 | "kdf": "scrypt", 14 | "kdfparams": { 15 | "dklen": 32, 16 | "salt": "18304455ac2dbe2a2018bda162bd03ef95b81622e99d8275c34a6d5e6932a68b", 17 | "n": 4096, 18 | "r": 8, 19 | "p": 1 20 | }, 21 | "mac": "23756172195ac483fa29025dc331bc7aa2c139533922a8dc08642eb0a677541f" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /multiversx_sdk/testutils/testwallets/bob.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY for erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx----- 2 | YjhjYTZmODIwM2ZiNGI1NDVhOGU4M2M1Mzg0ZGEwMzNjNDE1ZGIxNTViNTNmYjVi 3 | OGViYTdmZjVhMDM5ZDYzOTgwNDlkNjM5ZTVhNjk4MGQxY2QyMzkyYWJjY2U0MTAy 4 | OWNkYTc0YTE1NjM1MjNhMjAyZjA5NjQxY2MyNjE4Zjg= 5 | -----END PRIVATE KEY for erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx----- -------------------------------------------------------------------------------- /multiversx_sdk/testutils/testwallets/carol.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 4, 3 | "kind": "secretKey", 4 | "id": "65894f35-d142-41d2-9335-6ad02e0ed0be", 5 | "address": "b2a11555ce521e4944e09ab17549d85b487dcd26c84b5017a39e31a3670889ba", 6 | "bech32": "erd1k2s324ww2g0yj38qn2ch2jwctdy8mnfxep94q9arncc6xecg3xaq6mjse8", 7 | "crypto": { 8 | "ciphertext": "bdfb984a1e7c7460f0a289749609730cdc99d7ce85b59305417c2c0f007b2a6aaa7203dd94dbf27315bced39b0b281769fbc70b01e6e57f89ae2f2a9e9100007", 9 | "cipherparams": { 10 | "iv": "258ed2b4dc506b4dc9d274b0449b0eb0" 11 | }, 12 | "cipher": "aes-128-ctr", 13 | "kdf": "scrypt", 14 | "kdfparams": { 15 | "dklen": 32, 16 | "salt": "4f2f5530ce28dc0210962589b908f52714f75c8fb79ff18bdd0024c43c7a220b", 17 | "n": 4096, 18 | "r": 8, 19 | "p": 1 20 | }, 21 | "mac": "f8de52e2627024eaa33f2ee5eadcd3d3815e10dd274ea966dc083d000cc8b258" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /multiversx_sdk/testutils/testwallets/carol.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY for erd1k2s324ww2g0yj38qn2ch2jwctdy8mnfxep94q9arncc6xecg3xaq6mjse8----- 2 | ZTI1M2E1NzFjYTE1M2RjMmFlZTg0NTgxOWY3NGJjYzk3NzNiMDU4NmVkZWFkMTVh 3 | OTRjYjcyMzVhNTAyNzQzNmIyYTExNTU1Y2U1MjFlNDk0NGUwOWFiMTc1NDlkODVi 4 | NDg3ZGNkMjZjODRiNTAxN2EzOWUzMWEzNjcwODg5YmE= 5 | -----END PRIVATE KEY for erd1k2s324ww2g0yj38qn2ch2jwctdy8mnfxep94q9arncc6xecg3xaq6mjse8----- -------------------------------------------------------------------------------- /multiversx_sdk/testutils/testwallets/frank.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY for erd1kdl46yctawygtwg2k462307dmz2v55c605737dp3zkxh04sct7asqylhyv----- 2 | NzgyMGE5MTE4OWM3MjdjMDc1YjQ5OWM1MjNmMGRjNDAzZmVkMzc5OTBlYzhkMzdm 3 | NGJkZWNiOGI2OGZkMmE5N2IzN2Y1ZDEzMGJlYjg4ODViOTBhYjU3NGE4YmZjZGQ4 4 | OTRjYTUzMWE3ZDNkMWYzNDMxMTU4ZDc3ZDYxODVmYmI= 5 | -----END PRIVATE KEY for erd1kdl46yctawygtwg2k462307dmz2v55c605737dp3zkxh04sct7asqylhyv----- 6 | -------------------------------------------------------------------------------- /multiversx_sdk/testutils/testwallets/grace.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY for erd1r69gk66fmedhhcg24g2c5kn2f2a5k4kvpr6jfw67dn2lyydd8cfswy6ede----- 2 | ODY5Yzk4ZGYwYjExZmRhMDgwNGQ3ODM1NGVjYTQzMWY4N2E2ZTkxMGZhZTE0MDgx 3 | ZjczOGMzMTZhMjVlYTQwOTFlOGE4YjZiNDlkZTViN2JlMTBhYWExNThhNWE2YTRh 4 | YmI0YjU2Y2MwOGY1MjRiYjVlNmNkNWYyMTFhZDNlMTM= 5 | -----END PRIVATE KEY for erd1r69gk66fmedhhcg24g2c5kn2f2a5k4kvpr6jfw67dn2lyydd8cfswy6ede----- 6 | -------------------------------------------------------------------------------- /multiversx_sdk/testutils/testwallets/multipleUserKeys.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY for erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th----- 2 | NDEzZjQyNTc1ZjdmMjZmYWQzMzE3YTc3ODc3MTIxMmZkYjgwMjQ1ODUwOTgxZTQ4 3 | YjU4YTRmMjVlMzQ0ZThmOTAxMzk0NzJlZmY2ODg2NzcxYTk4MmYzMDgzZGE1ZDQy 4 | MWYyNGMyOTE4MWU2Mzg4ODIyOGRjODFjYTYwZDY5ZTE= 5 | -----END PRIVATE KEY for erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th----- 6 | -----BEGIN PRIVATE KEY for erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx----- 7 | YjhjYTZmODIwM2ZiNGI1NDVhOGU4M2M1Mzg0ZGEwMzNjNDE1ZGIxNTViNTNmYjVi 8 | OGViYTdmZjVhMDM5ZDYzOTgwNDlkNjM5ZTVhNjk4MGQxY2QyMzkyYWJjY2U0MTAy 9 | OWNkYTc0YTE1NjM1MjNhMjAyZjA5NjQxY2MyNjE4Zjg= 10 | -----END PRIVATE KEY for erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx----- 11 | -----BEGIN PRIVATE KEY for erd1k2s324ww2g0yj38qn2ch2jwctdy8mnfxep94q9arncc6xecg3xaq6mjse8----- 12 | ZTI1M2E1NzFjYTE1M2RjMmFlZTg0NTgxOWY3NGJjYzk3NzNiMDU4NmVkZWFkMTVh 13 | OTRjYjcyMzVhNTAyNzQzNmIyYTExNTU1Y2U1MjFlNDk0NGUwOWFiMTc1NDlkODVi 14 | NDg3ZGNkMjZjODRiNTAxN2EzOWUzMWEzNjcwODg5YmE= 15 | -----END PRIVATE KEY for erd1k2s324ww2g0yj38qn2ch2jwctdy8mnfxep94q9arncc6xecg3xaq6mjse8----- 16 | -------------------------------------------------------------------------------- /multiversx_sdk/testutils/testwallets/multipleValidatorKeys.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY for f8910e47cf9464777c912e6390758bb39715fffcb861b184017920e4a807b42553f2f21e7f3914b81bcf58b66a72ab16d97013ae1cff807cefc977ef8cbf116258534b9e46d19528042d16ef8374404a89b184e0a4ee18c77c49e454d04eae8d----- 2 | N2MxOWJmM2EwYzU3Y2RkMWZiMDhlNDYwN2NlYmFhMzY0N2Q2YjkyNjFiNDY5M2Y2 3 | MWU5NmU1NGIyMThkNDQyYQ== 4 | -----END PRIVATE KEY for f8910e47cf9464777c912e6390758bb39715fffcb861b184017920e4a807b42553f2f21e7f3914b81bcf58b66a72ab16d97013ae1cff807cefc977ef8cbf116258534b9e46d19528042d16ef8374404a89b184e0a4ee18c77c49e454d04eae8d----- 5 | -----BEGIN PRIVATE KEY for 1b4e60e6d100cdf234d3427494dac55fbac49856cadc86bcb13a01b9bb05a0d9143e86c186c948e7ae9e52427c9523102efe9019a2a9c06db02993f2e3e6756576ae5a3ec7c235d548bc79de1a6990e1120ae435cb48f7fc436c9f9098b92a0d----- 6 | MzAzNGIxZDU4NjI4YTg0Mjk4NGRhMGM3MGRhMGI1YTI1MWViYjJhZWJmNTFhZmM1 7 | YjU4NmUyODM5YjVlNTI2Mw== 8 | -----END PRIVATE KEY for 1b4e60e6d100cdf234d3427494dac55fbac49856cadc86bcb13a01b9bb05a0d9143e86c186c948e7ae9e52427c9523102efe9019a2a9c06db02993f2e3e6756576ae5a3ec7c235d548bc79de1a6990e1120ae435cb48f7fc436c9f9098b92a0d----- 9 | -----BEGIN PRIVATE KEY for e5dc552b4b170cdec4405ff8f9af20313bf0e2756d06c35877b6fbcfa6b354a7b3e2d439ea87999befb09a8fa1b3f014e57ec747bf738c4199338fcd4a87b373dd62f5c8329f1f5f245956bbb06685596a2e83dc38befa63e4a2b5c4ce408506----- 10 | ZGU3ZTFiMzg1ZWRiYjBlMWU4ZjlmYzI1ZDkxYmQ4ZWVkNzFhMWRhN2NhYWI3MzJl 11 | NmI0N2E0ODA0MmQ4NTIzZA== 12 | -----END PRIVATE KEY for e5dc552b4b170cdec4405ff8f9af20313bf0e2756d06c35877b6fbcfa6b354a7b3e2d439ea87999befb09a8fa1b3f014e57ec747bf738c4199338fcd4a87b373dd62f5c8329f1f5f245956bbb06685596a2e83dc38befa63e4a2b5c4ce408506----- 13 | -----BEGIN PRIVATE KEY for 12773304cb718250edd89770cedcbf675ccdb7fe2b30bd3185ca65ffa0d516879768ed03f92e41a6e5bc5340b78a9d02655e3b727c79730ead791fb68eaa02b84e1be92a816a9604a1ab9a6d3874b638487e2145239438a4bafac3889348d405----- 14 | OGViZWIwN2QyOTZhZDI1Mjk0MDBiNDA2ODdhNzQxYTEzNWY4MzU3Zjc5ZjM5ZmNi 15 | Mjg5NGE2Zjk3MDNhNTgxNg== 16 | -----END PRIVATE KEY for 12773304cb718250edd89770cedcbf675ccdb7fe2b30bd3185ca65ffa0d516879768ed03f92e41a6e5bc5340b78a9d02655e3b727c79730ead791fb68eaa02b84e1be92a816a9604a1ab9a6d3874b638487e2145239438a4bafac3889348d405----- 17 | -------------------------------------------------------------------------------- /multiversx_sdk/testutils/testwallets/validatorKey00.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY for e7beaa95b3877f47348df4dd1cb578a4f7cabf7a20bfeefe5cdd263878ff132b765e04fef6f40c93512b666c47ed7719b8902f6c922c04247989b7137e837cc81a62e54712471c97a2ddab75aa9c2f58f813ed4c0fa722bde0ab718bff382208----- 2 | N2NmZjk5YmQ2NzE1MDJkYjdkMTViYzhhYmMwYzlhODA0ZmI5MjU0MDZmYmRkNTBm 3 | MWU0YzE3YTRjZDc3NDI0Nw== 4 | -----END PRIVATE KEY for e7beaa95b3877f47348df4dd1cb578a4f7cabf7a20bfeefe5cdd263878ff132b765e04fef6f40c93512b666c47ed7719b8902f6c922c04247989b7137e837cc81a62e54712471c97a2ddab75aa9c2f58f813ed4c0fa722bde0ab718bff382208----- 5 | -------------------------------------------------------------------------------- /multiversx_sdk/testutils/testwallets/validators.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY for f8910e47cf9464777c912e6390758bb39715fffcb861b184017920e4a807b42553f2f21e7f3914b81bcf58b66a72ab16d97013ae1cff807cefc977ef8cbf116258534b9e46d19528042d16ef8374404a89b184e0a4ee18c77c49e454d04eae8d----- 2 | N2MxOWJmM2EwYzU3Y2RkMWZiMDhlNDYwN2NlYmFhMzY0N2Q2YjkyNjFiNDY5M2Y2 3 | MWU5NmU1NGIyMThkNDQyYQ== 4 | -----END PRIVATE KEY for f8910e47cf9464777c912e6390758bb39715fffcb861b184017920e4a807b42553f2f21e7f3914b81bcf58b66a72ab16d97013ae1cff807cefc977ef8cbf116258534b9e46d19528042d16ef8374404a89b184e0a4ee18c77c49e454d04eae8d----- 5 | -----BEGIN PRIVATE KEY for 1b4e60e6d100cdf234d3427494dac55fbac49856cadc86bcb13a01b9bb05a0d9143e86c186c948e7ae9e52427c9523102efe9019a2a9c06db02993f2e3e6756576ae5a3ec7c235d548bc79de1a6990e1120ae435cb48f7fc436c9f9098b92a0d----- 6 | MzAzNGIxZDU4NjI4YTg0Mjk4NGRhMGM3MGRhMGI1YTI1MWViYjJhZWJmNTFhZmM1 7 | YjU4NmUyODM5YjVlNTI2Mw== 8 | -----END PRIVATE KEY for 1b4e60e6d100cdf234d3427494dac55fbac49856cadc86bcb13a01b9bb05a0d9143e86c186c948e7ae9e52427c9523102efe9019a2a9c06db02993f2e3e6756576ae5a3ec7c235d548bc79de1a6990e1120ae435cb48f7fc436c9f9098b92a0d----- 9 | -------------------------------------------------------------------------------- /multiversx_sdk/testutils/testwallets/withDummyMnemonic.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 4, 3 | "id": "5b448dbc-5c72-4d83-8038-938b1f8dff19", 4 | "kind": "mnemonic", 5 | "crypto": { 6 | "ciphertext": "6d70fbdceba874f56f15af4b1d060223799288cfc5d276d9ebb91732f5a38c3c59f83896fa7e7eb6a04c05475a6fe4d154de9b9441864c507abd0eb6987dac521b64c0c82783a3cd1e09270cd6cb5ae493f9af694b891253ac1f1ffded68b5ef39c972307e3c33a8354337540908acc795d4df72298dda1ca28ac920983e6a39a01e2bc988bd0b21f864c6de8b5356d11e4b77bc6f75ef", 7 | "cipherparams": { 8 | "iv": "2da5620906634972d9a623bc249d63d4" 9 | }, 10 | "cipher": "aes-128-ctr", 11 | "kdf": "scrypt", 12 | "kdfparams": { 13 | "dklen": 32, 14 | "salt": "aa9e0ba6b188703071a582c10e5331f2756279feb0e2768f1ba0fd38ec77f035", 15 | "n": 4096, 16 | "r": 8, 17 | "p": 1 18 | }, 19 | "mac": "5bc1b20b6d903b8ef3273eedf028112d65eaf85a5ef4215917c1209ec2df715a" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /multiversx_sdk/testutils/testwallets/withoutKind.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 4, 3 | "id": "0dc10c02-b59b-4bac-9710-6b2cfa4284ba", 4 | "address": "0139472eff6886771a982f3083da5d421f24c29181e63888228dc81ca60d69e1", 5 | "bech32": "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th", 6 | "crypto": { 7 | "ciphertext": "4c41ef6fdfd52c39b1585a875eb3c86d30a315642d0e35bb8205b6372c1882f135441099b11ff76345a6f3a930b5665aaf9f7325a32c8ccd60081c797aa2d538", 8 | "cipherparams": { 9 | "iv": "033182afaa1ebaafcde9ccc68a5eac31" 10 | }, 11 | "cipher": "aes-128-ctr", 12 | "kdf": "scrypt", 13 | "kdfparams": { 14 | "dklen": 32, 15 | "salt": "4903bd0e7880baa04fc4f886518ac5c672cdc745a6bd13dcec2b6c12e9bffe8d", 16 | "n": 4096, 17 | "r": 8, 18 | "p": 1 19 | }, 20 | "mac": "5b4a6f14ab74ba7ca23db6847e28447f0e6a7724ba9664cf425df707a84f5a8b" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /multiversx_sdk/testutils/utils.py: -------------------------------------------------------------------------------- 1 | import base64 2 | from typing import List 3 | 4 | EGLD_NUM_DECIMALS = 18 5 | 6 | 7 | def create_account_egld_balance(egld: int) -> int: 8 | value_as_str = str(egld) + ("0" * EGLD_NUM_DECIMALS) 9 | return int(value_as_str) 10 | 11 | 12 | def base64_topics_to_bytes(topics: List[str]) -> List[bytes]: 13 | return [base64.b64decode(topic) for topic in topics] 14 | -------------------------------------------------------------------------------- /multiversx_sdk/testutils/wallets.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | from typing import Dict 3 | 4 | from multiversx_sdk.wallet.user_pem import UserPEM 5 | 6 | alice_pem = Path(__file__).parent / "testwallets" / "alice.pem" 7 | bob_pem = Path(__file__).parent / "testwallets" / "bob.pem" 8 | frank_pem = Path(__file__).parent / "testwallets" / "frank.pem" 9 | grace_pem = Path(__file__).parent / "testwallets" / "grace.pem" 10 | carol_pem = Path(__file__).parent / "testwallets" / "carol.pem" 11 | 12 | 13 | def load_wallets() -> Dict[str, UserPEM]: 14 | wallets: Dict[str, UserPEM] = {} 15 | 16 | alice = UserPEM.from_file(Path(alice_pem)) 17 | wallets["alice"] = alice 18 | 19 | bob = UserPEM.from_file(Path(bob_pem)) 20 | wallets["bob"] = bob 21 | 22 | carol = UserPEM.from_file(Path(carol_pem)) 23 | wallets["carol"] = carol 24 | 25 | frank = UserPEM.from_file(Path(frank_pem)) 26 | wallets["frank"] = frank 27 | 28 | grace = UserPEM.from_file(Path(grace_pem)) 29 | wallets["grace"] = grace 30 | 31 | return wallets 32 | -------------------------------------------------------------------------------- /multiversx_sdk/token_management/__init__.py: -------------------------------------------------------------------------------- 1 | from multiversx_sdk.token_management.token_management_controller import ( 2 | TokenManagementController, 3 | ) 4 | from multiversx_sdk.token_management.token_management_transactions_factory import ( 5 | TokenManagementTransactionsFactory, 6 | TokenType, 7 | ) 8 | from multiversx_sdk.token_management.token_management_transactions_outcome_parser import ( 9 | TokenManagementTransactionsOutcomeParser, 10 | ) 11 | from multiversx_sdk.token_management.token_management_transactions_outcome_parser_types import ( 12 | AddQuantityOutcome, 13 | BurnOutcome, 14 | BurnQuantityOutcome, 15 | ChangeTokenToDynamicOutcome, 16 | FreezeOutcome, 17 | IssueFungibleOutcome, 18 | IssueNonFungibleOutcome, 19 | IssueSemiFungibleOutcome, 20 | MetadataRecreateOutcome, 21 | MintOutcome, 22 | ModifyCreatorOutcome, 23 | ModifyRoyaltiesOutcome, 24 | NFTCreateOutcome, 25 | PauseOutcome, 26 | RegisterAndSetAllRolesOutcome, 27 | RegisterDynamicOutcome, 28 | RegisterMetaEsdtOutcome, 29 | SetNewUrisOutcome, 30 | SetSpecialRoleOutcome, 31 | UnFreezeOutcome, 32 | UnPauseOutcome, 33 | UpdateAttributesOutcome, 34 | UpdateMetadataOutcome, 35 | WipeOutcome, 36 | ) 37 | 38 | __all__ = [ 39 | "TokenManagementTransactionsFactory", 40 | "TokenType", 41 | "TokenManagementTransactionsOutcomeParser", 42 | "TokenManagementController", 43 | "AddQuantityOutcome", 44 | "BurnOutcome", 45 | "BurnQuantityOutcome", 46 | "FreezeOutcome", 47 | "IssueFungibleOutcome", 48 | "IssueNonFungibleOutcome", 49 | "IssueSemiFungibleOutcome", 50 | "MintOutcome", 51 | "NFTCreateOutcome", 52 | "PauseOutcome", 53 | "RegisterAndSetAllRolesOutcome", 54 | "RegisterMetaEsdtOutcome", 55 | "SetSpecialRoleOutcome", 56 | "UnFreezeOutcome", 57 | "UnPauseOutcome", 58 | "UpdateAttributesOutcome", 59 | "WipeOutcome", 60 | "ModifyRoyaltiesOutcome", 61 | "SetNewUrisOutcome", 62 | "ModifyCreatorOutcome", 63 | "UpdateMetadataOutcome", 64 | "MetadataRecreateOutcome", 65 | "ChangeTokenToDynamicOutcome", 66 | "RegisterDynamicOutcome", 67 | ] 68 | -------------------------------------------------------------------------------- /multiversx_sdk/transfers/__init__.py: -------------------------------------------------------------------------------- 1 | from multiversx_sdk.transfers.transfer_transactions_factory import ( 2 | TransferTransactionsFactory, 3 | ) 4 | from multiversx_sdk.transfers.transfers_controller import TransfersController 5 | 6 | __all__ = ["TransferTransactionsFactory", "TransfersController"] 7 | -------------------------------------------------------------------------------- /multiversx_sdk/validators/__init__.py: -------------------------------------------------------------------------------- 1 | from multiversx_sdk.validators.validators_controller import ValidatorsController 2 | from multiversx_sdk.validators.validators_signers import ValidatorsSigners 3 | from multiversx_sdk.validators.validators_transactions_factory import ( 4 | ValidatorsTransactionsFactory, 5 | ) 6 | 7 | __all__ = [ 8 | "ValidatorsTransactionsFactory", 9 | "ValidatorsController", 10 | "ValidatorsSigners", 11 | ] 12 | -------------------------------------------------------------------------------- /multiversx_sdk/validators/validators_signers.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | 3 | from multiversx_sdk.wallet.validator_keys import ValidatorPublicKey 4 | from multiversx_sdk.wallet.validator_pem import ValidatorPEM 5 | from multiversx_sdk.wallet.validator_signer import ValidatorSigner 6 | 7 | 8 | class ValidatorsSigners: 9 | def __init__(self, validator_signers: list[ValidatorSigner]): 10 | self.signers = validator_signers 11 | 12 | @staticmethod 13 | def new_from_pem(file: Path) -> "ValidatorsSigners": 14 | validator_pem_files = ValidatorPEM.from_file_all(file) 15 | signers = [ValidatorSigner(pem.secret_key) for pem in validator_pem_files] 16 | return ValidatorsSigners(signers) 17 | 18 | def get_num_of_nodes(self) -> int: 19 | return len(self.signers) 20 | 21 | def get_signers(self) -> list[ValidatorSigner]: 22 | return self.signers 23 | 24 | def get_public_keys(self) -> list[ValidatorPublicKey]: 25 | return [signer.get_pubkey() for signer in self.signers] 26 | -------------------------------------------------------------------------------- /multiversx_sdk/wallet/__init__.py: -------------------------------------------------------------------------------- 1 | from multiversx_sdk.wallet.keypair import KeyPair 2 | from multiversx_sdk.wallet.mnemonic import Mnemonic 3 | from multiversx_sdk.wallet.user_keys import UserPublicKey, UserSecretKey 4 | from multiversx_sdk.wallet.user_pem import UserPEM 5 | from multiversx_sdk.wallet.user_signer import UserSigner 6 | from multiversx_sdk.wallet.user_verifer import UserVerifier 7 | from multiversx_sdk.wallet.user_wallet import UserWallet 8 | from multiversx_sdk.wallet.validator_keys import ValidatorPublicKey, ValidatorSecretKey 9 | from multiversx_sdk.wallet.validator_pem import ValidatorPEM 10 | from multiversx_sdk.wallet.validator_signer import ValidatorSigner 11 | from multiversx_sdk.wallet.validator_verifier import ValidatorVerifier 12 | 13 | __all__ = [ 14 | "UserSigner", 15 | "Mnemonic", 16 | "UserSecretKey", 17 | "UserPublicKey", 18 | "ValidatorSecretKey", 19 | "ValidatorPublicKey", 20 | "UserVerifier", 21 | "ValidatorSigner", 22 | "ValidatorVerifier", 23 | "ValidatorPEM", 24 | "UserWallet", 25 | "UserPEM", 26 | "KeyPair", 27 | ] 28 | -------------------------------------------------------------------------------- /multiversx_sdk/wallet/constants.py: -------------------------------------------------------------------------------- 1 | BIP39_STRENGTH: int = 256 2 | BIP39_LANGUAGE: str = "english" 3 | USER_SEED_LENGTH: int = 32 4 | USER_PUBKEY_LENGTH: int = 32 5 | VALIDATOR_SECRETKEY_LENGTH: int = 32 6 | VALIDATOR_PUBKEY_LENGTH: int = 96 7 | -------------------------------------------------------------------------------- /multiversx_sdk/wallet/core.py: -------------------------------------------------------------------------------- 1 | import hashlib 2 | import hmac 3 | import struct 4 | 5 | # TODO: Rename "core.py" to "bip39.py". 6 | 7 | BIP39_SALT_MODIFIER = "mnemonic" 8 | BIP39_PBKDF2_ROUNDS = 2048 9 | BIP32_SEED_MODIFIER = b"ed25519 seed" 10 | BIP39_DERIVATION_PATH = [44, 508, 0, 0] 11 | HARDENED_OFFSET = 0x80000000 12 | 13 | 14 | def derive_keys(mnemonic: str, address_index: int = 0): 15 | bip39seed = mnemonic_to_bip39seed(mnemonic) 16 | secret_key = bip39seed_to_secret_key(bip39seed, address_index) 17 | return secret_key 18 | 19 | 20 | # References: 21 | # https://github.com/trezor/python-mnemonic/blob/master/mnemonic/mnemonic.py 22 | # https://ethereum.stackexchange.com/a/72871/59887 23 | def mnemonic_to_bip39seed(mnemonic: str, passphrase: str = ""): 24 | passphrase = BIP39_SALT_MODIFIER + passphrase 25 | mnemonic_bytes = mnemonic.encode("utf-8") 26 | passphrase_bytes = passphrase.encode("utf-8") 27 | stretched = hashlib.pbkdf2_hmac("sha512", mnemonic_bytes, passphrase_bytes, BIP39_PBKDF2_ROUNDS) 28 | return stretched[:64] 29 | 30 | 31 | # References: 32 | # https://ethereum.stackexchange.com/a/72871/59887s 33 | # https://github.com/alepop/ed25519-hd-key/blob/master/src/index.ts#L22 34 | def bip39seed_to_master_key(seed: bytes): 35 | hashed = hmac.new(BIP32_SEED_MODIFIER, seed, hashlib.sha512).digest() 36 | key, chain_code = hashed[:32], hashed[32:] 37 | return key, chain_code 38 | 39 | 40 | # Reference: https://github.com/alepop/ed25519-hd-key 41 | def bip39seed_to_secret_key(seed: bytes, address_index: int = 0): 42 | key, chain_code = bip39seed_to_master_key(seed) 43 | 44 | for segment in BIP39_DERIVATION_PATH + [address_index]: 45 | key, chain_code = _ckd_priv(key, chain_code, segment + HARDENED_OFFSET) 46 | 47 | return key 48 | 49 | 50 | # Reference: https://github.com/alepop/ed25519-hd-key 51 | def _ckd_priv(key: bytes, chain_code: bytes, index: int): 52 | index_buffer = struct.pack(">I", index) 53 | data = bytearray([0]) + bytearray(key) + bytearray(index_buffer) 54 | hashed = hmac.new(chain_code, data, hashlib.sha512).digest() 55 | key, chain_code = hashed[:32], hashed[32:] 56 | 57 | return key, chain_code 58 | -------------------------------------------------------------------------------- /multiversx_sdk/wallet/crypto/__init__.py: -------------------------------------------------------------------------------- 1 | from multiversx_sdk.wallet.crypto import decryptor, encryptor 2 | from multiversx_sdk.wallet.crypto.encrypted_data import EncryptedData 3 | from multiversx_sdk.wallet.crypto.randomness import Randomness 4 | 5 | __all__ = [ 6 | "EncryptedData", 7 | "Randomness", 8 | "encryptor", 9 | "decryptor", 10 | ] 11 | -------------------------------------------------------------------------------- /multiversx_sdk/wallet/crypto/constants.py: -------------------------------------------------------------------------------- 1 | CIPHER_ALGORITHM_AES_128_CTR = "aes-128-ctr" 2 | KEY_DERIVATION_FUNCTION_SCRYPT = "scrypt" 3 | RANDOM_SALT_LENGTH = 32 4 | RANDOM_IV_LENGTH = 16 5 | ENCRYPTOR_VERSION = 4 6 | -------------------------------------------------------------------------------- /multiversx_sdk/wallet/crypto/decryptor.py: -------------------------------------------------------------------------------- 1 | from cryptography.hazmat.backends import default_backend 2 | from cryptography.hazmat.primitives import hashes, hmac 3 | from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes 4 | from cryptography.hazmat.primitives.kdf.scrypt import Scrypt 5 | 6 | from multiversx_sdk.wallet.crypto.constants import ( 7 | CIPHER_ALGORITHM_AES_128_CTR, 8 | KEY_DERIVATION_FUNCTION_SCRYPT, 9 | ) 10 | from multiversx_sdk.wallet.crypto.encrypted_data import EncryptedData 11 | from multiversx_sdk.wallet.errors import ( 12 | InvalidKeystoreFilePasswordError, 13 | UnknownCipherError, 14 | UnknownDerivationFunctionError, 15 | ) 16 | 17 | 18 | def decrypt(encrypted_data: EncryptedData, password: str) -> bytes: 19 | """ 20 | Also see: https://github.com/multiversx/mx-sdk-js-wallet/blob/main/src/crypto/decryptor.ts 21 | """ 22 | backend = default_backend() 23 | 24 | if encrypted_data.kdf != KEY_DERIVATION_FUNCTION_SCRYPT: 25 | raise UnknownDerivationFunctionError() 26 | 27 | if encrypted_data.cipher != CIPHER_ALGORITHM_AES_128_CTR: 28 | raise UnknownCipherError(name=encrypted_data.cipher) 29 | 30 | salt = bytes.fromhex(encrypted_data.salt) 31 | iv = bytes.fromhex(encrypted_data.iv) 32 | ciphertext = bytes.fromhex(encrypted_data.ciphertext) 33 | 34 | kdf = Scrypt( 35 | salt=salt, 36 | length=encrypted_data.kdfparams.dklen, 37 | n=encrypted_data.kdfparams.n, 38 | r=encrypted_data.kdfparams.r, 39 | p=encrypted_data.kdfparams.p, 40 | backend=backend, 41 | ) 42 | 43 | derived_key = kdf.derive(bytes(password.encode())) 44 | derived_key_first_half = derived_key[0:16] 45 | derived_key_second_half = derived_key[16:32] 46 | 47 | h = hmac.HMAC(derived_key_second_half, hashes.SHA256(), backend=backend) 48 | h.update(ciphertext) 49 | computed_mac = h.finalize() 50 | actual_mac = bytes.fromhex(encrypted_data.mac) 51 | 52 | if computed_mac != actual_mac: 53 | raise InvalidKeystoreFilePasswordError() 54 | 55 | cipher = Cipher(algorithms.AES(derived_key_first_half), modes.CTR(iv), backend=backend) 56 | decryptor = cipher.decryptor() 57 | data = decryptor.update(ciphertext) + decryptor.finalize() 58 | 59 | return data 60 | -------------------------------------------------------------------------------- /multiversx_sdk/wallet/crypto/encrypted_data.py: -------------------------------------------------------------------------------- 1 | from typing import Any 2 | 3 | 4 | class KeyDerivationParams: 5 | def __init__(self, n: int, r: int, p: int, dklen: int): 6 | # numIterations 7 | self.n = n 8 | # memFactor 9 | self.r = r 10 | # pFactor 11 | self.p = p 12 | self.dklen = dklen 13 | 14 | 15 | class EncryptedData: 16 | def __init__( 17 | self, 18 | id: str, 19 | version: int, 20 | cipher: str, 21 | ciphertext: str, 22 | iv: str, 23 | kdf: str, 24 | kdfparams: KeyDerivationParams, 25 | salt: str, 26 | mac: str, 27 | ): 28 | self.id = id 29 | self.version = version 30 | self.cipher = cipher 31 | self.ciphertext = ciphertext 32 | self.iv = iv 33 | self.kdf = kdf 34 | self.kdfparams = kdfparams 35 | self.salt = salt 36 | self.mac = mac 37 | 38 | @classmethod 39 | def from_keyfile_object(cls, keyfile_object: dict[str, Any]) -> "EncryptedData": 40 | return cls( 41 | id=keyfile_object["id"], 42 | version=keyfile_object["version"], 43 | cipher=keyfile_object["crypto"]["cipher"], 44 | ciphertext=keyfile_object["crypto"]["ciphertext"], 45 | iv=keyfile_object["crypto"]["cipherparams"]["iv"], 46 | kdf=keyfile_object["crypto"]["kdf"], 47 | kdfparams=KeyDerivationParams( 48 | n=keyfile_object["crypto"]["kdfparams"]["n"], 49 | r=keyfile_object["crypto"]["kdfparams"]["r"], 50 | p=keyfile_object["crypto"]["kdfparams"]["p"], 51 | dklen=keyfile_object["crypto"]["kdfparams"]["dklen"], 52 | ), 53 | salt=keyfile_object["crypto"]["kdfparams"]["salt"], 54 | mac=keyfile_object["crypto"]["mac"], 55 | ) 56 | -------------------------------------------------------------------------------- /multiversx_sdk/wallet/crypto/encryptor.py: -------------------------------------------------------------------------------- 1 | from cryptography.hazmat.backends import default_backend 2 | from cryptography.hazmat.primitives import hashes, hmac 3 | from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes 4 | from cryptography.hazmat.primitives.kdf.scrypt import Scrypt 5 | 6 | from multiversx_sdk.wallet.crypto.constants import ( 7 | CIPHER_ALGORITHM_AES_128_CTR, 8 | ENCRYPTOR_VERSION, 9 | KEY_DERIVATION_FUNCTION_SCRYPT, 10 | ) 11 | from multiversx_sdk.wallet.crypto.encrypted_data import ( 12 | EncryptedData, 13 | KeyDerivationParams, 14 | ) 15 | from multiversx_sdk.wallet.interfaces import IRandomness 16 | 17 | 18 | def encrypt(data: bytes, password: str, randomness: IRandomness) -> EncryptedData: 19 | """ 20 | Also see: https://github.com/multiversx/mx-sdk-js-wallet/blob/main/src/crypto/encryptor.ts 21 | """ 22 | backend = default_backend() 23 | 24 | kdParams = KeyDerivationParams(n=4096, r=8, p=1, dklen=32) 25 | kdf = Scrypt( 26 | salt=randomness.salt, 27 | length=kdParams.dklen, 28 | n=kdParams.n, 29 | r=kdParams.r, 30 | p=kdParams.p, 31 | backend=backend, 32 | ) 33 | derived_key = kdf.derive(bytes(password.encode())) 34 | derived_key_first_half = derived_key[0:16] 35 | derived_key_second_half = derived_key[16:32] 36 | cipher = Cipher( 37 | algorithms.AES(derived_key_first_half), 38 | modes.CTR(randomness.iv), 39 | backend=backend, 40 | ) 41 | 42 | encryptor = cipher.encryptor() 43 | ciphertext = encryptor.update(data) + encryptor.finalize() 44 | 45 | h = hmac.HMAC(derived_key_second_half, hashes.SHA256(), backend=default_backend()) 46 | h.update(ciphertext) 47 | mac = h.finalize() 48 | 49 | encrypted_data = EncryptedData( 50 | id=randomness.id, 51 | version=ENCRYPTOR_VERSION, 52 | cipher=CIPHER_ALGORITHM_AES_128_CTR, 53 | ciphertext=ciphertext.hex(), 54 | iv=randomness.iv.hex(), 55 | kdf=KEY_DERIVATION_FUNCTION_SCRYPT, 56 | kdfparams=kdParams, 57 | salt=randomness.salt.hex(), 58 | mac=mac.hex(), 59 | ) 60 | 61 | return encrypted_data 62 | -------------------------------------------------------------------------------- /multiversx_sdk/wallet/crypto/randomness.py: -------------------------------------------------------------------------------- 1 | import os 2 | from typing import Optional 3 | from uuid import uuid4 4 | 5 | from multiversx_sdk.wallet.crypto.constants import RANDOM_IV_LENGTH, RANDOM_SALT_LENGTH 6 | 7 | 8 | class Randomness: 9 | def __init__( 10 | self, 11 | salt: Optional[bytes] = None, 12 | iv: Optional[bytes] = None, 13 | id: Optional[str] = None, 14 | ): 15 | self.salt = salt or os.urandom(RANDOM_SALT_LENGTH) 16 | self.iv = iv or os.urandom(RANDOM_IV_LENGTH) 17 | self.id = id or str(uuid4()) 18 | -------------------------------------------------------------------------------- /multiversx_sdk/wallet/errors.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | 3 | 4 | class UnknownCipherError(Exception): 5 | def __init__(self, name: str): 6 | super().__init__(f"Unknown cipher: {name}.") 7 | 8 | 9 | class UnknownDerivationFunctionError(Exception): 10 | def __init__(self): 11 | super().__init__("Unknown key derivation function.") 12 | 13 | 14 | class InvalidKeystoreFilePasswordError(Exception): 15 | def __init__(self): 16 | super().__init__("Provided keystore file password is invalid.") 17 | 18 | 19 | class CannotSignError(Exception): 20 | def __init__(self) -> None: 21 | super().__init__("Cannot sign object.") 22 | 23 | 24 | class InvalidMnemonicError(Exception): 25 | def __init__(self) -> None: 26 | super().__init__("Bad mnemonic") 27 | 28 | 29 | class InvalidSecretKeyLengthError(Exception): 30 | def __init__(self) -> None: 31 | super().__init__("Bad length of secret key") 32 | 33 | 34 | class InvalidPublicKeyLengthError(Exception): 35 | def __init__(self) -> None: 36 | super().__init__("Bad length of public key") 37 | 38 | 39 | class LibraryNotFoundError(Exception): 40 | def __init__(self, path: Path) -> None: 41 | super().__init__(f"Library not found: {path}") 42 | 43 | 44 | class UnsupportedOSError(Exception): 45 | def __init__(self, os_name: str) -> None: 46 | super().__init__(f"Unsupported OS: {os_name}") 47 | -------------------------------------------------------------------------------- /multiversx_sdk/wallet/interfaces.py: -------------------------------------------------------------------------------- 1 | from typing import Protocol 2 | 3 | 4 | class IRandomness(Protocol): 5 | salt: bytes 6 | iv: bytes 7 | id: str 8 | -------------------------------------------------------------------------------- /multiversx_sdk/wallet/keypair.py: -------------------------------------------------------------------------------- 1 | from multiversx_sdk.wallet.user_keys import UserPublicKey, UserSecretKey 2 | 3 | 4 | class KeyPair: 5 | def __init__(self, secret_key: UserSecretKey) -> None: 6 | self.secret_key = secret_key 7 | self.public_key = self.secret_key.generate_public_key() 8 | 9 | @staticmethod 10 | def generate() -> "KeyPair": 11 | secret_key = UserSecretKey.generate() 12 | return KeyPair(secret_key) 13 | 14 | @staticmethod 15 | def new_from_bytes(data: bytes) -> "KeyPair": 16 | secret_key = UserSecretKey(data) 17 | return KeyPair(secret_key) 18 | 19 | def sign(self, data: bytes) -> bytes: 20 | """Signs using the secret key of the keypair.""" 21 | return self.secret_key.sign(data) 22 | 23 | def verify(self, data: bytes, signature: bytes) -> bool: 24 | """Verifies using the public key of the keypair.""" 25 | return self.public_key.verify(data, signature) 26 | 27 | def get_secret_key(self) -> UserSecretKey: 28 | return self.secret_key 29 | 30 | def get_public_key(self) -> UserPublicKey: 31 | return self.public_key 32 | -------------------------------------------------------------------------------- /multiversx_sdk/wallet/keypair_test.py: -------------------------------------------------------------------------------- 1 | from multiversx_sdk.core.address import Address 2 | from multiversx_sdk.core.transaction import Transaction 3 | from multiversx_sdk.core.transaction_computer import TransactionComputer 4 | from multiversx_sdk.wallet.keypair import KeyPair 5 | from multiversx_sdk.wallet.user_keys import UserSecretKey 6 | 7 | 8 | def test_create_keypair(): 9 | buffer_hex = "413f42575f7f26fad3317a778771212fdb80245850981e48b58a4f25e344e8f9" 10 | buffer = bytes.fromhex(buffer_hex) 11 | 12 | user_secret_key = UserSecretKey(buffer) 13 | keypair = KeyPair.new_from_bytes(buffer) 14 | 15 | secret_key = keypair.get_secret_key() 16 | assert secret_key.hex() == buffer_hex 17 | assert secret_key == user_secret_key 18 | 19 | keypair = KeyPair(secret_key) 20 | assert keypair.get_secret_key() == user_secret_key 21 | assert keypair.get_public_key() == user_secret_key.generate_public_key() 22 | 23 | keypair = KeyPair.generate() 24 | pubkey = keypair.get_public_key() 25 | secret_key = keypair.get_secret_key() 26 | assert len(pubkey.get_bytes()) == 32 27 | assert len(secret_key.get_bytes()) == 32 28 | 29 | 30 | def test_sign_and_verify_transaction(): 31 | """ 32 | Also see: https://github.com/multiversx/mx-chain-go/blob/master/examples/construction_test.go 33 | """ 34 | 35 | tx = Transaction( 36 | nonce=89, 37 | value=0, 38 | receiver=Address.new_from_bech32("erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"), 39 | sender=Address.new_from_bech32("erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"), 40 | data=None, 41 | gas_price=1000000000, 42 | gas_limit=50000, 43 | chain_id="local-testnet", 44 | version=1, 45 | options=0, 46 | ) 47 | 48 | buffer_hex = "413f42575f7f26fad3317a778771212fdb80245850981e48b58a4f25e344e8f9" 49 | buffer = bytes.fromhex(buffer_hex) 50 | keypair = KeyPair.new_from_bytes(buffer) 51 | 52 | transaction_computer = TransactionComputer() 53 | serialized_tx = transaction_computer.compute_bytes_for_signing(tx) 54 | 55 | tx.signature = keypair.sign(serialized_tx) 56 | assert ( 57 | tx.signature.hex() 58 | == "b56769014f2bdc5cf9fc4a05356807d71fcf8775c819b0f1b0964625b679c918ffa64862313bfef86f99b38cb84fcdb16fa33ad6eb565276616723405cd8f109" 59 | ) 60 | assert keypair.verify(serialized_tx, tx.signature) 61 | -------------------------------------------------------------------------------- /multiversx_sdk/wallet/libraries/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/multiversx/mx-sdk-py/e7994cfd135a4e88ab4282ce07b41d7524ddc34e/multiversx_sdk/wallet/libraries/__init__.py -------------------------------------------------------------------------------- /multiversx_sdk/wallet/libraries/libbls.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/multiversx/mx-sdk-py/e7994cfd135a4e88ab4282ce07b41d7524ddc34e/multiversx_sdk/wallet/libraries/libbls.dll -------------------------------------------------------------------------------- /multiversx_sdk/wallet/libraries/libbls.dylib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/multiversx/mx-sdk-py/e7994cfd135a4e88ab4282ce07b41d7524ddc34e/multiversx_sdk/wallet/libraries/libbls.dylib -------------------------------------------------------------------------------- /multiversx_sdk/wallet/libraries/libbls.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/multiversx/mx-sdk-py/e7994cfd135a4e88ab4282ce07b41d7524ddc34e/multiversx_sdk/wallet/libraries/libbls.so -------------------------------------------------------------------------------- /multiversx_sdk/wallet/libraries/libbls_arm64.dylib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/multiversx/mx-sdk-py/e7994cfd135a4e88ab4282ce07b41d7524ddc34e/multiversx_sdk/wallet/libraries/libbls_arm64.dylib -------------------------------------------------------------------------------- /multiversx_sdk/wallet/mnemonic.py: -------------------------------------------------------------------------------- 1 | import mnemonic 2 | 3 | from multiversx_sdk.wallet import core 4 | from multiversx_sdk.wallet.constants import BIP39_LANGUAGE, BIP39_STRENGTH 5 | from multiversx_sdk.wallet.errors import InvalidMnemonicError 6 | from multiversx_sdk.wallet.user_keys import UserSecretKey 7 | 8 | 9 | class Mnemonic: 10 | def __init__(self, text: str) -> None: 11 | text = text.strip() 12 | self.assert_text_is_valid(text) 13 | self.text = text 14 | 15 | @classmethod 16 | def assert_text_is_valid(cls, text: str) -> None: 17 | if not cls.is_text_valid(text): 18 | raise InvalidMnemonicError() 19 | 20 | @classmethod 21 | def is_text_valid(cls, text: str) -> bool: 22 | return mnemonic.Mnemonic(BIP39_LANGUAGE).check(text) 23 | 24 | @classmethod 25 | def generate(cls) -> "Mnemonic": 26 | text = mnemonic.Mnemonic(BIP39_LANGUAGE).generate(strength=BIP39_STRENGTH) 27 | return cls(text) 28 | 29 | @classmethod 30 | def from_entropy(cls, entropy: bytes) -> "Mnemonic": 31 | text = mnemonic.Mnemonic(BIP39_LANGUAGE).to_mnemonic(entropy) 32 | return cls(text) 33 | 34 | def derive_key(self, address_index: int = 0) -> UserSecretKey: 35 | secret_key = core.derive_keys(self.text, address_index) 36 | return UserSecretKey(secret_key) 37 | 38 | def get_text(self) -> str: 39 | return self.text 40 | 41 | def get_words(self) -> list[str]: 42 | return self.text.split() 43 | 44 | def get_entropy(self) -> bytes: 45 | entropy = mnemonic.Mnemonic(BIP39_LANGUAGE).to_entropy(self.text) 46 | return bytes(entropy) 47 | 48 | def __str__(self) -> str: 49 | return Mnemonic.__name__ 50 | 51 | def __repr__(self) -> str: 52 | return Mnemonic.__name__ 53 | -------------------------------------------------------------------------------- /multiversx_sdk/wallet/mnemonic_test.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from multiversx_sdk.wallet.errors import InvalidMnemonicError 4 | from multiversx_sdk.wallet.mnemonic import Mnemonic 5 | 6 | 7 | def test_assert_text_is_valid(): 8 | with pytest.raises(InvalidMnemonicError): 9 | Mnemonic.assert_text_is_valid("bad mnemonic") 10 | Mnemonic.assert_text_is_valid("moral volcano peasant pass circle pen over picture") 11 | 12 | 13 | def test_generate(): 14 | mnemonic = Mnemonic.generate() 15 | words = mnemonic.get_words() 16 | assert len(words) == 24 17 | 18 | 19 | def test_derive_keys(): 20 | mnemonic = Mnemonic( 21 | "moral volcano peasant pass circle pen over picture flat shop clap goat never lyrics gather prepare woman film husband gravity behind test tiger improve" 22 | ) 23 | assert mnemonic.derive_key(0).hex() == "413f42575f7f26fad3317a778771212fdb80245850981e48b58a4f25e344e8f9" 24 | assert mnemonic.derive_key(1).hex() == "b8ca6f8203fb4b545a8e83c5384da033c415db155b53fb5b8eba7ff5a039d639" 25 | assert mnemonic.derive_key(2).hex() == "e253a571ca153dc2aee845819f74bcc9773b0586edead15a94cb7235a5027436" 26 | 27 | 28 | def test_convert_entropy_to_mnemonic_and_back(): 29 | def test_conversion(text: str, entropy_hex: str) -> None: 30 | entropy_from_mnemonic = Mnemonic(text).get_entropy() 31 | mnemonic_from_entropy = Mnemonic.from_entropy(bytes.fromhex(entropy_hex)) 32 | 33 | assert entropy_from_mnemonic.hex() == entropy_hex 34 | assert mnemonic_from_entropy.get_text() == text 35 | 36 | test_conversion( 37 | text="abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about", 38 | entropy_hex="00000000000000000000000000000000", 39 | ) 40 | 41 | test_conversion( 42 | text="moral volcano peasant pass circle pen over picture flat shop clap goat never lyrics gather prepare woman film husband gravity behind test tiger improve", 43 | entropy_hex="8fbeb688d0529344e77d225898d4a73209510ad81d4ffceac9bfb30149bf387b", 44 | ) 45 | 46 | with pytest.raises(ValueError): 47 | Mnemonic.from_entropy(bytes.fromhex("abba")) 48 | -------------------------------------------------------------------------------- /multiversx_sdk/wallet/pem_entry.py: -------------------------------------------------------------------------------- 1 | import base64 2 | import itertools 3 | import textwrap 4 | 5 | 6 | class PemEntry: 7 | def __init__(self, label: str, message: bytes) -> None: 8 | self.label: str = label 9 | self.message: bytes = message 10 | 11 | @classmethod 12 | def from_text_all(cls, pem_text: str) -> list["PemEntry"]: 13 | lines = pem_text.splitlines() 14 | lines = _clean_lines(lines) 15 | 16 | messages_lines = [ 17 | list(message_lines) 18 | for is_next_entry, message_lines in itertools.groupby(lines, lambda line: "-----" in line) 19 | if not is_next_entry 20 | ] 21 | messages_base64 = ["".join(message_lines) for message_lines in messages_lines] 22 | labels = _parse_labels(lines) 23 | 24 | result: list[PemEntry] = [] 25 | 26 | for index, message_base64 in enumerate(messages_base64): 27 | message_hex = base64.b64decode(message_base64).decode() 28 | message_bytes = bytes.fromhex(message_hex) 29 | label = labels[index] 30 | 31 | result.append(cls(label, message_bytes)) 32 | 33 | return result 34 | 35 | def to_text(self) -> str: 36 | header = f"-----BEGIN PRIVATE KEY for {self.label}-----" 37 | footer = f"-----END PRIVATE KEY for {self.label}-----" 38 | 39 | message_hex = self.message.hex().encode() 40 | message_base64 = base64.b64encode(message_hex).decode() 41 | 42 | payload_lines = textwrap.wrap(message_base64, 64) 43 | payload = "\n".join(payload_lines) 44 | text = "\n".join([header, payload, footer]) 45 | return text 46 | 47 | 48 | def _clean_lines(lines: list[str]) -> list[str]: 49 | lines = [line.strip() for line in lines] 50 | lines = list(filter(None, lines)) 51 | return lines 52 | 53 | 54 | def _parse_labels(headers: list[str]) -> list[str]: 55 | marker = "-----BEGIN PRIVATE KEY for" 56 | headers = [line for line in headers if line.startswith(marker)] 57 | labels = [line.replace(marker, "").replace("-", "").strip() for line in headers] 58 | return labels 59 | -------------------------------------------------------------------------------- /multiversx_sdk/wallet/user_pem.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | 3 | from multiversx_sdk.wallet.constants import USER_SEED_LENGTH 4 | from multiversx_sdk.wallet.pem_entry import PemEntry 5 | from multiversx_sdk.wallet.user_keys import UserSecretKey 6 | 7 | 8 | class UserPEM: 9 | def __init__(self, label: str, secret_key: UserSecretKey) -> None: 10 | self.label = label 11 | self.secret_key = secret_key 12 | self.public_key = secret_key.generate_public_key() 13 | 14 | @classmethod 15 | def from_file(cls, path: Path, index: int = 0) -> "UserPEM": 16 | return cls.from_file_all(path)[index] 17 | 18 | @classmethod 19 | def from_file_all(cls, path: Path) -> list["UserPEM"]: 20 | text = path.expanduser().resolve().read_text() 21 | return cls.from_text_all(text) 22 | 23 | @classmethod 24 | def from_text(cls, text: str, index: int = 0) -> "UserPEM": 25 | items = cls.from_text_all(text) 26 | return items[index] 27 | 28 | @classmethod 29 | def from_text_all(cls, text: str) -> list["UserPEM"]: 30 | entries = PemEntry.from_text_all(text) 31 | result_items: list[UserPEM] = [] 32 | 33 | for entry in entries: 34 | secret_key = UserSecretKey(entry.message[0:USER_SEED_LENGTH]) 35 | item = cls(entry.label, secret_key) 36 | result_items.append(item) 37 | 38 | return result_items 39 | 40 | def save(self, path: Path): 41 | path = path.expanduser().resolve() 42 | path.write_text(self.to_text()) 43 | 44 | def to_text(self) -> str: 45 | message = self.secret_key.buffer + self.public_key.buffer 46 | return PemEntry(self.label, message).to_text() 47 | -------------------------------------------------------------------------------- /multiversx_sdk/wallet/user_signer.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | 3 | from multiversx_sdk.wallet.errors import CannotSignError 4 | from multiversx_sdk.wallet.user_keys import UserPublicKey, UserSecretKey 5 | from multiversx_sdk.wallet.user_pem import UserPEM 6 | from multiversx_sdk.wallet.user_wallet import UserWallet 7 | 8 | 9 | class UserSigner: 10 | """ 11 | ed25519 signer 12 | """ 13 | 14 | def __init__(self, secret_key: UserSecretKey) -> None: 15 | self.secret_key = secret_key 16 | 17 | @classmethod 18 | def from_pem_file(cls, path: Path, index: int = 0) -> "UserSigner": 19 | secret_key = UserPEM.from_file(path, index).secret_key 20 | return cls(secret_key) 21 | 22 | @classmethod 23 | def from_pem_file_all(cls, path: Path) -> list["UserSigner"]: 24 | users = UserPEM.from_file_all(path) 25 | return [cls(user.secret_key) for user in users] 26 | 27 | @classmethod 28 | def from_wallet(cls, path: Path, password: str) -> "UserSigner": 29 | secret_key = UserWallet.load_secret_key(path, password) 30 | return cls(secret_key) 31 | 32 | def sign(self, data: bytes) -> bytes: 33 | try: 34 | return self._try_sign(data) 35 | except Exception as err: 36 | raise CannotSignError() from err 37 | 38 | def _try_sign(self, data: bytes) -> bytes: 39 | signature = self.secret_key.sign(data) 40 | return signature 41 | 42 | def get_pubkey(self) -> UserPublicKey: 43 | return self.secret_key.generate_public_key() 44 | -------------------------------------------------------------------------------- /multiversx_sdk/wallet/user_verifer.py: -------------------------------------------------------------------------------- 1 | from multiversx_sdk.core.address import Address 2 | from multiversx_sdk.wallet.user_keys import UserPublicKey 3 | 4 | 5 | class UserVerifier: 6 | def __init__(self, public_key: UserPublicKey) -> None: 7 | self.public_key = public_key 8 | 9 | @classmethod 10 | def from_address(cls, address: Address) -> "UserVerifier": 11 | buffer: bytes = address.get_public_key() 12 | public_key = UserPublicKey(buffer) 13 | return cls(public_key) 14 | 15 | def verify(self, data: bytes, signature: bytes) -> bool: 16 | return self.public_key.verify(data, signature) 17 | -------------------------------------------------------------------------------- /multiversx_sdk/wallet/validator_keys.py: -------------------------------------------------------------------------------- 1 | from multiversx_sdk.wallet.constants import ( 2 | VALIDATOR_PUBKEY_LENGTH, 3 | VALIDATOR_SECRETKEY_LENGTH, 4 | ) 5 | from multiversx_sdk.wallet.errors import InvalidSecretKeyLengthError 6 | from multiversx_sdk.wallet.libraries.bls_facade import BLSFacade 7 | 8 | 9 | class ValidatorSecretKey: 10 | def __init__(self, buffer: bytes) -> None: 11 | if len(buffer) != VALIDATOR_SECRETKEY_LENGTH: 12 | raise InvalidSecretKeyLengthError() 13 | 14 | self.buffer = buffer 15 | 16 | @classmethod 17 | def generate(cls) -> "ValidatorSecretKey": 18 | secret_key_bytes = BLSFacade().generate_private_key() 19 | return cls(secret_key_bytes) 20 | 21 | @classmethod 22 | def from_string(cls, buffer_hex: str) -> "ValidatorSecretKey": 23 | buffer = bytes.fromhex(buffer_hex) 24 | return cls(buffer) 25 | 26 | def generate_public_key(self) -> "ValidatorPublicKey": 27 | public_key_bytes = BLSFacade().generate_public_key(self.buffer) 28 | return ValidatorPublicKey(public_key_bytes) 29 | 30 | def sign(self, data: bytes) -> bytes: 31 | signature = BLSFacade().compute_message_signature(data, self.buffer) 32 | return signature 33 | 34 | def hex(self) -> str: 35 | return self.buffer.hex() 36 | 37 | def __str__(self) -> str: 38 | return ValidatorSecretKey.__name__ 39 | 40 | def __repr__(self) -> str: 41 | return ValidatorSecretKey.__name__ 42 | 43 | 44 | class ValidatorPublicKey: 45 | def __init__(self, buffer: bytes) -> None: 46 | if len(buffer) != VALIDATOR_PUBKEY_LENGTH: 47 | raise InvalidSecretKeyLengthError() 48 | 49 | self.buffer = buffer 50 | 51 | @classmethod 52 | def from_string(cls, buffer_hex: str) -> "ValidatorPublicKey": 53 | buffer = bytes.fromhex(buffer_hex) 54 | return ValidatorPublicKey(buffer) 55 | 56 | def verify(self, data: bytes, signature: bytes) -> bool: 57 | ok = BLSFacade().verify_message_signature(self.buffer, data, signature) 58 | return ok 59 | 60 | def hex(self) -> str: 61 | return self.buffer.hex() 62 | 63 | def __str__(self) -> str: 64 | return self.hex() 65 | 66 | def __repr__(self) -> str: 67 | return self.hex() 68 | -------------------------------------------------------------------------------- /multiversx_sdk/wallet/validator_pem.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | 3 | from multiversx_sdk.wallet.pem_entry import PemEntry 4 | from multiversx_sdk.wallet.validator_keys import ValidatorSecretKey 5 | 6 | 7 | class ValidatorPEM: 8 | def __init__(self, label: str, secret_key: ValidatorSecretKey) -> None: 9 | self.label = label 10 | self.secret_key = secret_key 11 | 12 | @classmethod 13 | def from_file(cls, path: Path, index: int = 0) -> "ValidatorPEM": 14 | return cls.from_file_all(path)[index] 15 | 16 | @classmethod 17 | def from_file_all(cls, path: Path) -> list["ValidatorPEM"]: 18 | text = path.expanduser().resolve().read_text() 19 | return cls.from_text_all(text) 20 | 21 | @classmethod 22 | def from_text(cls, text: str, index: int = 0) -> "ValidatorPEM": 23 | items = cls.from_text_all(text) 24 | return items[index] 25 | 26 | @classmethod 27 | def from_text_all(cls, text: str) -> list["ValidatorPEM"]: 28 | entries = PemEntry.from_text_all(text) 29 | result_items: list[ValidatorPEM] = [] 30 | 31 | for entry in entries: 32 | secret_key = ValidatorSecretKey(entry.message) 33 | item = cls(entry.label, secret_key) 34 | result_items.append(item) 35 | 36 | return result_items 37 | 38 | def save(self, path: Path): 39 | path = path.expanduser().resolve() 40 | path.write_text(self.to_text()) 41 | 42 | def to_text(self): 43 | message = self.secret_key.buffer 44 | return PemEntry(self.label, message).to_text() 45 | -------------------------------------------------------------------------------- /multiversx_sdk/wallet/validator_signer.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | 3 | from multiversx_sdk.wallet.errors import CannotSignError 4 | from multiversx_sdk.wallet.validator_keys import ValidatorPublicKey, ValidatorSecretKey 5 | from multiversx_sdk.wallet.validator_pem import ValidatorPEM 6 | 7 | 8 | class ValidatorSigner: 9 | """ 10 | Validator signer (BLS signer) 11 | """ 12 | 13 | def __init__(self, secret_key: ValidatorSecretKey) -> None: 14 | self.secret_key = secret_key 15 | 16 | @classmethod 17 | def from_pem_file(cls, path: Path, index: int = 0) -> "ValidatorSigner": 18 | secret_key = ValidatorPEM.from_file(path, index).secret_key 19 | return cls(secret_key) 20 | 21 | def sign(self, data: bytes) -> bytes: 22 | try: 23 | return self._try_sign(data) 24 | except Exception as err: 25 | raise CannotSignError() from err 26 | 27 | def _try_sign(self, data: bytes) -> bytes: 28 | signature = self.secret_key.sign(data) 29 | return signature 30 | 31 | def get_pubkey(self) -> ValidatorPublicKey: 32 | return self.secret_key.generate_public_key() 33 | -------------------------------------------------------------------------------- /multiversx_sdk/wallet/validator_test.py: -------------------------------------------------------------------------------- 1 | import os 2 | from pathlib import Path 3 | 4 | from multiversx_sdk.wallet.validator_keys import ValidatorSecretKey 5 | from multiversx_sdk.wallet.validator_pem import ValidatorPEM 6 | from multiversx_sdk.wallet.validator_signer import ValidatorSigner 7 | from multiversx_sdk.wallet.validator_verifier import ValidatorVerifier 8 | 9 | testwallets = Path(__file__).parent.parent / "testutils" / "testwallets" 10 | 11 | 12 | def test_validator_secret_key_generate_public_key(): 13 | assert ( 14 | ValidatorSecretKey.from_string("7cff99bd671502db7d15bc8abc0c9a804fb925406fbdd50f1e4c17a4cd774247") 15 | .generate_public_key() 16 | .hex() 17 | == "e7beaa95b3877f47348df4dd1cb578a4f7cabf7a20bfeefe5cdd263878ff132b765e04fef6f40c93512b666c47ed7719b8902f6c922c04247989b7137e837cc81a62e54712471c97a2ddab75aa9c2f58f813ed4c0fa722bde0ab718bff382208" 18 | ) 19 | 20 | 21 | def test_sign_message(): 22 | signer = ValidatorSigner.from_pem_file(testwallets / "validatorKey00.pem") 23 | message = b"hello" 24 | signature = signer.sign(message) 25 | assert ( 26 | signature.hex() 27 | == "84fd0a3a9d4f1ea2d4b40c6da67f9b786284a1c3895b7253fec7311597cda3f757862bb0690a92a13ce612c33889fd86" 28 | ) 29 | 30 | 31 | def test_verify_message(): 32 | verifier = ValidatorVerifier.from_string( 33 | "e7beaa95b3877f47348df4dd1cb578a4f7cabf7a20bfeefe5cdd263878ff132b765e04fef6f40c93512b666c47ed7719b8902f6c922c04247989b7137e837cc81a62e54712471c97a2ddab75aa9c2f58f813ed4c0fa722bde0ab718bff382208" 34 | ) 35 | 36 | message = b"hello" 37 | signature = bytes.fromhex( 38 | "84fd0a3a9d4f1ea2d4b40c6da67f9b786284a1c3895b7253fec7311597cda3f757862bb0690a92a13ce612c33889fd86" 39 | ) 40 | 41 | assert verifier.verify(message, signature) 42 | 43 | invalid_signature = bytes.fromhex( 44 | "94fd0a3a9d4f1ea2d4b40c6da67f9b786284a1c3895b7253fec7311597cda3f757862bb0690a92a13ce612c33889fd86" 45 | ) 46 | assert verifier.verify(message, invalid_signature) is False 47 | 48 | 49 | def test_pem_save(): 50 | path = testwallets / "validatorKey00.pem" 51 | path_saved = path.with_suffix(".saved") 52 | 53 | with open(path) as f: 54 | content_expected = f.read().strip() 55 | 56 | pem = ValidatorPEM.from_file(path) 57 | pem.save(path_saved) 58 | 59 | with open(path_saved) as f: 60 | content_actual = f.read().strip() 61 | 62 | assert content_actual == content_expected 63 | os.remove(path_saved) 64 | -------------------------------------------------------------------------------- /multiversx_sdk/wallet/validator_verifier.py: -------------------------------------------------------------------------------- 1 | from multiversx_sdk.wallet.validator_keys import ValidatorPublicKey 2 | 3 | 4 | class ValidatorVerifier: 5 | def __init__(self, public_key: ValidatorPublicKey) -> None: 6 | self.public_key = public_key 7 | 8 | @classmethod 9 | def from_string(cls, buffer_hex: str) -> "ValidatorVerifier": 10 | public_key = ValidatorPublicKey.from_string(buffer_hex) 11 | return cls(public_key) 12 | 13 | def verify(self, data: bytes, signature: bytes) -> bool: 14 | return self.public_key.verify(data, signature) 15 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["hatchling"] 3 | build-backend = "hatchling.build" 4 | 5 | [tool.hatch.metadata] 6 | allow-direct-references = true 7 | 8 | [project] 9 | name = "multiversx-sdk" 10 | version = "1.5.4" 11 | authors = [ 12 | { name="MultiversX" }, 13 | ] 14 | license = "MIT" 15 | description = "The MultiversX Python SDK." 16 | readme = "README.md" 17 | requires-python = ">=3.8" 18 | classifiers = [ 19 | "Programming Language :: Python :: 3", 20 | "License :: OSI Approved :: MIT License", 21 | "Operating System :: OS Independent", 22 | ] 23 | dependencies = [ 24 | "pycryptodomex==3.19.1", 25 | "protobuf>=5.27.2,<6.0.0", 26 | "cryptography==43.0.1", 27 | "pynacl==1.5.0", 28 | "mnemonic==0.21", 29 | "requests>=2.32.0,<3.0.0" 30 | ] 31 | 32 | [project.optional-dependencies] 33 | ledger = ["ledgercomm[hid]"] 34 | 35 | [project.urls] 36 | "Homepage" = "https://github.com/multiversx/mx-sdk-py" 37 | 38 | [tool.hatch.build] 39 | include = [ 40 | "multiversx_sdk/**", 41 | "multiversx_sdk/py.typed" 42 | ] 43 | 44 | exclude = [ 45 | ".github", 46 | "./examples", 47 | ".vscode", 48 | "./multiversx_sdk/testutils/", 49 | "./docs", 50 | "*_test.py" 51 | ] 52 | -------------------------------------------------------------------------------- /pyrightconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": [ 3 | "multiversx_sdk" 4 | ], 5 | "exclude": [ 6 | "**/__pycache__" 7 | ], 8 | "ignore": [], 9 | "defineConstant": { 10 | "DEBUG": true 11 | }, 12 | "venvPath": ".", 13 | "venv": "venv", 14 | "stubPath": "", 15 | "reportMissingImports": true, 16 | "reportMissingTypeStubs": false, 17 | "reportUnknownParameterType": true 18 | } 19 | -------------------------------------------------------------------------------- /pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | markers = 3 | only: only run a specific test (run using: pytest -m "only") 4 | networkInteraction: only run API and Proxy network providers tests (run using `pytest -m networkInteraction` or run all tests excepting these using: `pytest -m "not networkInteraction"`) 5 | mainnet: tests that use a set of transactions from mainnet (api/proxy intensive), run tests using: `pytest -m mainnet` 6 | 7 | log_cli = True 8 | -------------------------------------------------------------------------------- /requirements-dev.txt: -------------------------------------------------------------------------------- 1 | pytest 2 | pytest-cov 3 | pytest-mock 4 | flake8 5 | black 6 | pre-commit 7 | ipykernel 8 | sphinx 9 | sphinx-rtd-theme 10 | coverage 11 | pyright 12 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | pycryptodomex==3.19.1 2 | protobuf>=5.27.2,<6.0.0 3 | cryptography==43.0.1 4 | pynacl==1.5.0 5 | mnemonic==0.21 6 | requests>=2.32.0,<3.0.0 7 | ledgercomm[hid] 8 | --------------------------------------------------------------------------------