diff --git a/.flake8 b/.flake8 index e44b8108..9983e283 100644 --- a/.flake8 +++ b/.flake8 @@ -1,2 +1,2 @@ [flake8] -ignore = E501 +extend-ignore = E501, E722, E203 diff --git a/.github/workflows/build-windows.yml b/.github/workflows/build-windows.yml index b157b6e8..fda48f40 100644 --- a/.github/workflows/build-windows.yml +++ b/.github/workflows/build-windows.yml @@ -22,7 +22,7 @@ jobs: steps: - uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} - name: Install dependencies @@ -38,11 +38,15 @@ jobs: export PYTHONPATH=. python3 -m multiversx_sdk_cli.cli config new test python3 -m multiversx_sdk_cli.cli config set github_api_token ${{ secrets.GITHUB_TOKEN }} + - name: Install Rust + uses: actions-rust-lang/setup-rust-toolchain@v1 + with: + toolchain: stable + target: wasm32-unknown-unknown - name: Setup test dependencies shell: bash run: | python3 -m multiversx_sdk_cli.cli deps install testwallets - python3 -m multiversx_sdk_cli.cli deps install rust - name: Run unit tests shell: bash run: | diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 044f4b88..7f31f431 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -18,11 +18,12 @@ jobs: matrix: os: [ubuntu-latest, macos-latest] python-version: [3.11] + max-parallel: 1 # This ensures jobs run sequentially, not concurrently steps: - uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} - name: Install dependencies @@ -36,10 +37,14 @@ jobs: export PYTHONPATH=. python3 -m multiversx_sdk_cli.cli config new test python3 -m multiversx_sdk_cli.cli config set github_api_token ${{ secrets.GITHUB_TOKEN }} + - name: Install Rust + uses: actions-rust-lang/setup-rust-toolchain@v1 + with: + toolchain: stable + target: wasm32-unknown-unknown - name: Setup test dependencies run: | python3 -m multiversx_sdk_cli.cli deps install testwallets - python3 -m multiversx_sdk_cli.cli deps install rust - name: Run unit tests run: | export PYTHONPATH=. diff --git a/.github/workflows/mypy.yml b/.github/workflows/mypy.yml index c2afd3e7..a82a8196 100644 --- a/.github/workflows/mypy.yml +++ b/.github/workflows/mypy.yml @@ -8,6 +8,8 @@ jobs: - uses: actions/checkout@v2 - name: Install Python dependencies uses: py-actions/py-dependency-install@v2 + - name: Install dev dependencies + run: pip install -r requirements-dev.txt - uses: tsuyoshicho/action-mypy@v3 with: github_token: ${{ secrets.github_token }} diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 355dde42..c6107254 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -17,7 +17,7 @@ permissions: contents: read jobs: - deploy: + deploy-mx-sdk-py-cli: runs-on: ubuntu-latest @@ -38,3 +38,32 @@ jobs: with: user: __token__ password: ${{ secrets.PYPI_API_TOKEN }} + + deploy-mxpy: + runs-on: ubuntu-latest + needs: deploy-mx-sdk-py-cli # Ensure main package is published first + + steps: + - uses: actions/checkout@v3 + + - name: Set up Python + uses: actions/setup-python@v3 + with: + python-version: '3.x' + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install build + + - name: Change package name for mxpy + run: sed -i 's/name = "multiversx-sdk-cli"/name = "mxpy"/' pyproject.toml + + - name: Build and publish mxpy package + run: python -m build + + - name: Publish mxpy package + uses: pypa/gh-action-pypi-publish@27b31702a0e7fc50959f5ad993c78deac1bdfc29 + with: + user: __token__ + password: ${{ secrets.PYPI_API_TOKEN }} diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 00000000..d969e533 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,35 @@ +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v5.0.0 + hooks: + - id: check-yaml + - id: end-of-file-fixer + - id: trailing-whitespace + - repo: https://github.com/psf/black + rev: 24.10.0 + hooks: + - id: black + args: [--line-length=120] + - repo: https://github.com/pycqa/isort + rev: 5.13.2 + hooks: + - id: isort + name: isort (python) + args: ["--profile", "black", "--filter-files"] + - repo: https://github.com/PyCQA/flake8 + rev: 7.1.1 + hooks: + - id: flake8 + args: + - "--config=.flake8" + - repo: https://github.com/PyCQA/autoflake + rev: v2.3.1 + hooks: + - id: autoflake + args: + - --in-place + - --remove-all-unused-imports + - repo: https://github.com/RobertCraigie/pyright-python + rev: v1.1.392 + hooks: + - id: pyright diff --git a/.vscode/settings.json b/.vscode/settings.json index 2a4f3afd..127cf7d3 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,16 +1,20 @@ { - "python.testing.pytestArgs": [ - "multiversx_sdk_cli" - ], - "python.testing.pytestEnabled": true, - "editor.formatOnSave": true, - "[python]": { - "editor.defaultFormatter": "ms-python.autopep8" - }, - "python.formatting.provider": "autopep8", - "editor.codeActionsOnSave": { - "source.organizeImports": true - }, - "files.insertFinalNewline": true, - "python.languageServer": "Pylance" + "python.testing.pytestArgs": ["multiversx_sdk_cli"], + "python.testing.pytestEnabled": true, + "editor.formatOnSave": true, + "[python]": { + "editor.defaultFormatter": "ms-python.black-formatter" + }, + "editor.codeActionsOnSave": { + "source.organizeImports": "explicit" + }, + "files.insertFinalNewline": true, + "python.languageServer": "Pylance", + "files.autoSave": "onWindowChange", + "editor.fontSize": 13, + "files.trimTrailingWhitespace": true, + "github.gitProtocol": "ssh", + "python.analysis.autoFormatStrings": true, + "python.analysis.completeFunctionParens": true, + "black-formatter.args": ["--line-length=120"] } diff --git a/CLI.md b/CLI.md index cc3b856e..c388fdd5 100644 --- a/CLI.md +++ b/CLI.md @@ -20,10 +20,10 @@ mxpy targets a broad audience of users and developers. See: - https://docs.multiversx.com/sdk-and-tools/sdk-py - https://docs.multiversx.com/sdk-and-tools/sdk-py/mxpy-cli - + COMMAND GROUPS: - {contract,tx,validator,account,ledger,wallet,deps,config,localnet,data,staking-provider,dns,faucet} + {contract,tx,validator,ledger,wallet,validator-wallet,deps,config,localnet,data,staking-provider,dns,faucet} TOP-LEVEL OPTIONS: -h, --help show this help message and exit @@ -33,12 +33,12 @@ TOP-LEVEL OPTIONS: ---------------------- COMMAND GROUPS summary ---------------------- -contract Build, deploy, upgrade and interact with Smart Contracts +contract Deploy, upgrade and interact with Smart Contracts tx Create and broadcast Transactions validator Stake, UnStake, UnBond, Unjail and other actions useful for Validators -account Get Account data (nonce, balance) from the Network ledger Get Ledger App addresses and version wallet Create wallet, derive secret key from mnemonic, bech32 address helpers etc. +validator-wallet Create a validator wallet, sign and verify messages and convert a validator wallet to a hex secret key. deps Manage dependencies or multiversx-sdk modules config Configure multiversx-sdk (default values etc.) localnet Set up, start and control localnets @@ -55,10 +55,10 @@ faucet Get xEGLD on Devnet or Testnet $ mxpy contract --help usage: mxpy contract COMMAND [-h] ... -Build, deploy, upgrade and interact with Smart Contracts +Deploy, upgrade and interact with Smart Contracts COMMANDS: - {new,templates,build,clean,test,report,deploy,call,upgrade,query,verify,reproducible-build} + {deploy,call,upgrade,query,verify,reproducible-build,build} OPTIONS: -h, --help show this help message and exit @@ -66,93 +66,13 @@ OPTIONS: ---------------- COMMANDS summary ---------------- -new Create a new Smart Contract project based on a template. -templates List the available Smart Contract templates. -build Build a Smart Contract project. -clean Clean a Smart Contract project. -test Run tests. -report Print a detailed report of the smart contracts. deploy Deploy a Smart Contract. call Interact with a Smart Contract (execute function). upgrade Upgrade a previously-deployed Smart Contract. query Query a Smart Contract (call a pure function) verify Verify the authenticity of the code of a deployed Smart Contract reproducible-build Build a Smart Contract and get the same output as a previously built Smart Contract - -``` -### Contract.New - - -``` -$ mxpy contract new --help -usage: mxpy contract new [-h] ... - -Create a new Smart Contract project based on a template. - -options: - -h, --help show this help message and exit - --name NAME The name of the contract. If missing, the name of the template will be used. - --template TEMPLATE the template to use - --tag TAG the framework version on which the contract should be created - --path PATH the parent directory of the project (default: current directory) - -``` -### Contract.Templates - - -``` -$ mxpy contract templates --help -usage: mxpy contract templates [-h] ... - -List the available Smart Contract templates. - -options: - -h, --help show this help message and exit - --tag TAG the sc-meta framework version referred to - -``` -### Contract.Build - - -``` -$ mxpy contract build --help -usage: mxpy contract build [-h] ... - -Build a Smart Contract project. - -options: - -h, --help show this help message and exit - --path PATH the project directory (default: current directory) - --no-wasm-opt do not optimize wasm files after the build (default: False) - --wasm-symbols for rust projects, does not strip the symbols from the wasm output. Useful for analysing - the bytecode. Creates larger wasm files. Avoid in production (default: False) - --wasm-name WASM_NAME for rust projects, optionally specify the name of the wasm bytecode output file - --wasm-suffix WASM_SUFFIX for rust projects, optionally specify the suffix of the wasm bytecode output file - --target-dir TARGET_DIR for rust projects, forward the parameter to Cargo - --wat also generate a WAT file when building - --mir also emit MIR files when building - --llvm-ir also emit LL (LLVM) files when building - --ignore IGNORE ignore all directories with these names. [default: target] - --no-imports skips extracting the EI imports after building the contracts - --no-abi-git-version skips loading the Git version into the ABI - --twiggy-top generate a twiggy top report after building - --twiggy-paths generate a twiggy paths report after building - --twiggy-monos generate a twiggy monos report after building - --twiggy-dominators generate a twiggy dominators report after building - -``` -### Contract.Clean - - -``` -$ mxpy contract clean --help -usage: mxpy contract clean [-h] ... - -Clean a Smart Contract project. - -options: - -h, --help show this help message and exit - --path PATH the project directory (default: current directory) +build Build a Smart Contract project. This command is DISABLED. ``` ### Contract.Deploy @@ -193,65 +113,60 @@ Output example: } options: - -h, --help show this help message and exit - --bytecode BYTECODE the file containing the WASM bytecode - --abi ABI the ABI of the Smart Contract - --metadata-not-upgradeable ‼ mark the contract as NOT upgradeable (default: upgradeable) - --metadata-not-readable ‼ mark the contract as NOT readable (default: readable) - --metadata-payable ‼ mark the contract as payable (default: not payable) - --metadata-payable-by-sc ‼ mark the contract as payable by SC (default: not payable by SC) - --outfile OUTFILE where to save the output (default: stdout) - --pem PEM 🔑 the PEM file, if keyfile not provided - --pem-index PEM_INDEX 🔑 the index in the PEM file (default: 0) - --keyfile KEYFILE 🔑 a JSON keyfile, if PEM not provided - --passfile PASSFILE 🔑 a file containing keyfile's password, if keyfile provided - --ledger 🔐 bool flag for signing transaction using ledger - --ledger-account-index LEDGER_ACCOUNT_INDEX 🔐 the index of the account when using Ledger - --ledger-address-index LEDGER_ADDRESS_INDEX 🔐 the index of the address when using Ledger - --sender-username SENDER_USERNAME 🖄 the username of the sender - --proxy PROXY 🔗 the URL of the proxy - --nonce NONCE # the nonce for the transaction - --recall-nonce ⭮ whether to recall the nonce when creating the transaction (default: - False) - --gas-price GAS_PRICE ⛽ the gas price (default: 1000000000) - --gas-limit GAS_LIMIT ⛽ the gas limit - --value VALUE the value to transfer (default: 0) - --chain CHAIN the chain identifier - --version VERSION the transaction version (default: 2) - --relayer RELAYER the bech32 address of the relayer - --relayer-pem RELAYER_PEM 🔑 the PEM file, if keyfile not provided - --relayer-pem-index RELAYER_PEM_INDEX 🔑 the index in the PEM file (default: 0) - --relayer-keyfile RELAYER_KEYFILE 🔑 a JSON keyfile, if PEM not provided - --relayer-passfile RELAYER_PASSFILE 🔑 a file containing keyfile's password, if keyfile provided - --relayer-ledger 🔐 bool flag for signing transaction using ledger - --relayer-ledger-account-index RELAYER_LEDGER_ACCOUNT_INDEX - 🔐 the index of the account when using Ledger - --relayer-ledger-address-index RELAYER_LEDGER_ADDRESS_INDEX - 🔐 the index of the address when using Ledger - --guardian GUARDIAN the address of the guradian - --guardian-service-url GUARDIAN_SERVICE_URL the url of the guardian service - --guardian-2fa-code GUARDIAN_2FA_CODE the 2fa code for the guardian - --options OPTIONS the transaction options (default: 0) - --arguments ARGUMENTS [ARGUMENTS ...] arguments for the contract transaction, as [number, bech32-address, - ascii string, boolean] or hex-encoded. E.g. --arguments 42 0x64 1000 - 0xabba str:TOK-a1c2ef true erd1[..] - --arguments-file ARGUMENTS_FILE a json file containing the arguments. ONLY if abi file is provided. - E.g. [{ 'to': 'erd1...', 'amount': 10000000000 }] - --wait-result signal to wait for the transaction result - only valid if --send is - set - --timeout TIMEOUT max num of seconds to wait for result - only valid if --wait-result is - set - --send ✓ whether to broadcast the transaction (default: False) - --simulate whether to simulate the transaction (default: False) - --guardian-pem GUARDIAN_PEM 🔑 the PEM file, if keyfile not provided - --guardian-pem-index GUARDIAN_PEM_INDEX 🔑 the index in the PEM file (default: 0) - --guardian-keyfile GUARDIAN_KEYFILE 🔑 a JSON keyfile, if PEM not provided - --guardian-passfile GUARDIAN_PASSFILE 🔑 a file containing keyfile's password, if keyfile provided - --guardian-ledger 🔐 bool flag for signing transaction using ledger - --guardian-ledger-account-index GUARDIAN_LEDGER_ACCOUNT_INDEX - 🔐 the index of the account when using Ledger - --guardian-ledger-address-index GUARDIAN_LEDGER_ADDRESS_INDEX - 🔐 the index of the address when using Ledger + -h, --help show this help message and exit + --bytecode BYTECODE the file containing the WASM bytecode + --abi ABI the ABI file of the Smart Contract + --metadata-not-upgradeable ‼ mark the contract as NOT upgradeable (default: upgradeable) + --metadata-not-readable ‼ mark the contract as NOT readable (default: readable) + --metadata-payable ‼ mark the contract as payable (default: not payable) + --metadata-payable-by-sc ‼ mark the contract as payable by SC (default: not payable by SC) + --outfile OUTFILE where to save the output (default: stdout) + --pem PEM 🔑 the PEM file, if keyfile not provided + --keyfile KEYFILE 🔑 a JSON keyfile, if PEM not provided + --passfile PASSFILE 🔑 a file containing keyfile's password, if keyfile provided. If not + provided, you'll be prompted to enter the password. + --ledger 🔐 bool flag for signing transaction using ledger + --sender-wallet-index SENDER_WALLET_INDEX 🔑 the address index; can be used for PEM files, keyfiles of type + mnemonic or Ledger devices (default: 0) + --sender-username SENDER_USERNAME 🖄 the username of the sender + --proxy PROXY 🔗 the URL of the proxy + --nonce NONCE # the nonce for the transaction + --recall-nonce ⭮ whether to recall the nonce when creating the transaction (default: + False) + --gas-price GAS_PRICE ⛽ the gas price (default: 1000000000) + --gas-limit GAS_LIMIT ⛽ the gas limit + --value VALUE the value to transfer (default: 0) + --chain CHAIN the chain identifier + --version VERSION the transaction version (default: 2) + --options OPTIONS the transaction options (default: 0) + --relayer RELAYER the bech32 address of the relayer + --guardian GUARDIAN the bech32 address of the guardian + --arguments ARGUMENTS [ARGUMENTS ...] arguments for the contract transaction, as [number, bech32-address, + ascii string, boolean] or hex-encoded. E.g. --arguments 42 0x64 1000 + 0xabba str:TOK-a1c2ef true addr:erd1[..] + --arguments-file ARGUMENTS_FILE a json file containing the arguments. ONLY if abi file is provided. + E.g. [{ 'to': 'erd1...', 'amount': 10000000000 }] + --wait-result signal to wait for the transaction result - only valid if --send is set + --timeout TIMEOUT max num of seconds to wait for result - only valid if --wait-result is + set + --send ✓ whether to broadcast the transaction (default: False) + --simulate whether to simulate the transaction (default: False) + --guardian-service-url GUARDIAN_SERVICE_URL the url of the guardian service + --guardian-2fa-code GUARDIAN_2FA_CODE the 2fa code for the guardian + --guardian-pem GUARDIAN_PEM 🔑 the PEM file, if keyfile not provided + --guardian-keyfile GUARDIAN_KEYFILE 🔑 a JSON keyfile, if PEM not provided + --guardian-passfile GUARDIAN_PASSFILE 🔑 a file containing keyfile's password, if keyfile provided. If not + provided, you'll be prompted to enter the password. + --guardian-ledger 🔐 bool flag for signing transaction using ledger + --guardian-wallet-index GUARDIAN_WALLET_INDEX 🔑 the address index; can be used for PEM files, keyfiles of type + mnemonic or Ledger devices (default: 0) + --relayer-pem RELAYER_PEM 🔑 the PEM file, if keyfile not provided + --relayer-keyfile RELAYER_KEYFILE 🔑 a JSON keyfile, if PEM not provided + --relayer-passfile RELAYER_PASSFILE 🔑 a file containing keyfile's password, if keyfile provided. If not + provided, you'll be prompted to enter the password. + --relayer-ledger 🔐 bool flag for signing transaction using ledger + --relayer-wallet-index RELAYER_WALLET_INDEX 🔑 the address index; can be used for PEM files, keyfiles of type + mnemonic or Ledger devices (default: 0) ``` ### Contract.Call @@ -292,19 +207,19 @@ Output example: } positional arguments: - contract 🖄 the address of the Smart Contract + contract 🖄 the bech32 address of the Smart Contract options: -h, --help show this help message and exit - --abi ABI the ABI of the Smart Contract + --abi ABI the ABI file of the Smart Contract --outfile OUTFILE where to save the output (default: stdout) --pem PEM 🔑 the PEM file, if keyfile not provided - --pem-index PEM_INDEX 🔑 the index in the PEM file (default: 0) --keyfile KEYFILE 🔑 a JSON keyfile, if PEM not provided - --passfile PASSFILE 🔑 a file containing keyfile's password, if keyfile provided + --passfile PASSFILE 🔑 a file containing keyfile's password, if keyfile provided. If not + provided, you'll be prompted to enter the password. --ledger 🔐 bool flag for signing transaction using ledger - --ledger-account-index LEDGER_ACCOUNT_INDEX 🔐 the index of the account when using Ledger - --ledger-address-index LEDGER_ADDRESS_INDEX 🔐 the index of the address when using Ledger + --sender-wallet-index SENDER_WALLET_INDEX 🔑 the address index; can be used for PEM files, keyfiles of type + mnemonic or Ledger devices (default: 0) --sender-username SENDER_USERNAME 🖄 the username of the sender --proxy PROXY 🔗 the URL of the proxy --nonce NONCE # the nonce for the transaction @@ -315,24 +230,13 @@ options: --value VALUE the value to transfer (default: 0) --chain CHAIN the chain identifier --version VERSION the transaction version (default: 2) - --relayer RELAYER the bech32 address of the relayer - --relayer-pem RELAYER_PEM 🔑 the PEM file, if keyfile not provided - --relayer-pem-index RELAYER_PEM_INDEX 🔑 the index in the PEM file (default: 0) - --relayer-keyfile RELAYER_KEYFILE 🔑 a JSON keyfile, if PEM not provided - --relayer-passfile RELAYER_PASSFILE 🔑 a file containing keyfile's password, if keyfile provided - --relayer-ledger 🔐 bool flag for signing transaction using ledger - --relayer-ledger-account-index RELAYER_LEDGER_ACCOUNT_INDEX - 🔐 the index of the account when using Ledger - --relayer-ledger-address-index RELAYER_LEDGER_ADDRESS_INDEX - 🔐 the index of the address when using Ledger - --guardian GUARDIAN the address of the guradian - --guardian-service-url GUARDIAN_SERVICE_URL the url of the guardian service - --guardian-2fa-code GUARDIAN_2FA_CODE the 2fa code for the guardian --options OPTIONS the transaction options (default: 0) + --relayer RELAYER the bech32 address of the relayer + --guardian GUARDIAN the bech32 address of the guardian --function FUNCTION the function to call --arguments ARGUMENTS [ARGUMENTS ...] arguments for the contract transaction, as [number, bech32-address, ascii string, boolean] or hex-encoded. E.g. --arguments 42 0x64 1000 - 0xabba str:TOK-a1c2ef true erd1[..] + 0xabba str:TOK-a1c2ef true addr:erd1[..] --arguments-file ARGUMENTS_FILE a json file containing the arguments. ONLY if abi file is provided. E.g. [{ 'to': 'erd1...', 'amount': 10000000000 }] --token-transfers TOKEN_TRANSFERS [TOKEN_TRANSFERS ...] @@ -344,16 +248,22 @@ options: set --send ✓ whether to broadcast the transaction (default: False) --simulate whether to simulate the transaction (default: False) - --relay whether to relay the transaction (default: False) + --guardian-service-url GUARDIAN_SERVICE_URL the url of the guardian service + --guardian-2fa-code GUARDIAN_2FA_CODE the 2fa code for the guardian --guardian-pem GUARDIAN_PEM 🔑 the PEM file, if keyfile not provided - --guardian-pem-index GUARDIAN_PEM_INDEX 🔑 the index in the PEM file (default: 0) --guardian-keyfile GUARDIAN_KEYFILE 🔑 a JSON keyfile, if PEM not provided - --guardian-passfile GUARDIAN_PASSFILE 🔑 a file containing keyfile's password, if keyfile provided + --guardian-passfile GUARDIAN_PASSFILE 🔑 a file containing keyfile's password, if keyfile provided. If not + provided, you'll be prompted to enter the password. --guardian-ledger 🔐 bool flag for signing transaction using ledger - --guardian-ledger-account-index GUARDIAN_LEDGER_ACCOUNT_INDEX - 🔐 the index of the account when using Ledger - --guardian-ledger-address-index GUARDIAN_LEDGER_ADDRESS_INDEX - 🔐 the index of the address when using Ledger + --guardian-wallet-index GUARDIAN_WALLET_INDEX 🔑 the address index; can be used for PEM files, keyfiles of type + mnemonic or Ledger devices (default: 0) + --relayer-pem RELAYER_PEM 🔑 the PEM file, if keyfile not provided + --relayer-keyfile RELAYER_KEYFILE 🔑 a JSON keyfile, if PEM not provided + --relayer-passfile RELAYER_PASSFILE 🔑 a file containing keyfile's password, if keyfile provided. If not + provided, you'll be prompted to enter the password. + --relayer-ledger 🔐 bool flag for signing transaction using ledger + --relayer-wallet-index RELAYER_WALLET_INDEX 🔑 the address index; can be used for PEM files, keyfiles of type + mnemonic or Ledger devices (default: 0) ``` ### Contract.Upgrade @@ -394,68 +304,63 @@ Output example: } positional arguments: - contract 🖄 the address of the Smart Contract + contract 🖄 the bech32 address of the Smart Contract options: - -h, --help show this help message and exit - --abi ABI the ABI of the Smart Contract - --outfile OUTFILE where to save the output (default: stdout) - --bytecode BYTECODE the file containing the WASM bytecode - --metadata-not-upgradeable ‼ mark the contract as NOT upgradeable (default: upgradeable) - --metadata-not-readable ‼ mark the contract as NOT readable (default: readable) - --metadata-payable ‼ mark the contract as payable (default: not payable) - --metadata-payable-by-sc ‼ mark the contract as payable by SC (default: not payable by SC) - --pem PEM 🔑 the PEM file, if keyfile not provided - --pem-index PEM_INDEX 🔑 the index in the PEM file (default: 0) - --keyfile KEYFILE 🔑 a JSON keyfile, if PEM not provided - --passfile PASSFILE 🔑 a file containing keyfile's password, if keyfile provided - --ledger 🔐 bool flag for signing transaction using ledger - --ledger-account-index LEDGER_ACCOUNT_INDEX 🔐 the index of the account when using Ledger - --ledger-address-index LEDGER_ADDRESS_INDEX 🔐 the index of the address when using Ledger - --sender-username SENDER_USERNAME 🖄 the username of the sender - --proxy PROXY 🔗 the URL of the proxy - --nonce NONCE # the nonce for the transaction - --recall-nonce ⭮ whether to recall the nonce when creating the transaction (default: - False) - --gas-price GAS_PRICE ⛽ the gas price (default: 1000000000) - --gas-limit GAS_LIMIT ⛽ the gas limit - --value VALUE the value to transfer (default: 0) - --chain CHAIN the chain identifier - --version VERSION the transaction version (default: 2) - --relayer RELAYER the bech32 address of the relayer - --relayer-pem RELAYER_PEM 🔑 the PEM file, if keyfile not provided - --relayer-pem-index RELAYER_PEM_INDEX 🔑 the index in the PEM file (default: 0) - --relayer-keyfile RELAYER_KEYFILE 🔑 a JSON keyfile, if PEM not provided - --relayer-passfile RELAYER_PASSFILE 🔑 a file containing keyfile's password, if keyfile provided - --relayer-ledger 🔐 bool flag for signing transaction using ledger - --relayer-ledger-account-index RELAYER_LEDGER_ACCOUNT_INDEX - 🔐 the index of the account when using Ledger - --relayer-ledger-address-index RELAYER_LEDGER_ADDRESS_INDEX - 🔐 the index of the address when using Ledger - --guardian GUARDIAN the address of the guradian - --guardian-service-url GUARDIAN_SERVICE_URL the url of the guardian service - --guardian-2fa-code GUARDIAN_2FA_CODE the 2fa code for the guardian - --options OPTIONS the transaction options (default: 0) - --arguments ARGUMENTS [ARGUMENTS ...] arguments for the contract transaction, as [number, bech32-address, - ascii string, boolean] or hex-encoded. E.g. --arguments 42 0x64 1000 - 0xabba str:TOK-a1c2ef true erd1[..] - --arguments-file ARGUMENTS_FILE a json file containing the arguments. ONLY if abi file is provided. - E.g. [{ 'to': 'erd1...', 'amount': 10000000000 }] - --wait-result signal to wait for the transaction result - only valid if --send is - set - --timeout TIMEOUT max num of seconds to wait for result - only valid if --wait-result is - set - --send ✓ whether to broadcast the transaction (default: False) - --simulate whether to simulate the transaction (default: False) - --guardian-pem GUARDIAN_PEM 🔑 the PEM file, if keyfile not provided - --guardian-pem-index GUARDIAN_PEM_INDEX 🔑 the index in the PEM file (default: 0) - --guardian-keyfile GUARDIAN_KEYFILE 🔑 a JSON keyfile, if PEM not provided - --guardian-passfile GUARDIAN_PASSFILE 🔑 a file containing keyfile's password, if keyfile provided - --guardian-ledger 🔐 bool flag for signing transaction using ledger - --guardian-ledger-account-index GUARDIAN_LEDGER_ACCOUNT_INDEX - 🔐 the index of the account when using Ledger - --guardian-ledger-address-index GUARDIAN_LEDGER_ADDRESS_INDEX - 🔐 the index of the address when using Ledger + -h, --help show this help message and exit + --abi ABI the ABI file of the Smart Contract + --outfile OUTFILE where to save the output (default: stdout) + --bytecode BYTECODE the file containing the WASM bytecode + --metadata-not-upgradeable ‼ mark the contract as NOT upgradeable (default: upgradeable) + --metadata-not-readable ‼ mark the contract as NOT readable (default: readable) + --metadata-payable ‼ mark the contract as payable (default: not payable) + --metadata-payable-by-sc ‼ mark the contract as payable by SC (default: not payable by SC) + --pem PEM 🔑 the PEM file, if keyfile not provided + --keyfile KEYFILE 🔑 a JSON keyfile, if PEM not provided + --passfile PASSFILE 🔑 a file containing keyfile's password, if keyfile provided. If not + provided, you'll be prompted to enter the password. + --ledger 🔐 bool flag for signing transaction using ledger + --sender-wallet-index SENDER_WALLET_INDEX 🔑 the address index; can be used for PEM files, keyfiles of type + mnemonic or Ledger devices (default: 0) + --sender-username SENDER_USERNAME 🖄 the username of the sender + --proxy PROXY 🔗 the URL of the proxy + --nonce NONCE # the nonce for the transaction + --recall-nonce ⭮ whether to recall the nonce when creating the transaction (default: + False) + --gas-price GAS_PRICE ⛽ the gas price (default: 1000000000) + --gas-limit GAS_LIMIT ⛽ the gas limit + --value VALUE the value to transfer (default: 0) + --chain CHAIN the chain identifier + --version VERSION the transaction version (default: 2) + --options OPTIONS the transaction options (default: 0) + --relayer RELAYER the bech32 address of the relayer + --guardian GUARDIAN the bech32 address of the guardian + --arguments ARGUMENTS [ARGUMENTS ...] arguments for the contract transaction, as [number, bech32-address, + ascii string, boolean] or hex-encoded. E.g. --arguments 42 0x64 1000 + 0xabba str:TOK-a1c2ef true addr:erd1[..] + --arguments-file ARGUMENTS_FILE a json file containing the arguments. ONLY if abi file is provided. + E.g. [{ 'to': 'erd1...', 'amount': 10000000000 }] + --wait-result signal to wait for the transaction result - only valid if --send is set + --timeout TIMEOUT max num of seconds to wait for result - only valid if --wait-result is + set + --send ✓ whether to broadcast the transaction (default: False) + --simulate whether to simulate the transaction (default: False) + --guardian-service-url GUARDIAN_SERVICE_URL the url of the guardian service + --guardian-2fa-code GUARDIAN_2FA_CODE the 2fa code for the guardian + --guardian-pem GUARDIAN_PEM 🔑 the PEM file, if keyfile not provided + --guardian-keyfile GUARDIAN_KEYFILE 🔑 a JSON keyfile, if PEM not provided + --guardian-passfile GUARDIAN_PASSFILE 🔑 a file containing keyfile's password, if keyfile provided. If not + provided, you'll be prompted to enter the password. + --guardian-ledger 🔐 bool flag for signing transaction using ledger + --guardian-wallet-index GUARDIAN_WALLET_INDEX 🔑 the address index; can be used for PEM files, keyfiles of type + mnemonic or Ledger devices (default: 0) + --relayer-pem RELAYER_PEM 🔑 the PEM file, if keyfile not provided + --relayer-keyfile RELAYER_KEYFILE 🔑 a JSON keyfile, if PEM not provided + --relayer-passfile RELAYER_PASSFILE 🔑 a file containing keyfile's password, if keyfile provided. If not + provided, you'll be prompted to enter the password. + --relayer-ledger 🔐 bool flag for signing transaction using ledger + --relayer-wallet-index RELAYER_WALLET_INDEX 🔑 the address index; can be used for PEM files, keyfiles of type + mnemonic or Ledger devices (default: 0) ``` ### Contract.Query @@ -468,58 +373,76 @@ usage: mxpy contract query [-h] ... Query a Smart Contract (call a pure function) positional arguments: - contract 🖄 the address of the Smart Contract + contract 🖄 the bech32 address of the Smart Contract options: -h, --help show this help message and exit - --abi ABI the ABI of the Smart Contract + --abi ABI the ABI file of the Smart Contract --proxy PROXY 🔗 the URL of the proxy --function FUNCTION the function to call --arguments ARGUMENTS [ARGUMENTS ...] arguments for the contract transaction, as [number, bech32-address, ascii string, boolean] or hex-encoded. E.g. --arguments 42 0x64 1000 0xabba - str:TOK-a1c2ef true erd1[..] + str:TOK-a1c2ef true addr:erd1[..] --arguments-file ARGUMENTS_FILE a json file containing the arguments. ONLY if abi file is provided. E.g. [{ 'to': 'erd1...', 'amount': 10000000000 }] ``` -### Contract.Report +### Contract.Verify ``` -$ mxpy contract report --help -usage: mxpy contract report [-h] ... +$ mxpy contract verify --help +usage: mxpy contract verify [-h] ... -Print a detailed report of the smart contracts. +Verify the authenticity of the code of a deployed Smart Contract + +positional arguments: + contract 🖄 the bech32 address of the Smart Contract options: - -h, --help show this help message and exit - --skip-build skips the step of building of the wasm contracts - --skip-twiggy skips the steps of building the debug wasm files and running twiggy - --output-format {github-markdown,text-markdown,json} - report output format (default: text-markdown) - --output-file OUTPUT_FILE if specified, the output is written to a file, otherwise it's written - to the standard output - --compare report-1.json [report-2.json ...] create a comparison from two or more reports - --path PATH the project directory (default: current directory) - --no-wasm-opt do not optimize wasm files after the build (default: False) - --wasm-symbols for rust projects, does not strip the symbols from the wasm output. - Useful for analysing the bytecode. Creates larger wasm files. Avoid in - production (default: False) - --wasm-name WASM_NAME for rust projects, optionally specify the name of the wasm bytecode - output file - --wasm-suffix WASM_SUFFIX for rust projects, optionally specify the suffix of the wasm bytecode - output file - --target-dir TARGET_DIR for rust projects, forward the parameter to Cargo - --wat also generate a WAT file when building - --mir also emit MIR files when building - --llvm-ir also emit LL (LLVM) files when building - --ignore IGNORE ignore all directories with these names. [default: target] - --no-imports skips extracting the EI imports after building the contracts - --no-abi-git-version skips loading the Git version into the ABI - --twiggy-top generate a twiggy top report after building - --twiggy-paths generate a twiggy paths report after building - --twiggy-monos generate a twiggy monos report after building - --twiggy-dominators generate a twiggy dominators report after building + -h, --help show this help message and exit + --packaged-src PACKAGED_SRC JSON file containing the source code of the contract + --verifier-url VERIFIER_URL the url of the service that validates the contract + --docker-image DOCKER_IMAGE the docker image used for the build + --contract-variant CONTRACT_VARIANT in case of a multicontract, specify the contract variant you want to verify + --pem PEM 🔑 the PEM file, if keyfile not provided + --keyfile KEYFILE 🔑 a JSON keyfile, if PEM not provided + --passfile PASSFILE 🔑 a file containing keyfile's password, if keyfile provided. If not + provided, you'll be prompted to enter the password. + --ledger 🔐 bool flag for signing transaction using ledger + --sender-wallet-index SENDER_WALLET_INDEX 🔑 the address index; can be used for PEM files, keyfiles of type mnemonic + or Ledger devices (default: 0) + --sender-username SENDER_USERNAME 🖄 the username of the sender + +``` +### Contract.ReproducibleBuild + + +``` +$ mxpy contract reproducible-build --help +usage: mxpy contract reproducible-build [-h] ... + +Build a Smart Contract and get the same output as a previously built Smart Contract + +positional arguments: + project the project directory (default: current directory) + +options: + -h, --help show this help message and exit + --debug set debug flag (default: False) + --no-optimization bypass optimizations (for clang) (default: False) + --no-wasm-opt do not optimize wasm files after the build (default: False) + --cargo-target-dir CARGO_TARGET_DIR for rust projects, forward the parameter to Cargo + --wasm-symbols for rust projects, does not strip the symbols from the wasm output. Useful for + analysing the bytecode. Creates larger wasm files. Avoid in production (default: + False) + --wasm-name WASM_NAME for rust projects, optionally specify the name of the wasm bytecode output file + --wasm-suffix WASM_SUFFIX for rust projects, optionally specify the suffix of the wasm bytecode output file + --docker-image DOCKER_IMAGE the docker image tag used to build the contract + --contract CONTRACT contract to build (contract name, as found in Cargo.toml) + --no-docker-interactive + --no-docker-tty + --no-default-platform do not set DOCKER_DEFAULT_PLATFORM environment variable to 'linux/amd64' ``` ## Group **Transactions** @@ -532,7 +455,7 @@ usage: mxpy tx COMMAND [-h] ... Create and broadcast Transactions COMMANDS: - {new,send,get,sign,relay} + {new,send,sign,relay} OPTIONS: -h, --help show this help message and exit @@ -542,7 +465,6 @@ COMMANDS summary ---------------- new Create a new transaction. send Send a previously saved transaction. -get Get a transaction. sign Sign a previously saved transaction. relay Relay a previously saved transaction. @@ -572,12 +494,12 @@ Output example: options: -h, --help show this help message and exit --pem PEM 🔑 the PEM file, if keyfile not provided - --pem-index PEM_INDEX 🔑 the index in the PEM file (default: 0) --keyfile KEYFILE 🔑 a JSON keyfile, if PEM not provided - --passfile PASSFILE 🔑 a file containing keyfile's password, if keyfile provided + --passfile PASSFILE 🔑 a file containing keyfile's password, if keyfile provided. If not + provided, you'll be prompted to enter the password. --ledger 🔐 bool flag for signing transaction using ledger - --ledger-account-index LEDGER_ACCOUNT_INDEX 🔐 the index of the account when using Ledger - --ledger-address-index LEDGER_ADDRESS_INDEX 🔐 the index of the address when using Ledger + --sender-wallet-index SENDER_WALLET_INDEX 🔑 the address index; can be used for PEM files, keyfiles of type + mnemonic or Ledger devices (default: 0) --sender-username SENDER_USERNAME 🖄 the username of the sender --nonce NONCE # the nonce for the transaction --recall-nonce ⭮ whether to recall the nonce when creating the transaction (default: @@ -590,20 +512,9 @@ options: --data DATA the payload, or 'memo' of the transaction (default: ) --chain CHAIN the chain identifier --version VERSION the transaction version (default: 2) - --relayer RELAYER the bech32 address of the relayer - --relayer-pem RELAYER_PEM 🔑 the PEM file, if keyfile not provided - --relayer-pem-index RELAYER_PEM_INDEX 🔑 the index in the PEM file (default: 0) - --relayer-keyfile RELAYER_KEYFILE 🔑 a JSON keyfile, if PEM not provided - --relayer-passfile RELAYER_PASSFILE 🔑 a file containing keyfile's password, if keyfile provided - --relayer-ledger 🔐 bool flag for signing transaction using ledger - --relayer-ledger-account-index RELAYER_LEDGER_ACCOUNT_INDEX - 🔐 the index of the account when using Ledger - --relayer-ledger-address-index RELAYER_LEDGER_ADDRESS_INDEX - 🔐 the index of the address when using Ledger - --guardian GUARDIAN the address of the guradian - --guardian-service-url GUARDIAN_SERVICE_URL the url of the guardian service - --guardian-2fa-code GUARDIAN_2FA_CODE the 2fa code for the guardian --options OPTIONS the transaction options (default: 0) + --relayer RELAYER the bech32 address of the relayer + --guardian GUARDIAN the bech32 address of the guardian --data-file DATA_FILE a file containing transaction data --token-transfers TOKEN_TRANSFERS [TOKEN_TRANSFERS ...] token transfers for transfer & execute, as [token, amount] E.g. @@ -611,17 +522,23 @@ options: --outfile OUTFILE where to save the output (signed transaction, hash) (default: stdout) --send ✓ whether to broadcast the transaction (default: False) --simulate whether to simulate the transaction (default: False) - --relay whether to relay the transaction (default: False) --proxy PROXY 🔗 the URL of the proxy + --guardian-service-url GUARDIAN_SERVICE_URL the url of the guardian service + --guardian-2fa-code GUARDIAN_2FA_CODE the 2fa code for the guardian --guardian-pem GUARDIAN_PEM 🔑 the PEM file, if keyfile not provided - --guardian-pem-index GUARDIAN_PEM_INDEX 🔑 the index in the PEM file (default: 0) --guardian-keyfile GUARDIAN_KEYFILE 🔑 a JSON keyfile, if PEM not provided - --guardian-passfile GUARDIAN_PASSFILE 🔑 a file containing keyfile's password, if keyfile provided + --guardian-passfile GUARDIAN_PASSFILE 🔑 a file containing keyfile's password, if keyfile provided. If not + provided, you'll be prompted to enter the password. --guardian-ledger 🔐 bool flag for signing transaction using ledger - --guardian-ledger-account-index GUARDIAN_LEDGER_ACCOUNT_INDEX - 🔐 the index of the account when using Ledger - --guardian-ledger-address-index GUARDIAN_LEDGER_ADDRESS_INDEX - 🔐 the index of the address when using Ledger + --guardian-wallet-index GUARDIAN_WALLET_INDEX 🔑 the address index; can be used for PEM files, keyfiles of type + mnemonic or Ledger devices (default: 0) + --relayer-pem RELAYER_PEM 🔑 the PEM file, if keyfile not provided + --relayer-keyfile RELAYER_KEYFILE 🔑 a JSON keyfile, if PEM not provided + --relayer-passfile RELAYER_PASSFILE 🔑 a file containing keyfile's password, if keyfile provided. If not + provided, you'll be prompted to enter the password. + --relayer-ledger 🔐 bool flag for signing transaction using ledger + --relayer-wallet-index RELAYER_WALLET_INDEX 🔑 the address index; can be used for PEM files, keyfiles of type + mnemonic or Ledger devices (default: 0) --wait-result signal to wait for the transaction result - only valid if --send is set --timeout TIMEOUT max num of seconds to wait for result - only valid if --wait-result is @@ -657,33 +574,98 @@ options: --proxy PROXY 🔗 the URL of the proxy ``` -### Transactions.Get +### Transactions.Sign ``` -$ mxpy tx get --help -usage: mxpy tx get [-h] ... +$ mxpy tx sign --help +usage: mxpy tx sign [-h] ... -Get a transaction. +Sign a previously saved transaction. Output example: =============== { - "transactionOnNetwork": { + "emittedTransaction": { "nonce": 42, "sender": "alice", "receiver": "bob", "...": "..." - } + }, + "emittedTransactionData": "the transaction data, not encoded", + "emittedTransactionHash": "the transaction hash" } options: - -h, --help show this help message and exit - --hash HASH the hash - --sender SENDER the sender address - --with-results will also return the results of transaction - --proxy PROXY 🔗 the URL of the proxy - --omit-fields OMIT_FIELDS omit fields in the output payload (default: []) + -h, --help show this help message and exit + --pem PEM 🔑 the PEM file, if keyfile not provided + --keyfile KEYFILE 🔑 a JSON keyfile, if PEM not provided + --passfile PASSFILE 🔑 a file containing keyfile's password, if keyfile provided. If not + provided, you'll be prompted to enter the password. + --ledger 🔐 bool flag for signing transaction using ledger + --sender-wallet-index SENDER_WALLET_INDEX 🔑 the address index; can be used for PEM files, keyfiles of type + mnemonic or Ledger devices (default: 0) + --sender-username SENDER_USERNAME 🖄 the username of the sender + --infile INFILE input file (a previously saved transaction) + --outfile OUTFILE where to save the output (the signed transaction) (default: stdout) + --send ✓ whether to broadcast the transaction (default: False) + --simulate whether to simulate the transaction (default: False) + --proxy PROXY 🔗 the URL of the proxy + --guardian-service-url GUARDIAN_SERVICE_URL the url of the guardian service + --guardian-2fa-code GUARDIAN_2FA_CODE the 2fa code for the guardian + --guardian-pem GUARDIAN_PEM 🔑 the PEM file, if keyfile not provided + --guardian-keyfile GUARDIAN_KEYFILE 🔑 a JSON keyfile, if PEM not provided + --guardian-passfile GUARDIAN_PASSFILE 🔑 a file containing keyfile's password, if keyfile provided. If not + provided, you'll be prompted to enter the password. + --guardian-ledger 🔐 bool flag for signing transaction using ledger + --guardian-wallet-index GUARDIAN_WALLET_INDEX 🔑 the address index; can be used for PEM files, keyfiles of type + mnemonic or Ledger devices (default: 0) + --relayer-pem RELAYER_PEM 🔑 the PEM file, if keyfile not provided + --relayer-keyfile RELAYER_KEYFILE 🔑 a JSON keyfile, if PEM not provided + --relayer-passfile RELAYER_PASSFILE 🔑 a file containing keyfile's password, if keyfile provided. If not + provided, you'll be prompted to enter the password. + --relayer-ledger 🔐 bool flag for signing transaction using ledger + --relayer-wallet-index RELAYER_WALLET_INDEX 🔑 the address index; can be used for PEM files, keyfiles of type + mnemonic or Ledger devices (default: 0) + +``` +### Transactions.Relay + + +``` +$ mxpy tx relay --help +usage: mxpy tx relay [-h] ... + +Relay a previously saved transaction. + +Output example: +=============== +{ + "emittedTransaction": { + "nonce": 42, + "sender": "alice", + "receiver": "bob", + "...": "..." + }, + "emittedTransactionData": "the transaction data, not encoded", + "emittedTransactionHash": "the transaction hash" +} + +options: + -h, --help show this help message and exit + --relayer-pem RELAYER_PEM 🔑 the PEM file, if keyfile not provided + --relayer-keyfile RELAYER_KEYFILE 🔑 a JSON keyfile, if PEM not provided + --relayer-passfile RELAYER_PASSFILE 🔑 a file containing keyfile's password, if keyfile provided. If not + provided, you'll be prompted to enter the password. + --relayer-ledger 🔐 bool flag for signing transaction using ledger + --relayer-wallet-index RELAYER_WALLET_INDEX 🔑 the address index; can be used for PEM files, keyfiles of type mnemonic + or Ledger devices (default: 0) + --infile INFILE input file (a previously saved transaction) + --outfile OUTFILE where to save the output (the relayer signed transaction) (default: + stdout) + --send ✓ whether to broadcast the transaction (default: False) + --simulate whether to simulate the transaction (default: False) + --proxy PROXY 🔗 the URL of the proxy ``` ## Group **Validator** @@ -728,54 +710,49 @@ usage: mxpy validator stake [-h] ... Stake value into the Network options: - -h, --help show this help message and exit - --proxy PROXY 🔗 the URL of the proxy - --pem PEM 🔑 the PEM file, if keyfile not provided - --pem-index PEM_INDEX 🔑 the index in the PEM file (default: 0) - --keyfile KEYFILE 🔑 a JSON keyfile, if PEM not provided - --passfile PASSFILE 🔑 a file containing keyfile's password, if keyfile provided - --ledger 🔐 bool flag for signing transaction using ledger - --ledger-account-index LEDGER_ACCOUNT_INDEX 🔐 the index of the account when using Ledger - --ledger-address-index LEDGER_ADDRESS_INDEX 🔐 the index of the address when using Ledger - --sender-username SENDER_USERNAME 🖄 the username of the sender - --nonce NONCE # the nonce for the transaction - --recall-nonce ⭮ whether to recall the nonce when creating the transaction (default: - False) - --gas-price GAS_PRICE ⛽ the gas price (default: 1000000000) - --gas-limit GAS_LIMIT ⛽ the gas limit - --estimate-gas ⛽ whether to estimate the gas limit (default: 0) - --value VALUE the value to transfer (default: 0) - --chain CHAIN the chain identifier - --version VERSION the transaction version (default: 2) - --relayer RELAYER the bech32 address of the relayer - --relayer-pem RELAYER_PEM 🔑 the PEM file, if keyfile not provided - --relayer-pem-index RELAYER_PEM_INDEX 🔑 the index in the PEM file (default: 0) - --relayer-keyfile RELAYER_KEYFILE 🔑 a JSON keyfile, if PEM not provided - --relayer-passfile RELAYER_PASSFILE 🔑 a file containing keyfile's password, if keyfile provided - --relayer-ledger 🔐 bool flag for signing transaction using ledger - --relayer-ledger-account-index RELAYER_LEDGER_ACCOUNT_INDEX - 🔐 the index of the account when using Ledger - --relayer-ledger-address-index RELAYER_LEDGER_ADDRESS_INDEX - 🔐 the index of the address when using Ledger - --guardian GUARDIAN the address of the guradian - --guardian-service-url GUARDIAN_SERVICE_URL the url of the guardian service - --guardian-2fa-code GUARDIAN_2FA_CODE the 2fa code for the guardian - --options OPTIONS the transaction options (default: 0) - --send ✓ whether to broadcast the transaction (default: False) - --simulate whether to simulate the transaction (default: False) - --outfile OUTFILE where to save the output (signed transaction, hash) (default: stdout) - --guardian-pem GUARDIAN_PEM 🔑 the PEM file, if keyfile not provided - --guardian-pem-index GUARDIAN_PEM_INDEX 🔑 the index in the PEM file (default: 0) - --guardian-keyfile GUARDIAN_KEYFILE 🔑 a JSON keyfile, if PEM not provided - --guardian-passfile GUARDIAN_PASSFILE 🔑 a file containing keyfile's password, if keyfile provided - --guardian-ledger 🔐 bool flag for signing transaction using ledger - --guardian-ledger-account-index GUARDIAN_LEDGER_ACCOUNT_INDEX - 🔐 the index of the account when using Ledger - --guardian-ledger-address-index GUARDIAN_LEDGER_ADDRESS_INDEX - 🔐 the index of the address when using Ledger - --reward-address REWARD_ADDRESS the reward address - --validators-file VALIDATORS_FILE a JSON file describing the Nodes - --top-up Stake value for top up + -h, --help show this help message and exit + --proxy PROXY 🔗 the URL of the proxy + --pem PEM 🔑 the PEM file, if keyfile not provided + --keyfile KEYFILE 🔑 a JSON keyfile, if PEM not provided + --passfile PASSFILE 🔑 a file containing keyfile's password, if keyfile provided. If not + provided, you'll be prompted to enter the password. + --ledger 🔐 bool flag for signing transaction using ledger + --sender-wallet-index SENDER_WALLET_INDEX 🔑 the address index; can be used for PEM files, keyfiles of type + mnemonic or Ledger devices (default: 0) + --sender-username SENDER_USERNAME 🖄 the username of the sender + --nonce NONCE # the nonce for the transaction + --recall-nonce ⭮ whether to recall the nonce when creating the transaction (default: + False) + --gas-price GAS_PRICE ⛽ the gas price (default: 1000000000) + --gas-limit GAS_LIMIT ⛽ the gas limit + --value VALUE the value to transfer (default: 0) + --chain CHAIN the chain identifier + --version VERSION the transaction version (default: 2) + --options OPTIONS the transaction options (default: 0) + --relayer RELAYER the bech32 address of the relayer + --guardian GUARDIAN the bech32 address of the guardian + --send ✓ whether to broadcast the transaction (default: False) + --simulate whether to simulate the transaction (default: False) + --outfile OUTFILE where to save the output (signed transaction, hash) (default: stdout) + --guardian-service-url GUARDIAN_SERVICE_URL the url of the guardian service + --guardian-2fa-code GUARDIAN_2FA_CODE the 2fa code for the guardian + --guardian-pem GUARDIAN_PEM 🔑 the PEM file, if keyfile not provided + --guardian-keyfile GUARDIAN_KEYFILE 🔑 a JSON keyfile, if PEM not provided + --guardian-passfile GUARDIAN_PASSFILE 🔑 a file containing keyfile's password, if keyfile provided. If not + provided, you'll be prompted to enter the password. + --guardian-ledger 🔐 bool flag for signing transaction using ledger + --guardian-wallet-index GUARDIAN_WALLET_INDEX 🔑 the address index; can be used for PEM files, keyfiles of type + mnemonic or Ledger devices (default: 0) + --relayer-pem RELAYER_PEM 🔑 the PEM file, if keyfile not provided + --relayer-keyfile RELAYER_KEYFILE 🔑 a JSON keyfile, if PEM not provided + --relayer-passfile RELAYER_PASSFILE 🔑 a file containing keyfile's password, if keyfile provided. If not + provided, you'll be prompted to enter the password. + --relayer-ledger 🔐 bool flag for signing transaction using ledger + --relayer-wallet-index RELAYER_WALLET_INDEX 🔑 the address index; can be used for PEM files, keyfiles of type + mnemonic or Ledger devices (default: 0) + --reward-address REWARD_ADDRESS the reward address + --validators-pem VALIDATORS_PEM a PEM file describing the nodes; can contain multiple nodes + --top-up Stake value for top up ``` ### Validator.Unstake @@ -788,52 +765,47 @@ usage: mxpy validator unstake [-h] ... Unstake value options: - -h, --help show this help message and exit - --proxy PROXY 🔗 the URL of the proxy - --pem PEM 🔑 the PEM file, if keyfile not provided - --pem-index PEM_INDEX 🔑 the index in the PEM file (default: 0) - --keyfile KEYFILE 🔑 a JSON keyfile, if PEM not provided - --passfile PASSFILE 🔑 a file containing keyfile's password, if keyfile provided - --ledger 🔐 bool flag for signing transaction using ledger - --ledger-account-index LEDGER_ACCOUNT_INDEX 🔐 the index of the account when using Ledger - --ledger-address-index LEDGER_ADDRESS_INDEX 🔐 the index of the address when using Ledger - --sender-username SENDER_USERNAME 🖄 the username of the sender - --nonce NONCE # the nonce for the transaction - --recall-nonce ⭮ whether to recall the nonce when creating the transaction (default: - False) - --gas-price GAS_PRICE ⛽ the gas price (default: 1000000000) - --gas-limit GAS_LIMIT ⛽ the gas limit - --estimate-gas ⛽ whether to estimate the gas limit (default: 0) - --value VALUE the value to transfer (default: 0) - --chain CHAIN the chain identifier - --version VERSION the transaction version (default: 2) - --relayer RELAYER the bech32 address of the relayer - --relayer-pem RELAYER_PEM 🔑 the PEM file, if keyfile not provided - --relayer-pem-index RELAYER_PEM_INDEX 🔑 the index in the PEM file (default: 0) - --relayer-keyfile RELAYER_KEYFILE 🔑 a JSON keyfile, if PEM not provided - --relayer-passfile RELAYER_PASSFILE 🔑 a file containing keyfile's password, if keyfile provided - --relayer-ledger 🔐 bool flag for signing transaction using ledger - --relayer-ledger-account-index RELAYER_LEDGER_ACCOUNT_INDEX - 🔐 the index of the account when using Ledger - --relayer-ledger-address-index RELAYER_LEDGER_ADDRESS_INDEX - 🔐 the index of the address when using Ledger - --guardian GUARDIAN the address of the guradian - --guardian-service-url GUARDIAN_SERVICE_URL the url of the guardian service - --guardian-2fa-code GUARDIAN_2FA_CODE the 2fa code for the guardian - --options OPTIONS the transaction options (default: 0) - --send ✓ whether to broadcast the transaction (default: False) - --simulate whether to simulate the transaction (default: False) - --outfile OUTFILE where to save the output (signed transaction, hash) (default: stdout) - --guardian-pem GUARDIAN_PEM 🔑 the PEM file, if keyfile not provided - --guardian-pem-index GUARDIAN_PEM_INDEX 🔑 the index in the PEM file (default: 0) - --guardian-keyfile GUARDIAN_KEYFILE 🔑 a JSON keyfile, if PEM not provided - --guardian-passfile GUARDIAN_PASSFILE 🔑 a file containing keyfile's password, if keyfile provided - --guardian-ledger 🔐 bool flag for signing transaction using ledger - --guardian-ledger-account-index GUARDIAN_LEDGER_ACCOUNT_INDEX - 🔐 the index of the account when using Ledger - --guardian-ledger-address-index GUARDIAN_LEDGER_ADDRESS_INDEX - 🔐 the index of the address when using Ledger - --nodes-public-keys NODES_PUBLIC_KEYS the public keys of the nodes as CSV (addrA,addrB) + -h, --help show this help message and exit + --proxy PROXY 🔗 the URL of the proxy + --pem PEM 🔑 the PEM file, if keyfile not provided + --keyfile KEYFILE 🔑 a JSON keyfile, if PEM not provided + --passfile PASSFILE 🔑 a file containing keyfile's password, if keyfile provided. If not + provided, you'll be prompted to enter the password. + --ledger 🔐 bool flag for signing transaction using ledger + --sender-wallet-index SENDER_WALLET_INDEX 🔑 the address index; can be used for PEM files, keyfiles of type + mnemonic or Ledger devices (default: 0) + --sender-username SENDER_USERNAME 🖄 the username of the sender + --nonce NONCE # the nonce for the transaction + --recall-nonce ⭮ whether to recall the nonce when creating the transaction (default: + False) + --gas-price GAS_PRICE ⛽ the gas price (default: 1000000000) + --gas-limit GAS_LIMIT ⛽ the gas limit + --value VALUE the value to transfer (default: 0) + --chain CHAIN the chain identifier + --version VERSION the transaction version (default: 2) + --options OPTIONS the transaction options (default: 0) + --relayer RELAYER the bech32 address of the relayer + --guardian GUARDIAN the bech32 address of the guardian + --send ✓ whether to broadcast the transaction (default: False) + --simulate whether to simulate the transaction (default: False) + --outfile OUTFILE where to save the output (signed transaction, hash) (default: stdout) + --guardian-service-url GUARDIAN_SERVICE_URL the url of the guardian service + --guardian-2fa-code GUARDIAN_2FA_CODE the 2fa code for the guardian + --guardian-pem GUARDIAN_PEM 🔑 the PEM file, if keyfile not provided + --guardian-keyfile GUARDIAN_KEYFILE 🔑 a JSON keyfile, if PEM not provided + --guardian-passfile GUARDIAN_PASSFILE 🔑 a file containing keyfile's password, if keyfile provided. If not + provided, you'll be prompted to enter the password. + --guardian-ledger 🔐 bool flag for signing transaction using ledger + --guardian-wallet-index GUARDIAN_WALLET_INDEX 🔑 the address index; can be used for PEM files, keyfiles of type + mnemonic or Ledger devices (default: 0) + --relayer-pem RELAYER_PEM 🔑 the PEM file, if keyfile not provided + --relayer-keyfile RELAYER_KEYFILE 🔑 a JSON keyfile, if PEM not provided + --relayer-passfile RELAYER_PASSFILE 🔑 a file containing keyfile's password, if keyfile provided. If not + provided, you'll be prompted to enter the password. + --relayer-ledger 🔐 bool flag for signing transaction using ledger + --relayer-wallet-index RELAYER_WALLET_INDEX 🔑 the address index; can be used for PEM files, keyfiles of type + mnemonic or Ledger devices (default: 0) + --nodes-public-keys NODES_PUBLIC_KEYS the public keys of the nodes as CSV (addrA,addrB) ``` ### Validator.Unjail @@ -846,52 +818,47 @@ usage: mxpy validator unjail [-h] ... Unjail a Validator Node options: - -h, --help show this help message and exit - --proxy PROXY 🔗 the URL of the proxy - --pem PEM 🔑 the PEM file, if keyfile not provided - --pem-index PEM_INDEX 🔑 the index in the PEM file (default: 0) - --keyfile KEYFILE 🔑 a JSON keyfile, if PEM not provided - --passfile PASSFILE 🔑 a file containing keyfile's password, if keyfile provided - --ledger 🔐 bool flag for signing transaction using ledger - --ledger-account-index LEDGER_ACCOUNT_INDEX 🔐 the index of the account when using Ledger - --ledger-address-index LEDGER_ADDRESS_INDEX 🔐 the index of the address when using Ledger - --sender-username SENDER_USERNAME 🖄 the username of the sender - --nonce NONCE # the nonce for the transaction - --recall-nonce ⭮ whether to recall the nonce when creating the transaction (default: - False) - --gas-price GAS_PRICE ⛽ the gas price (default: 1000000000) - --gas-limit GAS_LIMIT ⛽ the gas limit - --estimate-gas ⛽ whether to estimate the gas limit (default: 0) - --value VALUE the value to transfer (default: 0) - --chain CHAIN the chain identifier - --version VERSION the transaction version (default: 2) - --relayer RELAYER the bech32 address of the relayer - --relayer-pem RELAYER_PEM 🔑 the PEM file, if keyfile not provided - --relayer-pem-index RELAYER_PEM_INDEX 🔑 the index in the PEM file (default: 0) - --relayer-keyfile RELAYER_KEYFILE 🔑 a JSON keyfile, if PEM not provided - --relayer-passfile RELAYER_PASSFILE 🔑 a file containing keyfile's password, if keyfile provided - --relayer-ledger 🔐 bool flag for signing transaction using ledger - --relayer-ledger-account-index RELAYER_LEDGER_ACCOUNT_INDEX - 🔐 the index of the account when using Ledger - --relayer-ledger-address-index RELAYER_LEDGER_ADDRESS_INDEX - 🔐 the index of the address when using Ledger - --guardian GUARDIAN the address of the guradian - --guardian-service-url GUARDIAN_SERVICE_URL the url of the guardian service - --guardian-2fa-code GUARDIAN_2FA_CODE the 2fa code for the guardian - --options OPTIONS the transaction options (default: 0) - --send ✓ whether to broadcast the transaction (default: False) - --simulate whether to simulate the transaction (default: False) - --outfile OUTFILE where to save the output (signed transaction, hash) (default: stdout) - --guardian-pem GUARDIAN_PEM 🔑 the PEM file, if keyfile not provided - --guardian-pem-index GUARDIAN_PEM_INDEX 🔑 the index in the PEM file (default: 0) - --guardian-keyfile GUARDIAN_KEYFILE 🔑 a JSON keyfile, if PEM not provided - --guardian-passfile GUARDIAN_PASSFILE 🔑 a file containing keyfile's password, if keyfile provided - --guardian-ledger 🔐 bool flag for signing transaction using ledger - --guardian-ledger-account-index GUARDIAN_LEDGER_ACCOUNT_INDEX - 🔐 the index of the account when using Ledger - --guardian-ledger-address-index GUARDIAN_LEDGER_ADDRESS_INDEX - 🔐 the index of the address when using Ledger - --nodes-public-keys NODES_PUBLIC_KEYS the public keys of the nodes as CSV (addrA,addrB) + -h, --help show this help message and exit + --proxy PROXY 🔗 the URL of the proxy + --pem PEM 🔑 the PEM file, if keyfile not provided + --keyfile KEYFILE 🔑 a JSON keyfile, if PEM not provided + --passfile PASSFILE 🔑 a file containing keyfile's password, if keyfile provided. If not + provided, you'll be prompted to enter the password. + --ledger 🔐 bool flag for signing transaction using ledger + --sender-wallet-index SENDER_WALLET_INDEX 🔑 the address index; can be used for PEM files, keyfiles of type + mnemonic or Ledger devices (default: 0) + --sender-username SENDER_USERNAME 🖄 the username of the sender + --nonce NONCE # the nonce for the transaction + --recall-nonce ⭮ whether to recall the nonce when creating the transaction (default: + False) + --gas-price GAS_PRICE ⛽ the gas price (default: 1000000000) + --gas-limit GAS_LIMIT ⛽ the gas limit + --value VALUE the value to transfer (default: 0) + --chain CHAIN the chain identifier + --version VERSION the transaction version (default: 2) + --options OPTIONS the transaction options (default: 0) + --relayer RELAYER the bech32 address of the relayer + --guardian GUARDIAN the bech32 address of the guardian + --send ✓ whether to broadcast the transaction (default: False) + --simulate whether to simulate the transaction (default: False) + --outfile OUTFILE where to save the output (signed transaction, hash) (default: stdout) + --guardian-service-url GUARDIAN_SERVICE_URL the url of the guardian service + --guardian-2fa-code GUARDIAN_2FA_CODE the 2fa code for the guardian + --guardian-pem GUARDIAN_PEM 🔑 the PEM file, if keyfile not provided + --guardian-keyfile GUARDIAN_KEYFILE 🔑 a JSON keyfile, if PEM not provided + --guardian-passfile GUARDIAN_PASSFILE 🔑 a file containing keyfile's password, if keyfile provided. If not + provided, you'll be prompted to enter the password. + --guardian-ledger 🔐 bool flag for signing transaction using ledger + --guardian-wallet-index GUARDIAN_WALLET_INDEX 🔑 the address index; can be used for PEM files, keyfiles of type + mnemonic or Ledger devices (default: 0) + --relayer-pem RELAYER_PEM 🔑 the PEM file, if keyfile not provided + --relayer-keyfile RELAYER_KEYFILE 🔑 a JSON keyfile, if PEM not provided + --relayer-passfile RELAYER_PASSFILE 🔑 a file containing keyfile's password, if keyfile provided. If not + provided, you'll be prompted to enter the password. + --relayer-ledger 🔐 bool flag for signing transaction using ledger + --relayer-wallet-index RELAYER_WALLET_INDEX 🔑 the address index; can be used for PEM files, keyfiles of type + mnemonic or Ledger devices (default: 0) + --nodes-public-keys NODES_PUBLIC_KEYS the public keys of the nodes as CSV (addrA,addrB) ``` ### Validator.Unbond @@ -904,52 +871,47 @@ usage: mxpy validator unbond [-h] ... Unbond tokens for a bls key options: - -h, --help show this help message and exit - --proxy PROXY 🔗 the URL of the proxy - --pem PEM 🔑 the PEM file, if keyfile not provided - --pem-index PEM_INDEX 🔑 the index in the PEM file (default: 0) - --keyfile KEYFILE 🔑 a JSON keyfile, if PEM not provided - --passfile PASSFILE 🔑 a file containing keyfile's password, if keyfile provided - --ledger 🔐 bool flag for signing transaction using ledger - --ledger-account-index LEDGER_ACCOUNT_INDEX 🔐 the index of the account when using Ledger - --ledger-address-index LEDGER_ADDRESS_INDEX 🔐 the index of the address when using Ledger - --sender-username SENDER_USERNAME 🖄 the username of the sender - --nonce NONCE # the nonce for the transaction - --recall-nonce ⭮ whether to recall the nonce when creating the transaction (default: - False) - --gas-price GAS_PRICE ⛽ the gas price (default: 1000000000) - --gas-limit GAS_LIMIT ⛽ the gas limit - --estimate-gas ⛽ whether to estimate the gas limit (default: 0) - --value VALUE the value to transfer (default: 0) - --chain CHAIN the chain identifier - --version VERSION the transaction version (default: 2) - --relayer RELAYER the bech32 address of the relayer - --relayer-pem RELAYER_PEM 🔑 the PEM file, if keyfile not provided - --relayer-pem-index RELAYER_PEM_INDEX 🔑 the index in the PEM file (default: 0) - --relayer-keyfile RELAYER_KEYFILE 🔑 a JSON keyfile, if PEM not provided - --relayer-passfile RELAYER_PASSFILE 🔑 a file containing keyfile's password, if keyfile provided - --relayer-ledger 🔐 bool flag for signing transaction using ledger - --relayer-ledger-account-index RELAYER_LEDGER_ACCOUNT_INDEX - 🔐 the index of the account when using Ledger - --relayer-ledger-address-index RELAYER_LEDGER_ADDRESS_INDEX - 🔐 the index of the address when using Ledger - --guardian GUARDIAN the address of the guradian - --guardian-service-url GUARDIAN_SERVICE_URL the url of the guardian service - --guardian-2fa-code GUARDIAN_2FA_CODE the 2fa code for the guardian - --options OPTIONS the transaction options (default: 0) - --send ✓ whether to broadcast the transaction (default: False) - --simulate whether to simulate the transaction (default: False) - --outfile OUTFILE where to save the output (signed transaction, hash) (default: stdout) - --guardian-pem GUARDIAN_PEM 🔑 the PEM file, if keyfile not provided - --guardian-pem-index GUARDIAN_PEM_INDEX 🔑 the index in the PEM file (default: 0) - --guardian-keyfile GUARDIAN_KEYFILE 🔑 a JSON keyfile, if PEM not provided - --guardian-passfile GUARDIAN_PASSFILE 🔑 a file containing keyfile's password, if keyfile provided - --guardian-ledger 🔐 bool flag for signing transaction using ledger - --guardian-ledger-account-index GUARDIAN_LEDGER_ACCOUNT_INDEX - 🔐 the index of the account when using Ledger - --guardian-ledger-address-index GUARDIAN_LEDGER_ADDRESS_INDEX - 🔐 the index of the address when using Ledger - --nodes-public-keys NODES_PUBLIC_KEYS the public keys of the nodes as CSV (addrA,addrB) + -h, --help show this help message and exit + --proxy PROXY 🔗 the URL of the proxy + --pem PEM 🔑 the PEM file, if keyfile not provided + --keyfile KEYFILE 🔑 a JSON keyfile, if PEM not provided + --passfile PASSFILE 🔑 a file containing keyfile's password, if keyfile provided. If not + provided, you'll be prompted to enter the password. + --ledger 🔐 bool flag for signing transaction using ledger + --sender-wallet-index SENDER_WALLET_INDEX 🔑 the address index; can be used for PEM files, keyfiles of type + mnemonic or Ledger devices (default: 0) + --sender-username SENDER_USERNAME 🖄 the username of the sender + --nonce NONCE # the nonce for the transaction + --recall-nonce ⭮ whether to recall the nonce when creating the transaction (default: + False) + --gas-price GAS_PRICE ⛽ the gas price (default: 1000000000) + --gas-limit GAS_LIMIT ⛽ the gas limit + --value VALUE the value to transfer (default: 0) + --chain CHAIN the chain identifier + --version VERSION the transaction version (default: 2) + --options OPTIONS the transaction options (default: 0) + --relayer RELAYER the bech32 address of the relayer + --guardian GUARDIAN the bech32 address of the guardian + --send ✓ whether to broadcast the transaction (default: False) + --simulate whether to simulate the transaction (default: False) + --outfile OUTFILE where to save the output (signed transaction, hash) (default: stdout) + --guardian-service-url GUARDIAN_SERVICE_URL the url of the guardian service + --guardian-2fa-code GUARDIAN_2FA_CODE the 2fa code for the guardian + --guardian-pem GUARDIAN_PEM 🔑 the PEM file, if keyfile not provided + --guardian-keyfile GUARDIAN_KEYFILE 🔑 a JSON keyfile, if PEM not provided + --guardian-passfile GUARDIAN_PASSFILE 🔑 a file containing keyfile's password, if keyfile provided. If not + provided, you'll be prompted to enter the password. + --guardian-ledger 🔐 bool flag for signing transaction using ledger + --guardian-wallet-index GUARDIAN_WALLET_INDEX 🔑 the address index; can be used for PEM files, keyfiles of type + mnemonic or Ledger devices (default: 0) + --relayer-pem RELAYER_PEM 🔑 the PEM file, if keyfile not provided + --relayer-keyfile RELAYER_KEYFILE 🔑 a JSON keyfile, if PEM not provided + --relayer-passfile RELAYER_PASSFILE 🔑 a file containing keyfile's password, if keyfile provided. If not + provided, you'll be prompted to enter the password. + --relayer-ledger 🔐 bool flag for signing transaction using ledger + --relayer-wallet-index RELAYER_WALLET_INDEX 🔑 the address index; can be used for PEM files, keyfiles of type + mnemonic or Ledger devices (default: 0) + --nodes-public-keys NODES_PUBLIC_KEYS the public keys of the nodes as CSV (addrA,addrB) ``` ### Validator.ChangeRewardAddress @@ -962,52 +924,47 @@ usage: mxpy validator change-reward-address [-h] ... Change the reward address options: - -h, --help show this help message and exit - --proxy PROXY 🔗 the URL of the proxy - --pem PEM 🔑 the PEM file, if keyfile not provided - --pem-index PEM_INDEX 🔑 the index in the PEM file (default: 0) - --keyfile KEYFILE 🔑 a JSON keyfile, if PEM not provided - --passfile PASSFILE 🔑 a file containing keyfile's password, if keyfile provided - --ledger 🔐 bool flag for signing transaction using ledger - --ledger-account-index LEDGER_ACCOUNT_INDEX 🔐 the index of the account when using Ledger - --ledger-address-index LEDGER_ADDRESS_INDEX 🔐 the index of the address when using Ledger - --sender-username SENDER_USERNAME 🖄 the username of the sender - --nonce NONCE # the nonce for the transaction - --recall-nonce ⭮ whether to recall the nonce when creating the transaction (default: - False) - --gas-price GAS_PRICE ⛽ the gas price (default: 1000000000) - --gas-limit GAS_LIMIT ⛽ the gas limit - --estimate-gas ⛽ whether to estimate the gas limit (default: 0) - --value VALUE the value to transfer (default: 0) - --chain CHAIN the chain identifier - --version VERSION the transaction version (default: 2) - --relayer RELAYER the bech32 address of the relayer - --relayer-pem RELAYER_PEM 🔑 the PEM file, if keyfile not provided - --relayer-pem-index RELAYER_PEM_INDEX 🔑 the index in the PEM file (default: 0) - --relayer-keyfile RELAYER_KEYFILE 🔑 a JSON keyfile, if PEM not provided - --relayer-passfile RELAYER_PASSFILE 🔑 a file containing keyfile's password, if keyfile provided - --relayer-ledger 🔐 bool flag for signing transaction using ledger - --relayer-ledger-account-index RELAYER_LEDGER_ACCOUNT_INDEX - 🔐 the index of the account when using Ledger - --relayer-ledger-address-index RELAYER_LEDGER_ADDRESS_INDEX - 🔐 the index of the address when using Ledger - --guardian GUARDIAN the address of the guradian - --guardian-service-url GUARDIAN_SERVICE_URL the url of the guardian service - --guardian-2fa-code GUARDIAN_2FA_CODE the 2fa code for the guardian - --options OPTIONS the transaction options (default: 0) - --send ✓ whether to broadcast the transaction (default: False) - --simulate whether to simulate the transaction (default: False) - --outfile OUTFILE where to save the output (signed transaction, hash) (default: stdout) - --guardian-pem GUARDIAN_PEM 🔑 the PEM file, if keyfile not provided - --guardian-pem-index GUARDIAN_PEM_INDEX 🔑 the index in the PEM file (default: 0) - --guardian-keyfile GUARDIAN_KEYFILE 🔑 a JSON keyfile, if PEM not provided - --guardian-passfile GUARDIAN_PASSFILE 🔑 a file containing keyfile's password, if keyfile provided - --guardian-ledger 🔐 bool flag for signing transaction using ledger - --guardian-ledger-account-index GUARDIAN_LEDGER_ACCOUNT_INDEX - 🔐 the index of the account when using Ledger - --guardian-ledger-address-index GUARDIAN_LEDGER_ADDRESS_INDEX - 🔐 the index of the address when using Ledger - --reward-address REWARD_ADDRESS the new reward address + -h, --help show this help message and exit + --proxy PROXY 🔗 the URL of the proxy + --pem PEM 🔑 the PEM file, if keyfile not provided + --keyfile KEYFILE 🔑 a JSON keyfile, if PEM not provided + --passfile PASSFILE 🔑 a file containing keyfile's password, if keyfile provided. If not + provided, you'll be prompted to enter the password. + --ledger 🔐 bool flag for signing transaction using ledger + --sender-wallet-index SENDER_WALLET_INDEX 🔑 the address index; can be used for PEM files, keyfiles of type + mnemonic or Ledger devices (default: 0) + --sender-username SENDER_USERNAME 🖄 the username of the sender + --nonce NONCE # the nonce for the transaction + --recall-nonce ⭮ whether to recall the nonce when creating the transaction (default: + False) + --gas-price GAS_PRICE ⛽ the gas price (default: 1000000000) + --gas-limit GAS_LIMIT ⛽ the gas limit + --value VALUE the value to transfer (default: 0) + --chain CHAIN the chain identifier + --version VERSION the transaction version (default: 2) + --options OPTIONS the transaction options (default: 0) + --relayer RELAYER the bech32 address of the relayer + --guardian GUARDIAN the bech32 address of the guardian + --send ✓ whether to broadcast the transaction (default: False) + --simulate whether to simulate the transaction (default: False) + --outfile OUTFILE where to save the output (signed transaction, hash) (default: stdout) + --guardian-service-url GUARDIAN_SERVICE_URL the url of the guardian service + --guardian-2fa-code GUARDIAN_2FA_CODE the 2fa code for the guardian + --guardian-pem GUARDIAN_PEM 🔑 the PEM file, if keyfile not provided + --guardian-keyfile GUARDIAN_KEYFILE 🔑 a JSON keyfile, if PEM not provided + --guardian-passfile GUARDIAN_PASSFILE 🔑 a file containing keyfile's password, if keyfile provided. If not + provided, you'll be prompted to enter the password. + --guardian-ledger 🔐 bool flag for signing transaction using ledger + --guardian-wallet-index GUARDIAN_WALLET_INDEX 🔑 the address index; can be used for PEM files, keyfiles of type + mnemonic or Ledger devices (default: 0) + --relayer-pem RELAYER_PEM 🔑 the PEM file, if keyfile not provided + --relayer-keyfile RELAYER_KEYFILE 🔑 a JSON keyfile, if PEM not provided + --relayer-passfile RELAYER_PASSFILE 🔑 a file containing keyfile's password, if keyfile provided. If not + provided, you'll be prompted to enter the password. + --relayer-ledger 🔐 bool flag for signing transaction using ledger + --relayer-wallet-index RELAYER_WALLET_INDEX 🔑 the address index; can be used for PEM files, keyfiles of type + mnemonic or Ledger devices (default: 0) + --reward-address REWARD_ADDRESS the new reward address ``` ### Validator.Claim @@ -1020,51 +977,363 @@ usage: mxpy validator claim [-h] ... Claim rewards options: - -h, --help show this help message and exit - --proxy PROXY 🔗 the URL of the proxy - --pem PEM 🔑 the PEM file, if keyfile not provided - --pem-index PEM_INDEX 🔑 the index in the PEM file (default: 0) - --keyfile KEYFILE 🔑 a JSON keyfile, if PEM not provided - --passfile PASSFILE 🔑 a file containing keyfile's password, if keyfile provided - --ledger 🔐 bool flag for signing transaction using ledger - --ledger-account-index LEDGER_ACCOUNT_INDEX 🔐 the index of the account when using Ledger - --ledger-address-index LEDGER_ADDRESS_INDEX 🔐 the index of the address when using Ledger - --sender-username SENDER_USERNAME 🖄 the username of the sender - --nonce NONCE # the nonce for the transaction - --recall-nonce ⭮ whether to recall the nonce when creating the transaction (default: - False) - --gas-price GAS_PRICE ⛽ the gas price (default: 1000000000) - --gas-limit GAS_LIMIT ⛽ the gas limit - --estimate-gas ⛽ whether to estimate the gas limit (default: 0) - --value VALUE the value to transfer (default: 0) - --chain CHAIN the chain identifier - --version VERSION the transaction version (default: 2) - --relayer RELAYER the bech32 address of the relayer - --relayer-pem RELAYER_PEM 🔑 the PEM file, if keyfile not provided - --relayer-pem-index RELAYER_PEM_INDEX 🔑 the index in the PEM file (default: 0) - --relayer-keyfile RELAYER_KEYFILE 🔑 a JSON keyfile, if PEM not provided - --relayer-passfile RELAYER_PASSFILE 🔑 a file containing keyfile's password, if keyfile provided - --relayer-ledger 🔐 bool flag for signing transaction using ledger - --relayer-ledger-account-index RELAYER_LEDGER_ACCOUNT_INDEX - 🔐 the index of the account when using Ledger - --relayer-ledger-address-index RELAYER_LEDGER_ADDRESS_INDEX - 🔐 the index of the address when using Ledger - --guardian GUARDIAN the address of the guradian - --guardian-service-url GUARDIAN_SERVICE_URL the url of the guardian service - --guardian-2fa-code GUARDIAN_2FA_CODE the 2fa code for the guardian - --options OPTIONS the transaction options (default: 0) - --send ✓ whether to broadcast the transaction (default: False) - --simulate whether to simulate the transaction (default: False) - --outfile OUTFILE where to save the output (signed transaction, hash) (default: stdout) - --guardian-pem GUARDIAN_PEM 🔑 the PEM file, if keyfile not provided - --guardian-pem-index GUARDIAN_PEM_INDEX 🔑 the index in the PEM file (default: 0) - --guardian-keyfile GUARDIAN_KEYFILE 🔑 a JSON keyfile, if PEM not provided - --guardian-passfile GUARDIAN_PASSFILE 🔑 a file containing keyfile's password, if keyfile provided - --guardian-ledger 🔐 bool flag for signing transaction using ledger - --guardian-ledger-account-index GUARDIAN_LEDGER_ACCOUNT_INDEX - 🔐 the index of the account when using Ledger - --guardian-ledger-address-index GUARDIAN_LEDGER_ADDRESS_INDEX - 🔐 the index of the address when using Ledger + -h, --help show this help message and exit + --proxy PROXY 🔗 the URL of the proxy + --pem PEM 🔑 the PEM file, if keyfile not provided + --keyfile KEYFILE 🔑 a JSON keyfile, if PEM not provided + --passfile PASSFILE 🔑 a file containing keyfile's password, if keyfile provided. If not + provided, you'll be prompted to enter the password. + --ledger 🔐 bool flag for signing transaction using ledger + --sender-wallet-index SENDER_WALLET_INDEX 🔑 the address index; can be used for PEM files, keyfiles of type + mnemonic or Ledger devices (default: 0) + --sender-username SENDER_USERNAME 🖄 the username of the sender + --nonce NONCE # the nonce for the transaction + --recall-nonce ⭮ whether to recall the nonce when creating the transaction (default: + False) + --gas-price GAS_PRICE ⛽ the gas price (default: 1000000000) + --gas-limit GAS_LIMIT ⛽ the gas limit + --value VALUE the value to transfer (default: 0) + --chain CHAIN the chain identifier + --version VERSION the transaction version (default: 2) + --options OPTIONS the transaction options (default: 0) + --relayer RELAYER the bech32 address of the relayer + --guardian GUARDIAN the bech32 address of the guardian + --send ✓ whether to broadcast the transaction (default: False) + --simulate whether to simulate the transaction (default: False) + --outfile OUTFILE where to save the output (signed transaction, hash) (default: stdout) + --guardian-service-url GUARDIAN_SERVICE_URL the url of the guardian service + --guardian-2fa-code GUARDIAN_2FA_CODE the 2fa code for the guardian + --guardian-pem GUARDIAN_PEM 🔑 the PEM file, if keyfile not provided + --guardian-keyfile GUARDIAN_KEYFILE 🔑 a JSON keyfile, if PEM not provided + --guardian-passfile GUARDIAN_PASSFILE 🔑 a file containing keyfile's password, if keyfile provided. If not + provided, you'll be prompted to enter the password. + --guardian-ledger 🔐 bool flag for signing transaction using ledger + --guardian-wallet-index GUARDIAN_WALLET_INDEX 🔑 the address index; can be used for PEM files, keyfiles of type + mnemonic or Ledger devices (default: 0) + --relayer-pem RELAYER_PEM 🔑 the PEM file, if keyfile not provided + --relayer-keyfile RELAYER_KEYFILE 🔑 a JSON keyfile, if PEM not provided + --relayer-passfile RELAYER_PASSFILE 🔑 a file containing keyfile's password, if keyfile provided. If not + provided, you'll be prompted to enter the password. + --relayer-ledger 🔐 bool flag for signing transaction using ledger + --relayer-wallet-index RELAYER_WALLET_INDEX 🔑 the address index; can be used for PEM files, keyfiles of type + mnemonic or Ledger devices (default: 0) + +``` +### Validator.UnstakeNodes + + +``` +$ mxpy validator unstake-nodes --help +usage: mxpy validator unstake-nodes [-h] ... + +Unstake-nodes will unstake nodes for provided bls keys + +options: + -h, --help show this help message and exit + --proxy PROXY 🔗 the URL of the proxy + --pem PEM 🔑 the PEM file, if keyfile not provided + --keyfile KEYFILE 🔑 a JSON keyfile, if PEM not provided + --passfile PASSFILE 🔑 a file containing keyfile's password, if keyfile provided. If not + provided, you'll be prompted to enter the password. + --ledger 🔐 bool flag for signing transaction using ledger + --sender-wallet-index SENDER_WALLET_INDEX 🔑 the address index; can be used for PEM files, keyfiles of type + mnemonic or Ledger devices (default: 0) + --sender-username SENDER_USERNAME 🖄 the username of the sender + --nonce NONCE # the nonce for the transaction + --recall-nonce ⭮ whether to recall the nonce when creating the transaction (default: + False) + --gas-price GAS_PRICE ⛽ the gas price (default: 1000000000) + --gas-limit GAS_LIMIT ⛽ the gas limit + --value VALUE the value to transfer (default: 0) + --chain CHAIN the chain identifier + --version VERSION the transaction version (default: 2) + --options OPTIONS the transaction options (default: 0) + --relayer RELAYER the bech32 address of the relayer + --guardian GUARDIAN the bech32 address of the guardian + --send ✓ whether to broadcast the transaction (default: False) + --simulate whether to simulate the transaction (default: False) + --outfile OUTFILE where to save the output (signed transaction, hash) (default: stdout) + --guardian-service-url GUARDIAN_SERVICE_URL the url of the guardian service + --guardian-2fa-code GUARDIAN_2FA_CODE the 2fa code for the guardian + --guardian-pem GUARDIAN_PEM 🔑 the PEM file, if keyfile not provided + --guardian-keyfile GUARDIAN_KEYFILE 🔑 a JSON keyfile, if PEM not provided + --guardian-passfile GUARDIAN_PASSFILE 🔑 a file containing keyfile's password, if keyfile provided. If not + provided, you'll be prompted to enter the password. + --guardian-ledger 🔐 bool flag for signing transaction using ledger + --guardian-wallet-index GUARDIAN_WALLET_INDEX 🔑 the address index; can be used for PEM files, keyfiles of type + mnemonic or Ledger devices (default: 0) + --relayer-pem RELAYER_PEM 🔑 the PEM file, if keyfile not provided + --relayer-keyfile RELAYER_KEYFILE 🔑 a JSON keyfile, if PEM not provided + --relayer-passfile RELAYER_PASSFILE 🔑 a file containing keyfile's password, if keyfile provided. If not + provided, you'll be prompted to enter the password. + --relayer-ledger 🔐 bool flag for signing transaction using ledger + --relayer-wallet-index RELAYER_WALLET_INDEX 🔑 the address index; can be used for PEM files, keyfiles of type + mnemonic or Ledger devices (default: 0) + --nodes-public-keys NODES_PUBLIC_KEYS the public keys of the nodes as CSV (addrA,addrB) + +``` +### Validator.UnstakeTokens + + +``` +$ mxpy validator unstake-tokens --help +usage: mxpy validator unstake-tokens [-h] ... + +This command will un-stake the given amount (if value is greater than the existing topUp value, it will unStake one or several nodes) + +options: + -h, --help show this help message and exit + --proxy PROXY 🔗 the URL of the proxy + --pem PEM 🔑 the PEM file, if keyfile not provided + --keyfile KEYFILE 🔑 a JSON keyfile, if PEM not provided + --passfile PASSFILE 🔑 a file containing keyfile's password, if keyfile provided. If not + provided, you'll be prompted to enter the password. + --ledger 🔐 bool flag for signing transaction using ledger + --sender-wallet-index SENDER_WALLET_INDEX 🔑 the address index; can be used for PEM files, keyfiles of type + mnemonic or Ledger devices (default: 0) + --sender-username SENDER_USERNAME 🖄 the username of the sender + --nonce NONCE # the nonce for the transaction + --recall-nonce ⭮ whether to recall the nonce when creating the transaction (default: + False) + --gas-price GAS_PRICE ⛽ the gas price (default: 1000000000) + --gas-limit GAS_LIMIT ⛽ the gas limit + --value VALUE the value to transfer (default: 0) + --chain CHAIN the chain identifier + --version VERSION the transaction version (default: 2) + --options OPTIONS the transaction options (default: 0) + --relayer RELAYER the bech32 address of the relayer + --guardian GUARDIAN the bech32 address of the guardian + --send ✓ whether to broadcast the transaction (default: False) + --simulate whether to simulate the transaction (default: False) + --outfile OUTFILE where to save the output (signed transaction, hash) (default: stdout) + --guardian-service-url GUARDIAN_SERVICE_URL the url of the guardian service + --guardian-2fa-code GUARDIAN_2FA_CODE the 2fa code for the guardian + --guardian-pem GUARDIAN_PEM 🔑 the PEM file, if keyfile not provided + --guardian-keyfile GUARDIAN_KEYFILE 🔑 a JSON keyfile, if PEM not provided + --guardian-passfile GUARDIAN_PASSFILE 🔑 a file containing keyfile's password, if keyfile provided. If not + provided, you'll be prompted to enter the password. + --guardian-ledger 🔐 bool flag for signing transaction using ledger + --guardian-wallet-index GUARDIAN_WALLET_INDEX 🔑 the address index; can be used for PEM files, keyfiles of type + mnemonic or Ledger devices (default: 0) + --relayer-pem RELAYER_PEM 🔑 the PEM file, if keyfile not provided + --relayer-keyfile RELAYER_KEYFILE 🔑 a JSON keyfile, if PEM not provided + --relayer-passfile RELAYER_PASSFILE 🔑 a file containing keyfile's password, if keyfile provided. If not + provided, you'll be prompted to enter the password. + --relayer-ledger 🔐 bool flag for signing transaction using ledger + --relayer-wallet-index RELAYER_WALLET_INDEX 🔑 the address index; can be used for PEM files, keyfiles of type + mnemonic or Ledger devices (default: 0) + --unstake-value UNSTAKE_VALUE the unstake value + +``` +### Validator.UnbondNodes + + +``` +$ mxpy validator unbond-nodes --help +usage: mxpy validator unbond-nodes [-h] ... + +It will unBond nodes + +options: + -h, --help show this help message and exit + --proxy PROXY 🔗 the URL of the proxy + --pem PEM 🔑 the PEM file, if keyfile not provided + --keyfile KEYFILE 🔑 a JSON keyfile, if PEM not provided + --passfile PASSFILE 🔑 a file containing keyfile's password, if keyfile provided. If not + provided, you'll be prompted to enter the password. + --ledger 🔐 bool flag for signing transaction using ledger + --sender-wallet-index SENDER_WALLET_INDEX 🔑 the address index; can be used for PEM files, keyfiles of type + mnemonic or Ledger devices (default: 0) + --sender-username SENDER_USERNAME 🖄 the username of the sender + --nonce NONCE # the nonce for the transaction + --recall-nonce ⭮ whether to recall the nonce when creating the transaction (default: + False) + --gas-price GAS_PRICE ⛽ the gas price (default: 1000000000) + --gas-limit GAS_LIMIT ⛽ the gas limit + --value VALUE the value to transfer (default: 0) + --chain CHAIN the chain identifier + --version VERSION the transaction version (default: 2) + --options OPTIONS the transaction options (default: 0) + --relayer RELAYER the bech32 address of the relayer + --guardian GUARDIAN the bech32 address of the guardian + --send ✓ whether to broadcast the transaction (default: False) + --simulate whether to simulate the transaction (default: False) + --outfile OUTFILE where to save the output (signed transaction, hash) (default: stdout) + --guardian-service-url GUARDIAN_SERVICE_URL the url of the guardian service + --guardian-2fa-code GUARDIAN_2FA_CODE the 2fa code for the guardian + --guardian-pem GUARDIAN_PEM 🔑 the PEM file, if keyfile not provided + --guardian-keyfile GUARDIAN_KEYFILE 🔑 a JSON keyfile, if PEM not provided + --guardian-passfile GUARDIAN_PASSFILE 🔑 a file containing keyfile's password, if keyfile provided. If not + provided, you'll be prompted to enter the password. + --guardian-ledger 🔐 bool flag for signing transaction using ledger + --guardian-wallet-index GUARDIAN_WALLET_INDEX 🔑 the address index; can be used for PEM files, keyfiles of type + mnemonic or Ledger devices (default: 0) + --relayer-pem RELAYER_PEM 🔑 the PEM file, if keyfile not provided + --relayer-keyfile RELAYER_KEYFILE 🔑 a JSON keyfile, if PEM not provided + --relayer-passfile RELAYER_PASSFILE 🔑 a file containing keyfile's password, if keyfile provided. If not + provided, you'll be prompted to enter the password. + --relayer-ledger 🔐 bool flag for signing transaction using ledger + --relayer-wallet-index RELAYER_WALLET_INDEX 🔑 the address index; can be used for PEM files, keyfiles of type + mnemonic or Ledger devices (default: 0) + --nodes-public-keys NODES_PUBLIC_KEYS the public keys of the nodes as CSV (addrA,addrB) + +``` +### Validator.UnbondTokens + + +``` +$ mxpy validator unbond-tokens --help +usage: mxpy validator unbond-tokens [-h] ... + +It will unBond tokens, if provided value is bigger that topUp value will unBond nodes + +options: + -h, --help show this help message and exit + --proxy PROXY 🔗 the URL of the proxy + --pem PEM 🔑 the PEM file, if keyfile not provided + --keyfile KEYFILE 🔑 a JSON keyfile, if PEM not provided + --passfile PASSFILE 🔑 a file containing keyfile's password, if keyfile provided. If not + provided, you'll be prompted to enter the password. + --ledger 🔐 bool flag for signing transaction using ledger + --sender-wallet-index SENDER_WALLET_INDEX 🔑 the address index; can be used for PEM files, keyfiles of type + mnemonic or Ledger devices (default: 0) + --sender-username SENDER_USERNAME 🖄 the username of the sender + --nonce NONCE # the nonce for the transaction + --recall-nonce ⭮ whether to recall the nonce when creating the transaction (default: + False) + --gas-price GAS_PRICE ⛽ the gas price (default: 1000000000) + --gas-limit GAS_LIMIT ⛽ the gas limit + --value VALUE the value to transfer (default: 0) + --chain CHAIN the chain identifier + --version VERSION the transaction version (default: 2) + --options OPTIONS the transaction options (default: 0) + --relayer RELAYER the bech32 address of the relayer + --guardian GUARDIAN the bech32 address of the guardian + --send ✓ whether to broadcast the transaction (default: False) + --simulate whether to simulate the transaction (default: False) + --outfile OUTFILE where to save the output (signed transaction, hash) (default: stdout) + --guardian-service-url GUARDIAN_SERVICE_URL the url of the guardian service + --guardian-2fa-code GUARDIAN_2FA_CODE the 2fa code for the guardian + --guardian-pem GUARDIAN_PEM 🔑 the PEM file, if keyfile not provided + --guardian-keyfile GUARDIAN_KEYFILE 🔑 a JSON keyfile, if PEM not provided + --guardian-passfile GUARDIAN_PASSFILE 🔑 a file containing keyfile's password, if keyfile provided. If not + provided, you'll be prompted to enter the password. + --guardian-ledger 🔐 bool flag for signing transaction using ledger + --guardian-wallet-index GUARDIAN_WALLET_INDEX 🔑 the address index; can be used for PEM files, keyfiles of type + mnemonic or Ledger devices (default: 0) + --relayer-pem RELAYER_PEM 🔑 the PEM file, if keyfile not provided + --relayer-keyfile RELAYER_KEYFILE 🔑 a JSON keyfile, if PEM not provided + --relayer-passfile RELAYER_PASSFILE 🔑 a file containing keyfile's password, if keyfile provided. If not + provided, you'll be prompted to enter the password. + --relayer-ledger 🔐 bool flag for signing transaction using ledger + --relayer-wallet-index RELAYER_WALLET_INDEX 🔑 the address index; can be used for PEM files, keyfiles of type + mnemonic or Ledger devices (default: 0) + --unbond-value UNBOND_VALUE the unbond value + +``` +### Validator.CleanRegisteredData + + +``` +$ mxpy validator clean-registered-data --help +usage: mxpy validator clean-registered-data [-h] ... + +Deletes duplicated keys from registered data + +options: + -h, --help show this help message and exit + --proxy PROXY 🔗 the URL of the proxy + --pem PEM 🔑 the PEM file, if keyfile not provided + --keyfile KEYFILE 🔑 a JSON keyfile, if PEM not provided + --passfile PASSFILE 🔑 a file containing keyfile's password, if keyfile provided. If not + provided, you'll be prompted to enter the password. + --ledger 🔐 bool flag for signing transaction using ledger + --sender-wallet-index SENDER_WALLET_INDEX 🔑 the address index; can be used for PEM files, keyfiles of type + mnemonic or Ledger devices (default: 0) + --sender-username SENDER_USERNAME 🖄 the username of the sender + --nonce NONCE # the nonce for the transaction + --recall-nonce ⭮ whether to recall the nonce when creating the transaction (default: + False) + --gas-price GAS_PRICE ⛽ the gas price (default: 1000000000) + --gas-limit GAS_LIMIT ⛽ the gas limit + --value VALUE the value to transfer (default: 0) + --chain CHAIN the chain identifier + --version VERSION the transaction version (default: 2) + --options OPTIONS the transaction options (default: 0) + --relayer RELAYER the bech32 address of the relayer + --guardian GUARDIAN the bech32 address of the guardian + --send ✓ whether to broadcast the transaction (default: False) + --simulate whether to simulate the transaction (default: False) + --outfile OUTFILE where to save the output (signed transaction, hash) (default: stdout) + --guardian-service-url GUARDIAN_SERVICE_URL the url of the guardian service + --guardian-2fa-code GUARDIAN_2FA_CODE the 2fa code for the guardian + --guardian-pem GUARDIAN_PEM 🔑 the PEM file, if keyfile not provided + --guardian-keyfile GUARDIAN_KEYFILE 🔑 a JSON keyfile, if PEM not provided + --guardian-passfile GUARDIAN_PASSFILE 🔑 a file containing keyfile's password, if keyfile provided. If not + provided, you'll be prompted to enter the password. + --guardian-ledger 🔐 bool flag for signing transaction using ledger + --guardian-wallet-index GUARDIAN_WALLET_INDEX 🔑 the address index; can be used for PEM files, keyfiles of type + mnemonic or Ledger devices (default: 0) + --relayer-pem RELAYER_PEM 🔑 the PEM file, if keyfile not provided + --relayer-keyfile RELAYER_KEYFILE 🔑 a JSON keyfile, if PEM not provided + --relayer-passfile RELAYER_PASSFILE 🔑 a file containing keyfile's password, if keyfile provided. If not + provided, you'll be prompted to enter the password. + --relayer-ledger 🔐 bool flag for signing transaction using ledger + --relayer-wallet-index RELAYER_WALLET_INDEX 🔑 the address index; can be used for PEM files, keyfiles of type + mnemonic or Ledger devices (default: 0) + +``` +### Validator.RestakeUnstakedNodes + + +``` +$ mxpy validator restake-unstaked-nodes --help +usage: mxpy validator restake-unstaked-nodes [-h] ... + +It will reStake UnStaked nodes + +options: + -h, --help show this help message and exit + --proxy PROXY 🔗 the URL of the proxy + --pem PEM 🔑 the PEM file, if keyfile not provided + --keyfile KEYFILE 🔑 a JSON keyfile, if PEM not provided + --passfile PASSFILE 🔑 a file containing keyfile's password, if keyfile provided. If not + provided, you'll be prompted to enter the password. + --ledger 🔐 bool flag for signing transaction using ledger + --sender-wallet-index SENDER_WALLET_INDEX 🔑 the address index; can be used for PEM files, keyfiles of type + mnemonic or Ledger devices (default: 0) + --sender-username SENDER_USERNAME 🖄 the username of the sender + --nonce NONCE # the nonce for the transaction + --recall-nonce ⭮ whether to recall the nonce when creating the transaction (default: + False) + --gas-price GAS_PRICE ⛽ the gas price (default: 1000000000) + --gas-limit GAS_LIMIT ⛽ the gas limit + --value VALUE the value to transfer (default: 0) + --chain CHAIN the chain identifier + --version VERSION the transaction version (default: 2) + --options OPTIONS the transaction options (default: 0) + --relayer RELAYER the bech32 address of the relayer + --guardian GUARDIAN the bech32 address of the guardian + --send ✓ whether to broadcast the transaction (default: False) + --simulate whether to simulate the transaction (default: False) + --outfile OUTFILE where to save the output (signed transaction, hash) (default: stdout) + --guardian-service-url GUARDIAN_SERVICE_URL the url of the guardian service + --guardian-2fa-code GUARDIAN_2FA_CODE the 2fa code for the guardian + --guardian-pem GUARDIAN_PEM 🔑 the PEM file, if keyfile not provided + --guardian-keyfile GUARDIAN_KEYFILE 🔑 a JSON keyfile, if PEM not provided + --guardian-passfile GUARDIAN_PASSFILE 🔑 a file containing keyfile's password, if keyfile provided. If not + provided, you'll be prompted to enter the password. + --guardian-ledger 🔐 bool flag for signing transaction using ledger + --guardian-wallet-index GUARDIAN_WALLET_INDEX 🔑 the address index; can be used for PEM files, keyfiles of type + mnemonic or Ledger devices (default: 0) + --relayer-pem RELAYER_PEM 🔑 the PEM file, if keyfile not provided + --relayer-keyfile RELAYER_KEYFILE 🔑 a JSON keyfile, if PEM not provided + --relayer-passfile RELAYER_PASSFILE 🔑 a file containing keyfile's password, if keyfile provided. If not + provided, you'll be prompted to enter the password. + --relayer-ledger 🔐 bool flag for signing transaction using ledger + --relayer-wallet-index RELAYER_WALLET_INDEX 🔑 the address index; can be used for PEM files, keyfiles of type + mnemonic or Ledger devices (default: 0) + --nodes-public-keys NODES_PUBLIC_KEYS the public keys of the nodes as CSV (addrA,addrB) ``` ## Group **StakingProvider** @@ -1116,53 +1385,48 @@ usage: mxpy staking-provider create-new-delegation-contract [-h] ... Create a new delegation system smart contract, transferred value must be greater than baseIssuingCost + min deposit value options: - -h, --help show this help message and exit - --proxy PROXY 🔗 the URL of the proxy - --pem PEM 🔑 the PEM file, if keyfile not provided - --pem-index PEM_INDEX 🔑 the index in the PEM file (default: 0) - --keyfile KEYFILE 🔑 a JSON keyfile, if PEM not provided - --passfile PASSFILE 🔑 a file containing keyfile's password, if keyfile provided - --ledger 🔐 bool flag for signing transaction using ledger - --ledger-account-index LEDGER_ACCOUNT_INDEX 🔐 the index of the account when using Ledger - --ledger-address-index LEDGER_ADDRESS_INDEX 🔐 the index of the address when using Ledger - --sender-username SENDER_USERNAME 🖄 the username of the sender - --nonce NONCE # the nonce for the transaction - --recall-nonce ⭮ whether to recall the nonce when creating the transaction (default: - False) - --gas-price GAS_PRICE ⛽ the gas price (default: 1000000000) - --gas-limit GAS_LIMIT ⛽ the gas limit - --estimate-gas ⛽ whether to estimate the gas limit (default: 0) - --value VALUE the value to transfer (default: 0) - --chain CHAIN the chain identifier - --version VERSION the transaction version (default: 2) - --relayer RELAYER the bech32 address of the relayer - --relayer-pem RELAYER_PEM 🔑 the PEM file, if keyfile not provided - --relayer-pem-index RELAYER_PEM_INDEX 🔑 the index in the PEM file (default: 0) - --relayer-keyfile RELAYER_KEYFILE 🔑 a JSON keyfile, if PEM not provided - --relayer-passfile RELAYER_PASSFILE 🔑 a file containing keyfile's password, if keyfile provided - --relayer-ledger 🔐 bool flag for signing transaction using ledger - --relayer-ledger-account-index RELAYER_LEDGER_ACCOUNT_INDEX - 🔐 the index of the account when using Ledger - --relayer-ledger-address-index RELAYER_LEDGER_ADDRESS_INDEX - 🔐 the index of the address when using Ledger - --guardian GUARDIAN the address of the guradian - --guardian-service-url GUARDIAN_SERVICE_URL the url of the guardian service - --guardian-2fa-code GUARDIAN_2FA_CODE the 2fa code for the guardian - --options OPTIONS the transaction options (default: 0) - --send ✓ whether to broadcast the transaction (default: False) - --simulate whether to simulate the transaction (default: False) - --outfile OUTFILE where to save the output (signed transaction, hash) (default: stdout) - --guardian-pem GUARDIAN_PEM 🔑 the PEM file, if keyfile not provided - --guardian-pem-index GUARDIAN_PEM_INDEX 🔑 the index in the PEM file (default: 0) - --guardian-keyfile GUARDIAN_KEYFILE 🔑 a JSON keyfile, if PEM not provided - --guardian-passfile GUARDIAN_PASSFILE 🔑 a file containing keyfile's password, if keyfile provided - --guardian-ledger 🔐 bool flag for signing transaction using ledger - --guardian-ledger-account-index GUARDIAN_LEDGER_ACCOUNT_INDEX - 🔐 the index of the account when using Ledger - --guardian-ledger-address-index GUARDIAN_LEDGER_ADDRESS_INDEX - 🔐 the index of the address when using Ledger - --total-delegation-cap TOTAL_DELEGATION_CAP the total delegation contract capacity - --service-fee SERVICE_FEE the delegation contract service fee + -h, --help show this help message and exit + --proxy PROXY 🔗 the URL of the proxy + --pem PEM 🔑 the PEM file, if keyfile not provided + --keyfile KEYFILE 🔑 a JSON keyfile, if PEM not provided + --passfile PASSFILE 🔑 a file containing keyfile's password, if keyfile provided. If not + provided, you'll be prompted to enter the password. + --ledger 🔐 bool flag for signing transaction using ledger + --sender-wallet-index SENDER_WALLET_INDEX 🔑 the address index; can be used for PEM files, keyfiles of type + mnemonic or Ledger devices (default: 0) + --sender-username SENDER_USERNAME 🖄 the username of the sender + --nonce NONCE # the nonce for the transaction + --recall-nonce ⭮ whether to recall the nonce when creating the transaction (default: + False) + --gas-price GAS_PRICE ⛽ the gas price (default: 1000000000) + --gas-limit GAS_LIMIT ⛽ the gas limit + --value VALUE the value to transfer (default: 0) + --chain CHAIN the chain identifier + --version VERSION the transaction version (default: 2) + --options OPTIONS the transaction options (default: 0) + --relayer RELAYER the bech32 address of the relayer + --guardian GUARDIAN the bech32 address of the guardian + --send ✓ whether to broadcast the transaction (default: False) + --simulate whether to simulate the transaction (default: False) + --outfile OUTFILE where to save the output (signed transaction, hash) (default: stdout) + --guardian-service-url GUARDIAN_SERVICE_URL the url of the guardian service + --guardian-2fa-code GUARDIAN_2FA_CODE the 2fa code for the guardian + --guardian-pem GUARDIAN_PEM 🔑 the PEM file, if keyfile not provided + --guardian-keyfile GUARDIAN_KEYFILE 🔑 a JSON keyfile, if PEM not provided + --guardian-passfile GUARDIAN_PASSFILE 🔑 a file containing keyfile's password, if keyfile provided. If not + provided, you'll be prompted to enter the password. + --guardian-ledger 🔐 bool flag for signing transaction using ledger + --guardian-wallet-index GUARDIAN_WALLET_INDEX 🔑 the address index; can be used for PEM files, keyfiles of type + mnemonic or Ledger devices (default: 0) + --relayer-pem RELAYER_PEM 🔑 the PEM file, if keyfile not provided + --relayer-keyfile RELAYER_KEYFILE 🔑 a JSON keyfile, if PEM not provided + --relayer-passfile RELAYER_PASSFILE 🔑 a file containing keyfile's password, if keyfile provided. If not + provided, you'll be prompted to enter the password. + --relayer-ledger 🔐 bool flag for signing transaction using ledger + --relayer-wallet-index RELAYER_WALLET_INDEX 🔑 the address index; can be used for PEM files, keyfiles of type + mnemonic or Ledger devices (default: 0) + --total-delegation-cap TOTAL_DELEGATION_CAP the total delegation contract capacity + --service-fee SERVICE_FEE the delegation contract service fee ``` ### StakingProvider.GetContractAddress @@ -1177,7 +1441,6 @@ Get create contract address by transaction hash options: -h, --help show this help message and exit --create-tx-hash CREATE_TX_HASH the hash - --sender SENDER the sender address --proxy PROXY 🔗 the URL of the proxy ``` @@ -1191,53 +1454,48 @@ usage: mxpy staking-provider add-nodes [-h] ... Add new nodes must be called by the contract owner options: - -h, --help show this help message and exit - --validators-file VALIDATORS_FILE a JSON file describing the Nodes - --delegation-contract DELEGATION_CONTRACT address of the delegation contract - --proxy PROXY 🔗 the URL of the proxy - --pem PEM 🔑 the PEM file, if keyfile not provided - --pem-index PEM_INDEX 🔑 the index in the PEM file (default: 0) - --keyfile KEYFILE 🔑 a JSON keyfile, if PEM not provided - --passfile PASSFILE 🔑 a file containing keyfile's password, if keyfile provided - --ledger 🔐 bool flag for signing transaction using ledger - --ledger-account-index LEDGER_ACCOUNT_INDEX 🔐 the index of the account when using Ledger - --ledger-address-index LEDGER_ADDRESS_INDEX 🔐 the index of the address when using Ledger - --sender-username SENDER_USERNAME 🖄 the username of the sender - --nonce NONCE # the nonce for the transaction - --recall-nonce ⭮ whether to recall the nonce when creating the transaction (default: - False) - --gas-price GAS_PRICE ⛽ the gas price (default: 1000000000) - --gas-limit GAS_LIMIT ⛽ the gas limit - --estimate-gas ⛽ whether to estimate the gas limit (default: 0) - --value VALUE the value to transfer (default: 0) - --chain CHAIN the chain identifier - --version VERSION the transaction version (default: 2) - --relayer RELAYER the bech32 address of the relayer - --relayer-pem RELAYER_PEM 🔑 the PEM file, if keyfile not provided - --relayer-pem-index RELAYER_PEM_INDEX 🔑 the index in the PEM file (default: 0) - --relayer-keyfile RELAYER_KEYFILE 🔑 a JSON keyfile, if PEM not provided - --relayer-passfile RELAYER_PASSFILE 🔑 a file containing keyfile's password, if keyfile provided - --relayer-ledger 🔐 bool flag for signing transaction using ledger - --relayer-ledger-account-index RELAYER_LEDGER_ACCOUNT_INDEX - 🔐 the index of the account when using Ledger - --relayer-ledger-address-index RELAYER_LEDGER_ADDRESS_INDEX - 🔐 the index of the address when using Ledger - --guardian GUARDIAN the address of the guradian - --guardian-service-url GUARDIAN_SERVICE_URL the url of the guardian service - --guardian-2fa-code GUARDIAN_2FA_CODE the 2fa code for the guardian - --options OPTIONS the transaction options (default: 0) - --send ✓ whether to broadcast the transaction (default: False) - --simulate whether to simulate the transaction (default: False) - --outfile OUTFILE where to save the output (signed transaction, hash) (default: stdout) - --guardian-pem GUARDIAN_PEM 🔑 the PEM file, if keyfile not provided - --guardian-pem-index GUARDIAN_PEM_INDEX 🔑 the index in the PEM file (default: 0) - --guardian-keyfile GUARDIAN_KEYFILE 🔑 a JSON keyfile, if PEM not provided - --guardian-passfile GUARDIAN_PASSFILE 🔑 a file containing keyfile's password, if keyfile provided - --guardian-ledger 🔐 bool flag for signing transaction using ledger - --guardian-ledger-account-index GUARDIAN_LEDGER_ACCOUNT_INDEX - 🔐 the index of the account when using Ledger - --guardian-ledger-address-index GUARDIAN_LEDGER_ADDRESS_INDEX - 🔐 the index of the address when using Ledger + -h, --help show this help message and exit + --validators-pem VALIDATORS_PEM a PEM file holding the BLS keys; can contain multiple nodes + --delegation-contract DELEGATION_CONTRACT bech32 address of the delegation contract + --proxy PROXY 🔗 the URL of the proxy + --pem PEM 🔑 the PEM file, if keyfile not provided + --keyfile KEYFILE 🔑 a JSON keyfile, if PEM not provided + --passfile PASSFILE 🔑 a file containing keyfile's password, if keyfile provided. If not + provided, you'll be prompted to enter the password. + --ledger 🔐 bool flag for signing transaction using ledger + --sender-wallet-index SENDER_WALLET_INDEX 🔑 the address index; can be used for PEM files, keyfiles of type + mnemonic or Ledger devices (default: 0) + --sender-username SENDER_USERNAME 🖄 the username of the sender + --nonce NONCE # the nonce for the transaction + --recall-nonce ⭮ whether to recall the nonce when creating the transaction (default: + False) + --gas-price GAS_PRICE ⛽ the gas price (default: 1000000000) + --gas-limit GAS_LIMIT ⛽ the gas limit + --value VALUE the value to transfer (default: 0) + --chain CHAIN the chain identifier + --version VERSION the transaction version (default: 2) + --options OPTIONS the transaction options (default: 0) + --relayer RELAYER the bech32 address of the relayer + --guardian GUARDIAN the bech32 address of the guardian + --send ✓ whether to broadcast the transaction (default: False) + --simulate whether to simulate the transaction (default: False) + --outfile OUTFILE where to save the output (signed transaction, hash) (default: stdout) + --guardian-service-url GUARDIAN_SERVICE_URL the url of the guardian service + --guardian-2fa-code GUARDIAN_2FA_CODE the 2fa code for the guardian + --guardian-pem GUARDIAN_PEM 🔑 the PEM file, if keyfile not provided + --guardian-keyfile GUARDIAN_KEYFILE 🔑 a JSON keyfile, if PEM not provided + --guardian-passfile GUARDIAN_PASSFILE 🔑 a file containing keyfile's password, if keyfile provided. If not + provided, you'll be prompted to enter the password. + --guardian-ledger 🔐 bool flag for signing transaction using ledger + --guardian-wallet-index GUARDIAN_WALLET_INDEX 🔑 the address index; can be used for PEM files, keyfiles of type + mnemonic or Ledger devices (default: 0) + --relayer-pem RELAYER_PEM 🔑 the PEM file, if keyfile not provided + --relayer-keyfile RELAYER_KEYFILE 🔑 a JSON keyfile, if PEM not provided + --relayer-passfile RELAYER_PASSFILE 🔑 a file containing keyfile's password, if keyfile provided. If not + provided, you'll be prompted to enter the password. + --relayer-ledger 🔐 bool flag for signing transaction using ledger + --relayer-wallet-index RELAYER_WALLET_INDEX 🔑 the address index; can be used for PEM files, keyfiles of type + mnemonic or Ledger devices (default: 0) ``` ### StakingProvider.RemoveNodes @@ -1250,54 +1508,49 @@ usage: mxpy staking-provider remove-nodes [-h] ... Remove nodes must be called by the contract owner options: - -h, --help show this help message and exit - --bls-keys BLS_KEYS a list with the bls keys of the nodes - --validators-file VALIDATORS_FILE a JSON file describing the Nodes - --delegation-contract DELEGATION_CONTRACT address of the delegation contract - --proxy PROXY 🔗 the URL of the proxy - --pem PEM 🔑 the PEM file, if keyfile not provided - --pem-index PEM_INDEX 🔑 the index in the PEM file (default: 0) - --keyfile KEYFILE 🔑 a JSON keyfile, if PEM not provided - --passfile PASSFILE 🔑 a file containing keyfile's password, if keyfile provided - --ledger 🔐 bool flag for signing transaction using ledger - --ledger-account-index LEDGER_ACCOUNT_INDEX 🔐 the index of the account when using Ledger - --ledger-address-index LEDGER_ADDRESS_INDEX 🔐 the index of the address when using Ledger - --sender-username SENDER_USERNAME 🖄 the username of the sender - --nonce NONCE # the nonce for the transaction - --recall-nonce ⭮ whether to recall the nonce when creating the transaction (default: - False) - --gas-price GAS_PRICE ⛽ the gas price (default: 1000000000) - --gas-limit GAS_LIMIT ⛽ the gas limit - --estimate-gas ⛽ whether to estimate the gas limit (default: 0) - --value VALUE the value to transfer (default: 0) - --chain CHAIN the chain identifier - --version VERSION the transaction version (default: 2) - --relayer RELAYER the bech32 address of the relayer - --relayer-pem RELAYER_PEM 🔑 the PEM file, if keyfile not provided - --relayer-pem-index RELAYER_PEM_INDEX 🔑 the index in the PEM file (default: 0) - --relayer-keyfile RELAYER_KEYFILE 🔑 a JSON keyfile, if PEM not provided - --relayer-passfile RELAYER_PASSFILE 🔑 a file containing keyfile's password, if keyfile provided - --relayer-ledger 🔐 bool flag for signing transaction using ledger - --relayer-ledger-account-index RELAYER_LEDGER_ACCOUNT_INDEX - 🔐 the index of the account when using Ledger - --relayer-ledger-address-index RELAYER_LEDGER_ADDRESS_INDEX - 🔐 the index of the address when using Ledger - --guardian GUARDIAN the address of the guradian - --guardian-service-url GUARDIAN_SERVICE_URL the url of the guardian service - --guardian-2fa-code GUARDIAN_2FA_CODE the 2fa code for the guardian - --options OPTIONS the transaction options (default: 0) - --send ✓ whether to broadcast the transaction (default: False) - --simulate whether to simulate the transaction (default: False) - --outfile OUTFILE where to save the output (signed transaction, hash) (default: stdout) - --guardian-pem GUARDIAN_PEM 🔑 the PEM file, if keyfile not provided - --guardian-pem-index GUARDIAN_PEM_INDEX 🔑 the index in the PEM file (default: 0) - --guardian-keyfile GUARDIAN_KEYFILE 🔑 a JSON keyfile, if PEM not provided - --guardian-passfile GUARDIAN_PASSFILE 🔑 a file containing keyfile's password, if keyfile provided - --guardian-ledger 🔐 bool flag for signing transaction using ledger - --guardian-ledger-account-index GUARDIAN_LEDGER_ACCOUNT_INDEX - 🔐 the index of the account when using Ledger - --guardian-ledger-address-index GUARDIAN_LEDGER_ADDRESS_INDEX - 🔐 the index of the address when using Ledger + -h, --help show this help message and exit + --bls-keys BLS_KEYS a list with the bls keys of the nodes as CSV (addrA,addrB) + --validators-pem VALIDATORS_PEM a PEM file holding the BLS keys; can contain multiple nodes + --delegation-contract DELEGATION_CONTRACT address of the delegation contract + --proxy PROXY 🔗 the URL of the proxy + --pem PEM 🔑 the PEM file, if keyfile not provided + --keyfile KEYFILE 🔑 a JSON keyfile, if PEM not provided + --passfile PASSFILE 🔑 a file containing keyfile's password, if keyfile provided. If not + provided, you'll be prompted to enter the password. + --ledger 🔐 bool flag for signing transaction using ledger + --sender-wallet-index SENDER_WALLET_INDEX 🔑 the address index; can be used for PEM files, keyfiles of type + mnemonic or Ledger devices (default: 0) + --sender-username SENDER_USERNAME 🖄 the username of the sender + --nonce NONCE # the nonce for the transaction + --recall-nonce ⭮ whether to recall the nonce when creating the transaction (default: + False) + --gas-price GAS_PRICE ⛽ the gas price (default: 1000000000) + --gas-limit GAS_LIMIT ⛽ the gas limit + --value VALUE the value to transfer (default: 0) + --chain CHAIN the chain identifier + --version VERSION the transaction version (default: 2) + --options OPTIONS the transaction options (default: 0) + --relayer RELAYER the bech32 address of the relayer + --guardian GUARDIAN the bech32 address of the guardian + --send ✓ whether to broadcast the transaction (default: False) + --simulate whether to simulate the transaction (default: False) + --outfile OUTFILE where to save the output (signed transaction, hash) (default: stdout) + --guardian-service-url GUARDIAN_SERVICE_URL the url of the guardian service + --guardian-2fa-code GUARDIAN_2FA_CODE the 2fa code for the guardian + --guardian-pem GUARDIAN_PEM 🔑 the PEM file, if keyfile not provided + --guardian-keyfile GUARDIAN_KEYFILE 🔑 a JSON keyfile, if PEM not provided + --guardian-passfile GUARDIAN_PASSFILE 🔑 a file containing keyfile's password, if keyfile provided. If not + provided, you'll be prompted to enter the password. + --guardian-ledger 🔐 bool flag for signing transaction using ledger + --guardian-wallet-index GUARDIAN_WALLET_INDEX 🔑 the address index; can be used for PEM files, keyfiles of type + mnemonic or Ledger devices (default: 0) + --relayer-pem RELAYER_PEM 🔑 the PEM file, if keyfile not provided + --relayer-keyfile RELAYER_KEYFILE 🔑 a JSON keyfile, if PEM not provided + --relayer-passfile RELAYER_PASSFILE 🔑 a file containing keyfile's password, if keyfile provided. If not + provided, you'll be prompted to enter the password. + --relayer-ledger 🔐 bool flag for signing transaction using ledger + --relayer-wallet-index RELAYER_WALLET_INDEX 🔑 the address index; can be used for PEM files, keyfiles of type + mnemonic or Ledger devices (default: 0) ``` ### StakingProvider.StakeNodes @@ -1310,54 +1563,49 @@ usage: mxpy staking-provider stake-nodes [-h] ... Stake nodes must be called by the contract owner options: - -h, --help show this help message and exit - --bls-keys BLS_KEYS a list with the bls keys of the nodes - --validators-file VALIDATORS_FILE a JSON file describing the Nodes - --delegation-contract DELEGATION_CONTRACT address of the delegation contract - --proxy PROXY 🔗 the URL of the proxy - --pem PEM 🔑 the PEM file, if keyfile not provided - --pem-index PEM_INDEX 🔑 the index in the PEM file (default: 0) - --keyfile KEYFILE 🔑 a JSON keyfile, if PEM not provided - --passfile PASSFILE 🔑 a file containing keyfile's password, if keyfile provided - --ledger 🔐 bool flag for signing transaction using ledger - --ledger-account-index LEDGER_ACCOUNT_INDEX 🔐 the index of the account when using Ledger - --ledger-address-index LEDGER_ADDRESS_INDEX 🔐 the index of the address when using Ledger - --sender-username SENDER_USERNAME 🖄 the username of the sender - --nonce NONCE # the nonce for the transaction - --recall-nonce ⭮ whether to recall the nonce when creating the transaction (default: - False) - --gas-price GAS_PRICE ⛽ the gas price (default: 1000000000) - --gas-limit GAS_LIMIT ⛽ the gas limit - --estimate-gas ⛽ whether to estimate the gas limit (default: 0) - --value VALUE the value to transfer (default: 0) - --chain CHAIN the chain identifier - --version VERSION the transaction version (default: 2) - --relayer RELAYER the bech32 address of the relayer - --relayer-pem RELAYER_PEM 🔑 the PEM file, if keyfile not provided - --relayer-pem-index RELAYER_PEM_INDEX 🔑 the index in the PEM file (default: 0) - --relayer-keyfile RELAYER_KEYFILE 🔑 a JSON keyfile, if PEM not provided - --relayer-passfile RELAYER_PASSFILE 🔑 a file containing keyfile's password, if keyfile provided - --relayer-ledger 🔐 bool flag for signing transaction using ledger - --relayer-ledger-account-index RELAYER_LEDGER_ACCOUNT_INDEX - 🔐 the index of the account when using Ledger - --relayer-ledger-address-index RELAYER_LEDGER_ADDRESS_INDEX - 🔐 the index of the address when using Ledger - --guardian GUARDIAN the address of the guradian - --guardian-service-url GUARDIAN_SERVICE_URL the url of the guardian service - --guardian-2fa-code GUARDIAN_2FA_CODE the 2fa code for the guardian - --options OPTIONS the transaction options (default: 0) - --send ✓ whether to broadcast the transaction (default: False) - --simulate whether to simulate the transaction (default: False) - --outfile OUTFILE where to save the output (signed transaction, hash) (default: stdout) - --guardian-pem GUARDIAN_PEM 🔑 the PEM file, if keyfile not provided - --guardian-pem-index GUARDIAN_PEM_INDEX 🔑 the index in the PEM file (default: 0) - --guardian-keyfile GUARDIAN_KEYFILE 🔑 a JSON keyfile, if PEM not provided - --guardian-passfile GUARDIAN_PASSFILE 🔑 a file containing keyfile's password, if keyfile provided - --guardian-ledger 🔐 bool flag for signing transaction using ledger - --guardian-ledger-account-index GUARDIAN_LEDGER_ACCOUNT_INDEX - 🔐 the index of the account when using Ledger - --guardian-ledger-address-index GUARDIAN_LEDGER_ADDRESS_INDEX - 🔐 the index of the address when using Ledger + -h, --help show this help message and exit + --bls-keys BLS_KEYS a list with the bls keys of the nodes as CSV (addrA,addrB) + --validators-pem VALIDATORS_PEM a PEM file holding the BLS keys; can contain multiple nodes + --delegation-contract DELEGATION_CONTRACT bech32 address of the delegation contract + --proxy PROXY 🔗 the URL of the proxy + --pem PEM 🔑 the PEM file, if keyfile not provided + --keyfile KEYFILE 🔑 a JSON keyfile, if PEM not provided + --passfile PASSFILE 🔑 a file containing keyfile's password, if keyfile provided. If not + provided, you'll be prompted to enter the password. + --ledger 🔐 bool flag for signing transaction using ledger + --sender-wallet-index SENDER_WALLET_INDEX 🔑 the address index; can be used for PEM files, keyfiles of type + mnemonic or Ledger devices (default: 0) + --sender-username SENDER_USERNAME 🖄 the username of the sender + --nonce NONCE # the nonce for the transaction + --recall-nonce ⭮ whether to recall the nonce when creating the transaction (default: + False) + --gas-price GAS_PRICE ⛽ the gas price (default: 1000000000) + --gas-limit GAS_LIMIT ⛽ the gas limit + --value VALUE the value to transfer (default: 0) + --chain CHAIN the chain identifier + --version VERSION the transaction version (default: 2) + --options OPTIONS the transaction options (default: 0) + --relayer RELAYER the bech32 address of the relayer + --guardian GUARDIAN the bech32 address of the guardian + --send ✓ whether to broadcast the transaction (default: False) + --simulate whether to simulate the transaction (default: False) + --outfile OUTFILE where to save the output (signed transaction, hash) (default: stdout) + --guardian-service-url GUARDIAN_SERVICE_URL the url of the guardian service + --guardian-2fa-code GUARDIAN_2FA_CODE the 2fa code for the guardian + --guardian-pem GUARDIAN_PEM 🔑 the PEM file, if keyfile not provided + --guardian-keyfile GUARDIAN_KEYFILE 🔑 a JSON keyfile, if PEM not provided + --guardian-passfile GUARDIAN_PASSFILE 🔑 a file containing keyfile's password, if keyfile provided. If not + provided, you'll be prompted to enter the password. + --guardian-ledger 🔐 bool flag for signing transaction using ledger + --guardian-wallet-index GUARDIAN_WALLET_INDEX 🔑 the address index; can be used for PEM files, keyfiles of type + mnemonic or Ledger devices (default: 0) + --relayer-pem RELAYER_PEM 🔑 the PEM file, if keyfile not provided + --relayer-keyfile RELAYER_KEYFILE 🔑 a JSON keyfile, if PEM not provided + --relayer-passfile RELAYER_PASSFILE 🔑 a file containing keyfile's password, if keyfile provided. If not + provided, you'll be prompted to enter the password. + --relayer-ledger 🔐 bool flag for signing transaction using ledger + --relayer-wallet-index RELAYER_WALLET_INDEX 🔑 the address index; can be used for PEM files, keyfiles of type + mnemonic or Ledger devices (default: 0) ``` ### StakingProvider.UnbondNodes @@ -1370,54 +1618,49 @@ usage: mxpy staking-provider unbond-nodes [-h] ... Unbond nodes must be called by the contract owner options: - -h, --help show this help message and exit - --bls-keys BLS_KEYS a list with the bls keys of the nodes - --validators-file VALIDATORS_FILE a JSON file describing the Nodes - --delegation-contract DELEGATION_CONTRACT address of the delegation contract - --proxy PROXY 🔗 the URL of the proxy - --pem PEM 🔑 the PEM file, if keyfile not provided - --pem-index PEM_INDEX 🔑 the index in the PEM file (default: 0) - --keyfile KEYFILE 🔑 a JSON keyfile, if PEM not provided - --passfile PASSFILE 🔑 a file containing keyfile's password, if keyfile provided - --ledger 🔐 bool flag for signing transaction using ledger - --ledger-account-index LEDGER_ACCOUNT_INDEX 🔐 the index of the account when using Ledger - --ledger-address-index LEDGER_ADDRESS_INDEX 🔐 the index of the address when using Ledger - --sender-username SENDER_USERNAME 🖄 the username of the sender - --nonce NONCE # the nonce for the transaction - --recall-nonce ⭮ whether to recall the nonce when creating the transaction (default: - False) - --gas-price GAS_PRICE ⛽ the gas price (default: 1000000000) - --gas-limit GAS_LIMIT ⛽ the gas limit - --estimate-gas ⛽ whether to estimate the gas limit (default: 0) - --value VALUE the value to transfer (default: 0) - --chain CHAIN the chain identifier - --version VERSION the transaction version (default: 2) - --relayer RELAYER the bech32 address of the relayer - --relayer-pem RELAYER_PEM 🔑 the PEM file, if keyfile not provided - --relayer-pem-index RELAYER_PEM_INDEX 🔑 the index in the PEM file (default: 0) - --relayer-keyfile RELAYER_KEYFILE 🔑 a JSON keyfile, if PEM not provided - --relayer-passfile RELAYER_PASSFILE 🔑 a file containing keyfile's password, if keyfile provided - --relayer-ledger 🔐 bool flag for signing transaction using ledger - --relayer-ledger-account-index RELAYER_LEDGER_ACCOUNT_INDEX - 🔐 the index of the account when using Ledger - --relayer-ledger-address-index RELAYER_LEDGER_ADDRESS_INDEX - 🔐 the index of the address when using Ledger - --guardian GUARDIAN the address of the guradian - --guardian-service-url GUARDIAN_SERVICE_URL the url of the guardian service - --guardian-2fa-code GUARDIAN_2FA_CODE the 2fa code for the guardian - --options OPTIONS the transaction options (default: 0) - --send ✓ whether to broadcast the transaction (default: False) - --simulate whether to simulate the transaction (default: False) - --outfile OUTFILE where to save the output (signed transaction, hash) (default: stdout) - --guardian-pem GUARDIAN_PEM 🔑 the PEM file, if keyfile not provided - --guardian-pem-index GUARDIAN_PEM_INDEX 🔑 the index in the PEM file (default: 0) - --guardian-keyfile GUARDIAN_KEYFILE 🔑 a JSON keyfile, if PEM not provided - --guardian-passfile GUARDIAN_PASSFILE 🔑 a file containing keyfile's password, if keyfile provided - --guardian-ledger 🔐 bool flag for signing transaction using ledger - --guardian-ledger-account-index GUARDIAN_LEDGER_ACCOUNT_INDEX - 🔐 the index of the account when using Ledger - --guardian-ledger-address-index GUARDIAN_LEDGER_ADDRESS_INDEX - 🔐 the index of the address when using Ledger + -h, --help show this help message and exit + --bls-keys BLS_KEYS a list with the bls keys of the nodes as CSV (addrA,addrB) + --validators-pem VALIDATORS_PEM a PEM file holding the BLS keys; can contain multiple nodes + --delegation-contract DELEGATION_CONTRACT address of the delegation contract + --proxy PROXY 🔗 the URL of the proxy + --pem PEM 🔑 the PEM file, if keyfile not provided + --keyfile KEYFILE 🔑 a JSON keyfile, if PEM not provided + --passfile PASSFILE 🔑 a file containing keyfile's password, if keyfile provided. If not + provided, you'll be prompted to enter the password. + --ledger 🔐 bool flag for signing transaction using ledger + --sender-wallet-index SENDER_WALLET_INDEX 🔑 the address index; can be used for PEM files, keyfiles of type + mnemonic or Ledger devices (default: 0) + --sender-username SENDER_USERNAME 🖄 the username of the sender + --nonce NONCE # the nonce for the transaction + --recall-nonce ⭮ whether to recall the nonce when creating the transaction (default: + False) + --gas-price GAS_PRICE ⛽ the gas price (default: 1000000000) + --gas-limit GAS_LIMIT ⛽ the gas limit + --value VALUE the value to transfer (default: 0) + --chain CHAIN the chain identifier + --version VERSION the transaction version (default: 2) + --options OPTIONS the transaction options (default: 0) + --relayer RELAYER the bech32 address of the relayer + --guardian GUARDIAN the bech32 address of the guardian + --send ✓ whether to broadcast the transaction (default: False) + --simulate whether to simulate the transaction (default: False) + --outfile OUTFILE where to save the output (signed transaction, hash) (default: stdout) + --guardian-service-url GUARDIAN_SERVICE_URL the url of the guardian service + --guardian-2fa-code GUARDIAN_2FA_CODE the 2fa code for the guardian + --guardian-pem GUARDIAN_PEM 🔑 the PEM file, if keyfile not provided + --guardian-keyfile GUARDIAN_KEYFILE 🔑 a JSON keyfile, if PEM not provided + --guardian-passfile GUARDIAN_PASSFILE 🔑 a file containing keyfile's password, if keyfile provided. If not + provided, you'll be prompted to enter the password. + --guardian-ledger 🔐 bool flag for signing transaction using ledger + --guardian-wallet-index GUARDIAN_WALLET_INDEX 🔑 the address index; can be used for PEM files, keyfiles of type + mnemonic or Ledger devices (default: 0) + --relayer-pem RELAYER_PEM 🔑 the PEM file, if keyfile not provided + --relayer-keyfile RELAYER_KEYFILE 🔑 a JSON keyfile, if PEM not provided + --relayer-passfile RELAYER_PASSFILE 🔑 a file containing keyfile's password, if keyfile provided. If not + provided, you'll be prompted to enter the password. + --relayer-ledger 🔐 bool flag for signing transaction using ledger + --relayer-wallet-index RELAYER_WALLET_INDEX 🔑 the address index; can be used for PEM files, keyfiles of type + mnemonic or Ledger devices (default: 0) ``` ### StakingProvider.UnstakeNodes @@ -1427,58 +1670,53 @@ options: $ mxpy staking-provider unstake-nodes --help usage: mxpy staking-provider unstake-nodes [-h] ... -Unstake nodes must be called by the contract owner - -options: - -h, --help show this help message and exit - --bls-keys BLS_KEYS a list with the bls keys of the nodes - --validators-file VALIDATORS_FILE a JSON file describing the Nodes - --delegation-contract DELEGATION_CONTRACT address of the delegation contract - --proxy PROXY 🔗 the URL of the proxy - --pem PEM 🔑 the PEM file, if keyfile not provided - --pem-index PEM_INDEX 🔑 the index in the PEM file (default: 0) - --keyfile KEYFILE 🔑 a JSON keyfile, if PEM not provided - --passfile PASSFILE 🔑 a file containing keyfile's password, if keyfile provided - --ledger 🔐 bool flag for signing transaction using ledger - --ledger-account-index LEDGER_ACCOUNT_INDEX 🔐 the index of the account when using Ledger - --ledger-address-index LEDGER_ADDRESS_INDEX 🔐 the index of the address when using Ledger - --sender-username SENDER_USERNAME 🖄 the username of the sender - --nonce NONCE # the nonce for the transaction - --recall-nonce ⭮ whether to recall the nonce when creating the transaction (default: - False) - --gas-price GAS_PRICE ⛽ the gas price (default: 1000000000) - --gas-limit GAS_LIMIT ⛽ the gas limit - --estimate-gas ⛽ whether to estimate the gas limit (default: 0) - --value VALUE the value to transfer (default: 0) - --chain CHAIN the chain identifier - --version VERSION the transaction version (default: 2) - --relayer RELAYER the bech32 address of the relayer - --relayer-pem RELAYER_PEM 🔑 the PEM file, if keyfile not provided - --relayer-pem-index RELAYER_PEM_INDEX 🔑 the index in the PEM file (default: 0) - --relayer-keyfile RELAYER_KEYFILE 🔑 a JSON keyfile, if PEM not provided - --relayer-passfile RELAYER_PASSFILE 🔑 a file containing keyfile's password, if keyfile provided - --relayer-ledger 🔐 bool flag for signing transaction using ledger - --relayer-ledger-account-index RELAYER_LEDGER_ACCOUNT_INDEX - 🔐 the index of the account when using Ledger - --relayer-ledger-address-index RELAYER_LEDGER_ADDRESS_INDEX - 🔐 the index of the address when using Ledger - --guardian GUARDIAN the address of the guradian - --guardian-service-url GUARDIAN_SERVICE_URL the url of the guardian service - --guardian-2fa-code GUARDIAN_2FA_CODE the 2fa code for the guardian - --options OPTIONS the transaction options (default: 0) - --send ✓ whether to broadcast the transaction (default: False) - --simulate whether to simulate the transaction (default: False) - --outfile OUTFILE where to save the output (signed transaction, hash) (default: stdout) - --guardian-pem GUARDIAN_PEM 🔑 the PEM file, if keyfile not provided - --guardian-pem-index GUARDIAN_PEM_INDEX 🔑 the index in the PEM file (default: 0) - --guardian-keyfile GUARDIAN_KEYFILE 🔑 a JSON keyfile, if PEM not provided - --guardian-passfile GUARDIAN_PASSFILE 🔑 a file containing keyfile's password, if keyfile provided - --guardian-ledger 🔐 bool flag for signing transaction using ledger - --guardian-ledger-account-index GUARDIAN_LEDGER_ACCOUNT_INDEX - 🔐 the index of the account when using Ledger - --guardian-ledger-address-index GUARDIAN_LEDGER_ADDRESS_INDEX - 🔐 the index of the address when using Ledger - +Unstake nodes must be called by the contract owner + +options: + -h, --help show this help message and exit + --bls-keys BLS_KEYS a list with the bls keys of the nodes as CSV (addrA,addrB) + --validators-pem VALIDATORS_PEM a PEM file holding the BLS keys; can contain multiple nodes + --delegation-contract DELEGATION_CONTRACT address of the delegation contract + --proxy PROXY 🔗 the URL of the proxy + --pem PEM 🔑 the PEM file, if keyfile not provided + --keyfile KEYFILE 🔑 a JSON keyfile, if PEM not provided + --passfile PASSFILE 🔑 a file containing keyfile's password, if keyfile provided. If not + provided, you'll be prompted to enter the password. + --ledger 🔐 bool flag for signing transaction using ledger + --sender-wallet-index SENDER_WALLET_INDEX 🔑 the address index; can be used for PEM files, keyfiles of type + mnemonic or Ledger devices (default: 0) + --sender-username SENDER_USERNAME 🖄 the username of the sender + --nonce NONCE # the nonce for the transaction + --recall-nonce ⭮ whether to recall the nonce when creating the transaction (default: + False) + --gas-price GAS_PRICE ⛽ the gas price (default: 1000000000) + --gas-limit GAS_LIMIT ⛽ the gas limit + --value VALUE the value to transfer (default: 0) + --chain CHAIN the chain identifier + --version VERSION the transaction version (default: 2) + --options OPTIONS the transaction options (default: 0) + --relayer RELAYER the bech32 address of the relayer + --guardian GUARDIAN the bech32 address of the guardian + --send ✓ whether to broadcast the transaction (default: False) + --simulate whether to simulate the transaction (default: False) + --outfile OUTFILE where to save the output (signed transaction, hash) (default: stdout) + --guardian-service-url GUARDIAN_SERVICE_URL the url of the guardian service + --guardian-2fa-code GUARDIAN_2FA_CODE the 2fa code for the guardian + --guardian-pem GUARDIAN_PEM 🔑 the PEM file, if keyfile not provided + --guardian-keyfile GUARDIAN_KEYFILE 🔑 a JSON keyfile, if PEM not provided + --guardian-passfile GUARDIAN_PASSFILE 🔑 a file containing keyfile's password, if keyfile provided. If not + provided, you'll be prompted to enter the password. + --guardian-ledger 🔐 bool flag for signing transaction using ledger + --guardian-wallet-index GUARDIAN_WALLET_INDEX 🔑 the address index; can be used for PEM files, keyfiles of type + mnemonic or Ledger devices (default: 0) + --relayer-pem RELAYER_PEM 🔑 the PEM file, if keyfile not provided + --relayer-keyfile RELAYER_KEYFILE 🔑 a JSON keyfile, if PEM not provided + --relayer-passfile RELAYER_PASSFILE 🔑 a file containing keyfile's password, if keyfile provided. If not + provided, you'll be prompted to enter the password. + --relayer-ledger 🔐 bool flag for signing transaction using ledger + --relayer-wallet-index RELAYER_WALLET_INDEX 🔑 the address index; can be used for PEM files, keyfiles of type + mnemonic or Ledger devices (default: 0) + ``` ### StakingProvider.UnjailNodes @@ -1490,54 +1728,314 @@ usage: mxpy staking-provider unjail-nodes [-h] ... Unjail nodes must be called by the contract owner options: - -h, --help show this help message and exit - --bls-keys BLS_KEYS a list with the bls keys of the nodes - --validators-file VALIDATORS_FILE a JSON file describing the Nodes - --delegation-contract DELEGATION_CONTRACT address of the delegation contract - --proxy PROXY 🔗 the URL of the proxy - --pem PEM 🔑 the PEM file, if keyfile not provided - --pem-index PEM_INDEX 🔑 the index in the PEM file (default: 0) - --keyfile KEYFILE 🔑 a JSON keyfile, if PEM not provided - --passfile PASSFILE 🔑 a file containing keyfile's password, if keyfile provided - --ledger 🔐 bool flag for signing transaction using ledger - --ledger-account-index LEDGER_ACCOUNT_INDEX 🔐 the index of the account when using Ledger - --ledger-address-index LEDGER_ADDRESS_INDEX 🔐 the index of the address when using Ledger - --sender-username SENDER_USERNAME 🖄 the username of the sender - --nonce NONCE # the nonce for the transaction - --recall-nonce ⭮ whether to recall the nonce when creating the transaction (default: - False) - --gas-price GAS_PRICE ⛽ the gas price (default: 1000000000) - --gas-limit GAS_LIMIT ⛽ the gas limit - --estimate-gas ⛽ whether to estimate the gas limit (default: 0) - --value VALUE the value to transfer (default: 0) - --chain CHAIN the chain identifier - --version VERSION the transaction version (default: 2) - --relayer RELAYER the bech32 address of the relayer - --relayer-pem RELAYER_PEM 🔑 the PEM file, if keyfile not provided - --relayer-pem-index RELAYER_PEM_INDEX 🔑 the index in the PEM file (default: 0) - --relayer-keyfile RELAYER_KEYFILE 🔑 a JSON keyfile, if PEM not provided - --relayer-passfile RELAYER_PASSFILE 🔑 a file containing keyfile's password, if keyfile provided - --relayer-ledger 🔐 bool flag for signing transaction using ledger - --relayer-ledger-account-index RELAYER_LEDGER_ACCOUNT_INDEX - 🔐 the index of the account when using Ledger - --relayer-ledger-address-index RELAYER_LEDGER_ADDRESS_INDEX - 🔐 the index of the address when using Ledger - --guardian GUARDIAN the address of the guradian - --guardian-service-url GUARDIAN_SERVICE_URL the url of the guardian service - --guardian-2fa-code GUARDIAN_2FA_CODE the 2fa code for the guardian - --options OPTIONS the transaction options (default: 0) - --send ✓ whether to broadcast the transaction (default: False) - --simulate whether to simulate the transaction (default: False) - --outfile OUTFILE where to save the output (signed transaction, hash) (default: stdout) - --guardian-pem GUARDIAN_PEM 🔑 the PEM file, if keyfile not provided - --guardian-pem-index GUARDIAN_PEM_INDEX 🔑 the index in the PEM file (default: 0) - --guardian-keyfile GUARDIAN_KEYFILE 🔑 a JSON keyfile, if PEM not provided - --guardian-passfile GUARDIAN_PASSFILE 🔑 a file containing keyfile's password, if keyfile provided - --guardian-ledger 🔐 bool flag for signing transaction using ledger - --guardian-ledger-account-index GUARDIAN_LEDGER_ACCOUNT_INDEX - 🔐 the index of the account when using Ledger - --guardian-ledger-address-index GUARDIAN_LEDGER_ADDRESS_INDEX - 🔐 the index of the address when using Ledger + -h, --help show this help message and exit + --bls-keys BLS_KEYS a list with the bls keys of the nodes as CSV (addrA,addrB) + --validators-pem VALIDATORS_PEM a PEM file holding the BLS keys; can contain multiple nodes + --delegation-contract DELEGATION_CONTRACT address of the delegation contract + --proxy PROXY 🔗 the URL of the proxy + --pem PEM 🔑 the PEM file, if keyfile not provided + --keyfile KEYFILE 🔑 a JSON keyfile, if PEM not provided + --passfile PASSFILE 🔑 a file containing keyfile's password, if keyfile provided. If not + provided, you'll be prompted to enter the password. + --ledger 🔐 bool flag for signing transaction using ledger + --sender-wallet-index SENDER_WALLET_INDEX 🔑 the address index; can be used for PEM files, keyfiles of type + mnemonic or Ledger devices (default: 0) + --sender-username SENDER_USERNAME 🖄 the username of the sender + --nonce NONCE # the nonce for the transaction + --recall-nonce ⭮ whether to recall the nonce when creating the transaction (default: + False) + --gas-price GAS_PRICE ⛽ the gas price (default: 1000000000) + --gas-limit GAS_LIMIT ⛽ the gas limit + --value VALUE the value to transfer (default: 0) + --chain CHAIN the chain identifier + --version VERSION the transaction version (default: 2) + --options OPTIONS the transaction options (default: 0) + --relayer RELAYER the bech32 address of the relayer + --guardian GUARDIAN the bech32 address of the guardian + --send ✓ whether to broadcast the transaction (default: False) + --simulate whether to simulate the transaction (default: False) + --outfile OUTFILE where to save the output (signed transaction, hash) (default: stdout) + --guardian-service-url GUARDIAN_SERVICE_URL the url of the guardian service + --guardian-2fa-code GUARDIAN_2FA_CODE the 2fa code for the guardian + --guardian-pem GUARDIAN_PEM 🔑 the PEM file, if keyfile not provided + --guardian-keyfile GUARDIAN_KEYFILE 🔑 a JSON keyfile, if PEM not provided + --guardian-passfile GUARDIAN_PASSFILE 🔑 a file containing keyfile's password, if keyfile provided. If not + provided, you'll be prompted to enter the password. + --guardian-ledger 🔐 bool flag for signing transaction using ledger + --guardian-wallet-index GUARDIAN_WALLET_INDEX 🔑 the address index; can be used for PEM files, keyfiles of type + mnemonic or Ledger devices (default: 0) + --relayer-pem RELAYER_PEM 🔑 the PEM file, if keyfile not provided + --relayer-keyfile RELAYER_KEYFILE 🔑 a JSON keyfile, if PEM not provided + --relayer-passfile RELAYER_PASSFILE 🔑 a file containing keyfile's password, if keyfile provided. If not + provided, you'll be prompted to enter the password. + --relayer-ledger 🔐 bool flag for signing transaction using ledger + --relayer-wallet-index RELAYER_WALLET_INDEX 🔑 the address index; can be used for PEM files, keyfiles of type + mnemonic or Ledger devices (default: 0) + +``` +### StakingProvider.Delegate + + +``` +$ mxpy staking-provider delegate --help +usage: mxpy staking-provider delegate [-h] ... + +Delegate funds to a delegation contract + +options: + -h, --help show this help message and exit + --delegation-contract DELEGATION_CONTRACT address of the delegation contract + --proxy PROXY 🔗 the URL of the proxy + --pem PEM 🔑 the PEM file, if keyfile not provided + --keyfile KEYFILE 🔑 a JSON keyfile, if PEM not provided + --passfile PASSFILE 🔑 a file containing keyfile's password, if keyfile provided. If not + provided, you'll be prompted to enter the password. + --ledger 🔐 bool flag for signing transaction using ledger + --sender-wallet-index SENDER_WALLET_INDEX 🔑 the address index; can be used for PEM files, keyfiles of type + mnemonic or Ledger devices (default: 0) + --sender-username SENDER_USERNAME 🖄 the username of the sender + --nonce NONCE # the nonce for the transaction + --recall-nonce ⭮ whether to recall the nonce when creating the transaction (default: + False) + --gas-price GAS_PRICE ⛽ the gas price (default: 1000000000) + --gas-limit GAS_LIMIT ⛽ the gas limit + --value VALUE the value to transfer (default: 0) + --chain CHAIN the chain identifier + --version VERSION the transaction version (default: 2) + --options OPTIONS the transaction options (default: 0) + --relayer RELAYER the bech32 address of the relayer + --guardian GUARDIAN the bech32 address of the guardian + --send ✓ whether to broadcast the transaction (default: False) + --simulate whether to simulate the transaction (default: False) + --outfile OUTFILE where to save the output (signed transaction, hash) (default: stdout) + --guardian-service-url GUARDIAN_SERVICE_URL the url of the guardian service + --guardian-2fa-code GUARDIAN_2FA_CODE the 2fa code for the guardian + --guardian-pem GUARDIAN_PEM 🔑 the PEM file, if keyfile not provided + --guardian-keyfile GUARDIAN_KEYFILE 🔑 a JSON keyfile, if PEM not provided + --guardian-passfile GUARDIAN_PASSFILE 🔑 a file containing keyfile's password, if keyfile provided. If not + provided, you'll be prompted to enter the password. + --guardian-ledger 🔐 bool flag for signing transaction using ledger + --guardian-wallet-index GUARDIAN_WALLET_INDEX 🔑 the address index; can be used for PEM files, keyfiles of type + mnemonic or Ledger devices (default: 0) + --relayer-pem RELAYER_PEM 🔑 the PEM file, if keyfile not provided + --relayer-keyfile RELAYER_KEYFILE 🔑 a JSON keyfile, if PEM not provided + --relayer-passfile RELAYER_PASSFILE 🔑 a file containing keyfile's password, if keyfile provided. If not + provided, you'll be prompted to enter the password. + --relayer-ledger 🔐 bool flag for signing transaction using ledger + --relayer-wallet-index RELAYER_WALLET_INDEX 🔑 the address index; can be used for PEM files, keyfiles of type + mnemonic or Ledger devices (default: 0) + +``` +### StakingProvider.ClaimRewards + + +``` +$ mxpy staking-provider claim-rewards --help +usage: mxpy staking-provider claim-rewards [-h] ... + +Claim the rewards earned for delegating + +options: + -h, --help show this help message and exit + --delegation-contract DELEGATION_CONTRACT address of the delegation contract + --proxy PROXY 🔗 the URL of the proxy + --pem PEM 🔑 the PEM file, if keyfile not provided + --keyfile KEYFILE 🔑 a JSON keyfile, if PEM not provided + --passfile PASSFILE 🔑 a file containing keyfile's password, if keyfile provided. If not + provided, you'll be prompted to enter the password. + --ledger 🔐 bool flag for signing transaction using ledger + --sender-wallet-index SENDER_WALLET_INDEX 🔑 the address index; can be used for PEM files, keyfiles of type + mnemonic or Ledger devices (default: 0) + --sender-username SENDER_USERNAME 🖄 the username of the sender + --nonce NONCE # the nonce for the transaction + --recall-nonce ⭮ whether to recall the nonce when creating the transaction (default: + False) + --gas-price GAS_PRICE ⛽ the gas price (default: 1000000000) + --gas-limit GAS_LIMIT ⛽ the gas limit + --value VALUE the value to transfer (default: 0) + --chain CHAIN the chain identifier + --version VERSION the transaction version (default: 2) + --options OPTIONS the transaction options (default: 0) + --relayer RELAYER the bech32 address of the relayer + --guardian GUARDIAN the bech32 address of the guardian + --send ✓ whether to broadcast the transaction (default: False) + --simulate whether to simulate the transaction (default: False) + --outfile OUTFILE where to save the output (signed transaction, hash) (default: stdout) + --guardian-service-url GUARDIAN_SERVICE_URL the url of the guardian service + --guardian-2fa-code GUARDIAN_2FA_CODE the 2fa code for the guardian + --guardian-pem GUARDIAN_PEM 🔑 the PEM file, if keyfile not provided + --guardian-keyfile GUARDIAN_KEYFILE 🔑 a JSON keyfile, if PEM not provided + --guardian-passfile GUARDIAN_PASSFILE 🔑 a file containing keyfile's password, if keyfile provided. If not + provided, you'll be prompted to enter the password. + --guardian-ledger 🔐 bool flag for signing transaction using ledger + --guardian-wallet-index GUARDIAN_WALLET_INDEX 🔑 the address index; can be used for PEM files, keyfiles of type + mnemonic or Ledger devices (default: 0) + --relayer-pem RELAYER_PEM 🔑 the PEM file, if keyfile not provided + --relayer-keyfile RELAYER_KEYFILE 🔑 a JSON keyfile, if PEM not provided + --relayer-passfile RELAYER_PASSFILE 🔑 a file containing keyfile's password, if keyfile provided. If not + provided, you'll be prompted to enter the password. + --relayer-ledger 🔐 bool flag for signing transaction using ledger + --relayer-wallet-index RELAYER_WALLET_INDEX 🔑 the address index; can be used for PEM files, keyfiles of type + mnemonic or Ledger devices (default: 0) + +``` +### StakingProvider.RedelegateRewards + + +``` +$ mxpy staking-provider redelegate-rewards --help +usage: mxpy staking-provider redelegate-rewards [-h] ... + +Redelegate the rewards earned for delegating + +options: + -h, --help show this help message and exit + --delegation-contract DELEGATION_CONTRACT address of the delegation contract + --proxy PROXY 🔗 the URL of the proxy + --pem PEM 🔑 the PEM file, if keyfile not provided + --keyfile KEYFILE 🔑 a JSON keyfile, if PEM not provided + --passfile PASSFILE 🔑 a file containing keyfile's password, if keyfile provided. If not + provided, you'll be prompted to enter the password. + --ledger 🔐 bool flag for signing transaction using ledger + --sender-wallet-index SENDER_WALLET_INDEX 🔑 the address index; can be used for PEM files, keyfiles of type + mnemonic or Ledger devices (default: 0) + --sender-username SENDER_USERNAME 🖄 the username of the sender + --nonce NONCE # the nonce for the transaction + --recall-nonce ⭮ whether to recall the nonce when creating the transaction (default: + False) + --gas-price GAS_PRICE ⛽ the gas price (default: 1000000000) + --gas-limit GAS_LIMIT ⛽ the gas limit + --value VALUE the value to transfer (default: 0) + --chain CHAIN the chain identifier + --version VERSION the transaction version (default: 2) + --options OPTIONS the transaction options (default: 0) + --relayer RELAYER the bech32 address of the relayer + --guardian GUARDIAN the bech32 address of the guardian + --send ✓ whether to broadcast the transaction (default: False) + --simulate whether to simulate the transaction (default: False) + --outfile OUTFILE where to save the output (signed transaction, hash) (default: stdout) + --guardian-service-url GUARDIAN_SERVICE_URL the url of the guardian service + --guardian-2fa-code GUARDIAN_2FA_CODE the 2fa code for the guardian + --guardian-pem GUARDIAN_PEM 🔑 the PEM file, if keyfile not provided + --guardian-keyfile GUARDIAN_KEYFILE 🔑 a JSON keyfile, if PEM not provided + --guardian-passfile GUARDIAN_PASSFILE 🔑 a file containing keyfile's password, if keyfile provided. If not + provided, you'll be prompted to enter the password. + --guardian-ledger 🔐 bool flag for signing transaction using ledger + --guardian-wallet-index GUARDIAN_WALLET_INDEX 🔑 the address index; can be used for PEM files, keyfiles of type + mnemonic or Ledger devices (default: 0) + --relayer-pem RELAYER_PEM 🔑 the PEM file, if keyfile not provided + --relayer-keyfile RELAYER_KEYFILE 🔑 a JSON keyfile, if PEM not provided + --relayer-passfile RELAYER_PASSFILE 🔑 a file containing keyfile's password, if keyfile provided. If not + provided, you'll be prompted to enter the password. + --relayer-ledger 🔐 bool flag for signing transaction using ledger + --relayer-wallet-index RELAYER_WALLET_INDEX 🔑 the address index; can be used for PEM files, keyfiles of type + mnemonic or Ledger devices (default: 0) + +``` +### StakingProvider.Undelegate + + +``` +$ mxpy staking-provider undelegate --help +usage: mxpy staking-provider undelegate [-h] ... + +Undelegate funds from a delegation contract + +options: + -h, --help show this help message and exit + --delegation-contract DELEGATION_CONTRACT address of the delegation contract + --proxy PROXY 🔗 the URL of the proxy + --pem PEM 🔑 the PEM file, if keyfile not provided + --keyfile KEYFILE 🔑 a JSON keyfile, if PEM not provided + --passfile PASSFILE 🔑 a file containing keyfile's password, if keyfile provided. If not + provided, you'll be prompted to enter the password. + --ledger 🔐 bool flag for signing transaction using ledger + --sender-wallet-index SENDER_WALLET_INDEX 🔑 the address index; can be used for PEM files, keyfiles of type + mnemonic or Ledger devices (default: 0) + --sender-username SENDER_USERNAME 🖄 the username of the sender + --nonce NONCE # the nonce for the transaction + --recall-nonce ⭮ whether to recall the nonce when creating the transaction (default: + False) + --gas-price GAS_PRICE ⛽ the gas price (default: 1000000000) + --gas-limit GAS_LIMIT ⛽ the gas limit + --value VALUE the value to transfer (default: 0) + --chain CHAIN the chain identifier + --version VERSION the transaction version (default: 2) + --options OPTIONS the transaction options (default: 0) + --relayer RELAYER the bech32 address of the relayer + --guardian GUARDIAN the bech32 address of the guardian + --send ✓ whether to broadcast the transaction (default: False) + --simulate whether to simulate the transaction (default: False) + --outfile OUTFILE where to save the output (signed transaction, hash) (default: stdout) + --guardian-service-url GUARDIAN_SERVICE_URL the url of the guardian service + --guardian-2fa-code GUARDIAN_2FA_CODE the 2fa code for the guardian + --guardian-pem GUARDIAN_PEM 🔑 the PEM file, if keyfile not provided + --guardian-keyfile GUARDIAN_KEYFILE 🔑 a JSON keyfile, if PEM not provided + --guardian-passfile GUARDIAN_PASSFILE 🔑 a file containing keyfile's password, if keyfile provided. If not + provided, you'll be prompted to enter the password. + --guardian-ledger 🔐 bool flag for signing transaction using ledger + --guardian-wallet-index GUARDIAN_WALLET_INDEX 🔑 the address index; can be used for PEM files, keyfiles of type + mnemonic or Ledger devices (default: 0) + --relayer-pem RELAYER_PEM 🔑 the PEM file, if keyfile not provided + --relayer-keyfile RELAYER_KEYFILE 🔑 a JSON keyfile, if PEM not provided + --relayer-passfile RELAYER_PASSFILE 🔑 a file containing keyfile's password, if keyfile provided. If not + provided, you'll be prompted to enter the password. + --relayer-ledger 🔐 bool flag for signing transaction using ledger + --relayer-wallet-index RELAYER_WALLET_INDEX 🔑 the address index; can be used for PEM files, keyfiles of type + mnemonic or Ledger devices (default: 0) + +``` +### StakingProvider.Withdraw + + +``` +$ mxpy staking-provider withdraw --help +usage: mxpy staking-provider withdraw [-h] ... + +Withdraw funds from a delegation contract + +options: + -h, --help show this help message and exit + --delegation-contract DELEGATION_CONTRACT address of the delegation contract + --proxy PROXY 🔗 the URL of the proxy + --pem PEM 🔑 the PEM file, if keyfile not provided + --keyfile KEYFILE 🔑 a JSON keyfile, if PEM not provided + --passfile PASSFILE 🔑 a file containing keyfile's password, if keyfile provided. If not + provided, you'll be prompted to enter the password. + --ledger 🔐 bool flag for signing transaction using ledger + --sender-wallet-index SENDER_WALLET_INDEX 🔑 the address index; can be used for PEM files, keyfiles of type + mnemonic or Ledger devices (default: 0) + --sender-username SENDER_USERNAME 🖄 the username of the sender + --nonce NONCE # the nonce for the transaction + --recall-nonce ⭮ whether to recall the nonce when creating the transaction (default: + False) + --gas-price GAS_PRICE ⛽ the gas price (default: 1000000000) + --gas-limit GAS_LIMIT ⛽ the gas limit + --value VALUE the value to transfer (default: 0) + --chain CHAIN the chain identifier + --version VERSION the transaction version (default: 2) + --options OPTIONS the transaction options (default: 0) + --relayer RELAYER the bech32 address of the relayer + --guardian GUARDIAN the bech32 address of the guardian + --send ✓ whether to broadcast the transaction (default: False) + --simulate whether to simulate the transaction (default: False) + --outfile OUTFILE where to save the output (signed transaction, hash) (default: stdout) + --guardian-service-url GUARDIAN_SERVICE_URL the url of the guardian service + --guardian-2fa-code GUARDIAN_2FA_CODE the 2fa code for the guardian + --guardian-pem GUARDIAN_PEM 🔑 the PEM file, if keyfile not provided + --guardian-keyfile GUARDIAN_KEYFILE 🔑 a JSON keyfile, if PEM not provided + --guardian-passfile GUARDIAN_PASSFILE 🔑 a file containing keyfile's password, if keyfile provided. If not + provided, you'll be prompted to enter the password. + --guardian-ledger 🔐 bool flag for signing transaction using ledger + --guardian-wallet-index GUARDIAN_WALLET_INDEX 🔑 the address index; can be used for PEM files, keyfiles of type + mnemonic or Ledger devices (default: 0) + --relayer-pem RELAYER_PEM 🔑 the PEM file, if keyfile not provided + --relayer-keyfile RELAYER_KEYFILE 🔑 a JSON keyfile, if PEM not provided + --relayer-passfile RELAYER_PASSFILE 🔑 a file containing keyfile's password, if keyfile provided. If not + provided, you'll be prompted to enter the password. + --relayer-ledger 🔐 bool flag for signing transaction using ledger + --relayer-wallet-index RELAYER_WALLET_INDEX 🔑 the address index; can be used for PEM files, keyfiles of type + mnemonic or Ledger devices (default: 0) ``` ### StakingProvider.ChangeServiceFee @@ -1550,53 +2048,48 @@ usage: mxpy staking-provider change-service-fee [-h] ... Change service fee must be called by the contract owner options: - -h, --help show this help message and exit - --service-fee SERVICE_FEE new service fee value - --delegation-contract DELEGATION_CONTRACT address of the delegation contract - --proxy PROXY 🔗 the URL of the proxy - --pem PEM 🔑 the PEM file, if keyfile not provided - --pem-index PEM_INDEX 🔑 the index in the PEM file (default: 0) - --keyfile KEYFILE 🔑 a JSON keyfile, if PEM not provided - --passfile PASSFILE 🔑 a file containing keyfile's password, if keyfile provided - --ledger 🔐 bool flag for signing transaction using ledger - --ledger-account-index LEDGER_ACCOUNT_INDEX 🔐 the index of the account when using Ledger - --ledger-address-index LEDGER_ADDRESS_INDEX 🔐 the index of the address when using Ledger - --sender-username SENDER_USERNAME 🖄 the username of the sender - --nonce NONCE # the nonce for the transaction - --recall-nonce ⭮ whether to recall the nonce when creating the transaction (default: - False) - --gas-price GAS_PRICE ⛽ the gas price (default: 1000000000) - --gas-limit GAS_LIMIT ⛽ the gas limit - --estimate-gas ⛽ whether to estimate the gas limit (default: 0) - --value VALUE the value to transfer (default: 0) - --chain CHAIN the chain identifier - --version VERSION the transaction version (default: 2) - --relayer RELAYER the bech32 address of the relayer - --relayer-pem RELAYER_PEM 🔑 the PEM file, if keyfile not provided - --relayer-pem-index RELAYER_PEM_INDEX 🔑 the index in the PEM file (default: 0) - --relayer-keyfile RELAYER_KEYFILE 🔑 a JSON keyfile, if PEM not provided - --relayer-passfile RELAYER_PASSFILE 🔑 a file containing keyfile's password, if keyfile provided - --relayer-ledger 🔐 bool flag for signing transaction using ledger - --relayer-ledger-account-index RELAYER_LEDGER_ACCOUNT_INDEX - 🔐 the index of the account when using Ledger - --relayer-ledger-address-index RELAYER_LEDGER_ADDRESS_INDEX - 🔐 the index of the address when using Ledger - --guardian GUARDIAN the address of the guradian - --guardian-service-url GUARDIAN_SERVICE_URL the url of the guardian service - --guardian-2fa-code GUARDIAN_2FA_CODE the 2fa code for the guardian - --options OPTIONS the transaction options (default: 0) - --send ✓ whether to broadcast the transaction (default: False) - --simulate whether to simulate the transaction (default: False) - --outfile OUTFILE where to save the output (signed transaction, hash) (default: stdout) - --guardian-pem GUARDIAN_PEM 🔑 the PEM file, if keyfile not provided - --guardian-pem-index GUARDIAN_PEM_INDEX 🔑 the index in the PEM file (default: 0) - --guardian-keyfile GUARDIAN_KEYFILE 🔑 a JSON keyfile, if PEM not provided - --guardian-passfile GUARDIAN_PASSFILE 🔑 a file containing keyfile's password, if keyfile provided - --guardian-ledger 🔐 bool flag for signing transaction using ledger - --guardian-ledger-account-index GUARDIAN_LEDGER_ACCOUNT_INDEX - 🔐 the index of the account when using Ledger - --guardian-ledger-address-index GUARDIAN_LEDGER_ADDRESS_INDEX - 🔐 the index of the address when using Ledger + -h, --help show this help message and exit + --service-fee SERVICE_FEE new service fee value + --delegation-contract DELEGATION_CONTRACT address of the delegation contract + --proxy PROXY 🔗 the URL of the proxy + --pem PEM 🔑 the PEM file, if keyfile not provided + --keyfile KEYFILE 🔑 a JSON keyfile, if PEM not provided + --passfile PASSFILE 🔑 a file containing keyfile's password, if keyfile provided. If not + provided, you'll be prompted to enter the password. + --ledger 🔐 bool flag for signing transaction using ledger + --sender-wallet-index SENDER_WALLET_INDEX 🔑 the address index; can be used for PEM files, keyfiles of type + mnemonic or Ledger devices (default: 0) + --sender-username SENDER_USERNAME 🖄 the username of the sender + --nonce NONCE # the nonce for the transaction + --recall-nonce ⭮ whether to recall the nonce when creating the transaction (default: + False) + --gas-price GAS_PRICE ⛽ the gas price (default: 1000000000) + --gas-limit GAS_LIMIT ⛽ the gas limit + --value VALUE the value to transfer (default: 0) + --chain CHAIN the chain identifier + --version VERSION the transaction version (default: 2) + --options OPTIONS the transaction options (default: 0) + --relayer RELAYER the bech32 address of the relayer + --guardian GUARDIAN the bech32 address of the guardian + --send ✓ whether to broadcast the transaction (default: False) + --simulate whether to simulate the transaction (default: False) + --outfile OUTFILE where to save the output (signed transaction, hash) (default: stdout) + --guardian-service-url GUARDIAN_SERVICE_URL the url of the guardian service + --guardian-2fa-code GUARDIAN_2FA_CODE the 2fa code for the guardian + --guardian-pem GUARDIAN_PEM 🔑 the PEM file, if keyfile not provided + --guardian-keyfile GUARDIAN_KEYFILE 🔑 a JSON keyfile, if PEM not provided + --guardian-passfile GUARDIAN_PASSFILE 🔑 a file containing keyfile's password, if keyfile provided. If not + provided, you'll be prompted to enter the password. + --guardian-ledger 🔐 bool flag for signing transaction using ledger + --guardian-wallet-index GUARDIAN_WALLET_INDEX 🔑 the address index; can be used for PEM files, keyfiles of type + mnemonic or Ledger devices (default: 0) + --relayer-pem RELAYER_PEM 🔑 the PEM file, if keyfile not provided + --relayer-keyfile RELAYER_KEYFILE 🔑 a JSON keyfile, if PEM not provided + --relayer-passfile RELAYER_PASSFILE 🔑 a file containing keyfile's password, if keyfile provided. If not + provided, you'll be prompted to enter the password. + --relayer-ledger 🔐 bool flag for signing transaction using ledger + --relayer-wallet-index RELAYER_WALLET_INDEX 🔑 the address index; can be used for PEM files, keyfiles of type + mnemonic or Ledger devices (default: 0) ``` ### StakingProvider.ModifyDelegationCap @@ -1609,53 +2102,48 @@ usage: mxpy staking-provider modify-delegation-cap [-h] ... Modify delegation cap must be called by the contract owner options: - -h, --help show this help message and exit - --delegation-cap DELEGATION_CAP new delegation contract capacity - --delegation-contract DELEGATION_CONTRACT address of the delegation contract - --proxy PROXY 🔗 the URL of the proxy - --pem PEM 🔑 the PEM file, if keyfile not provided - --pem-index PEM_INDEX 🔑 the index in the PEM file (default: 0) - --keyfile KEYFILE 🔑 a JSON keyfile, if PEM not provided - --passfile PASSFILE 🔑 a file containing keyfile's password, if keyfile provided - --ledger 🔐 bool flag for signing transaction using ledger - --ledger-account-index LEDGER_ACCOUNT_INDEX 🔐 the index of the account when using Ledger - --ledger-address-index LEDGER_ADDRESS_INDEX 🔐 the index of the address when using Ledger - --sender-username SENDER_USERNAME 🖄 the username of the sender - --nonce NONCE # the nonce for the transaction - --recall-nonce ⭮ whether to recall the nonce when creating the transaction (default: - False) - --gas-price GAS_PRICE ⛽ the gas price (default: 1000000000) - --gas-limit GAS_LIMIT ⛽ the gas limit - --estimate-gas ⛽ whether to estimate the gas limit (default: 0) - --value VALUE the value to transfer (default: 0) - --chain CHAIN the chain identifier - --version VERSION the transaction version (default: 2) - --relayer RELAYER the bech32 address of the relayer - --relayer-pem RELAYER_PEM 🔑 the PEM file, if keyfile not provided - --relayer-pem-index RELAYER_PEM_INDEX 🔑 the index in the PEM file (default: 0) - --relayer-keyfile RELAYER_KEYFILE 🔑 a JSON keyfile, if PEM not provided - --relayer-passfile RELAYER_PASSFILE 🔑 a file containing keyfile's password, if keyfile provided - --relayer-ledger 🔐 bool flag for signing transaction using ledger - --relayer-ledger-account-index RELAYER_LEDGER_ACCOUNT_INDEX - 🔐 the index of the account when using Ledger - --relayer-ledger-address-index RELAYER_LEDGER_ADDRESS_INDEX - 🔐 the index of the address when using Ledger - --guardian GUARDIAN the address of the guradian - --guardian-service-url GUARDIAN_SERVICE_URL the url of the guardian service - --guardian-2fa-code GUARDIAN_2FA_CODE the 2fa code for the guardian - --options OPTIONS the transaction options (default: 0) - --send ✓ whether to broadcast the transaction (default: False) - --simulate whether to simulate the transaction (default: False) - --outfile OUTFILE where to save the output (signed transaction, hash) (default: stdout) - --guardian-pem GUARDIAN_PEM 🔑 the PEM file, if keyfile not provided - --guardian-pem-index GUARDIAN_PEM_INDEX 🔑 the index in the PEM file (default: 0) - --guardian-keyfile GUARDIAN_KEYFILE 🔑 a JSON keyfile, if PEM not provided - --guardian-passfile GUARDIAN_PASSFILE 🔑 a file containing keyfile's password, if keyfile provided - --guardian-ledger 🔐 bool flag for signing transaction using ledger - --guardian-ledger-account-index GUARDIAN_LEDGER_ACCOUNT_INDEX - 🔐 the index of the account when using Ledger - --guardian-ledger-address-index GUARDIAN_LEDGER_ADDRESS_INDEX - 🔐 the index of the address when using Ledger + -h, --help show this help message and exit + --delegation-cap DELEGATION_CAP new delegation contract capacity + --delegation-contract DELEGATION_CONTRACT address of the delegation contract + --proxy PROXY 🔗 the URL of the proxy + --pem PEM 🔑 the PEM file, if keyfile not provided + --keyfile KEYFILE 🔑 a JSON keyfile, if PEM not provided + --passfile PASSFILE 🔑 a file containing keyfile's password, if keyfile provided. If not + provided, you'll be prompted to enter the password. + --ledger 🔐 bool flag for signing transaction using ledger + --sender-wallet-index SENDER_WALLET_INDEX 🔑 the address index; can be used for PEM files, keyfiles of type + mnemonic or Ledger devices (default: 0) + --sender-username SENDER_USERNAME 🖄 the username of the sender + --nonce NONCE # the nonce for the transaction + --recall-nonce ⭮ whether to recall the nonce when creating the transaction (default: + False) + --gas-price GAS_PRICE ⛽ the gas price (default: 1000000000) + --gas-limit GAS_LIMIT ⛽ the gas limit + --value VALUE the value to transfer (default: 0) + --chain CHAIN the chain identifier + --version VERSION the transaction version (default: 2) + --options OPTIONS the transaction options (default: 0) + --relayer RELAYER the bech32 address of the relayer + --guardian GUARDIAN the bech32 address of the guardian + --send ✓ whether to broadcast the transaction (default: False) + --simulate whether to simulate the transaction (default: False) + --outfile OUTFILE where to save the output (signed transaction, hash) (default: stdout) + --guardian-service-url GUARDIAN_SERVICE_URL the url of the guardian service + --guardian-2fa-code GUARDIAN_2FA_CODE the 2fa code for the guardian + --guardian-pem GUARDIAN_PEM 🔑 the PEM file, if keyfile not provided + --guardian-keyfile GUARDIAN_KEYFILE 🔑 a JSON keyfile, if PEM not provided + --guardian-passfile GUARDIAN_PASSFILE 🔑 a file containing keyfile's password, if keyfile provided. If not + provided, you'll be prompted to enter the password. + --guardian-ledger 🔐 bool flag for signing transaction using ledger + --guardian-wallet-index GUARDIAN_WALLET_INDEX 🔑 the address index; can be used for PEM files, keyfiles of type + mnemonic or Ledger devices (default: 0) + --relayer-pem RELAYER_PEM 🔑 the PEM file, if keyfile not provided + --relayer-keyfile RELAYER_KEYFILE 🔑 a JSON keyfile, if PEM not provided + --relayer-passfile RELAYER_PASSFILE 🔑 a file containing keyfile's password, if keyfile provided. If not + provided, you'll be prompted to enter the password. + --relayer-ledger 🔐 bool flag for signing transaction using ledger + --relayer-wallet-index RELAYER_WALLET_INDEX 🔑 the address index; can be used for PEM files, keyfiles of type + mnemonic or Ledger devices (default: 0) ``` ### StakingProvider.AutomaticActivation @@ -1668,54 +2156,49 @@ usage: mxpy staking-provider automatic-activation [-h] ... Automatic activation must be called by the contract owner options: - -h, --help show this help message and exit - --set set automatic activation True - --unset set automatic activation False - --delegation-contract DELEGATION_CONTRACT address of the delegation contract - --proxy PROXY 🔗 the URL of the proxy - --pem PEM 🔑 the PEM file, if keyfile not provided - --pem-index PEM_INDEX 🔑 the index in the PEM file (default: 0) - --keyfile KEYFILE 🔑 a JSON keyfile, if PEM not provided - --passfile PASSFILE 🔑 a file containing keyfile's password, if keyfile provided - --ledger 🔐 bool flag for signing transaction using ledger - --ledger-account-index LEDGER_ACCOUNT_INDEX 🔐 the index of the account when using Ledger - --ledger-address-index LEDGER_ADDRESS_INDEX 🔐 the index of the address when using Ledger - --sender-username SENDER_USERNAME 🖄 the username of the sender - --nonce NONCE # the nonce for the transaction - --recall-nonce ⭮ whether to recall the nonce when creating the transaction (default: - False) - --gas-price GAS_PRICE ⛽ the gas price (default: 1000000000) - --gas-limit GAS_LIMIT ⛽ the gas limit - --estimate-gas ⛽ whether to estimate the gas limit (default: 0) - --value VALUE the value to transfer (default: 0) - --chain CHAIN the chain identifier - --version VERSION the transaction version (default: 2) - --relayer RELAYER the bech32 address of the relayer - --relayer-pem RELAYER_PEM 🔑 the PEM file, if keyfile not provided - --relayer-pem-index RELAYER_PEM_INDEX 🔑 the index in the PEM file (default: 0) - --relayer-keyfile RELAYER_KEYFILE 🔑 a JSON keyfile, if PEM not provided - --relayer-passfile RELAYER_PASSFILE 🔑 a file containing keyfile's password, if keyfile provided - --relayer-ledger 🔐 bool flag for signing transaction using ledger - --relayer-ledger-account-index RELAYER_LEDGER_ACCOUNT_INDEX - 🔐 the index of the account when using Ledger - --relayer-ledger-address-index RELAYER_LEDGER_ADDRESS_INDEX - 🔐 the index of the address when using Ledger - --guardian GUARDIAN the address of the guradian - --guardian-service-url GUARDIAN_SERVICE_URL the url of the guardian service - --guardian-2fa-code GUARDIAN_2FA_CODE the 2fa code for the guardian - --options OPTIONS the transaction options (default: 0) - --send ✓ whether to broadcast the transaction (default: False) - --simulate whether to simulate the transaction (default: False) - --outfile OUTFILE where to save the output (signed transaction, hash) (default: stdout) - --guardian-pem GUARDIAN_PEM 🔑 the PEM file, if keyfile not provided - --guardian-pem-index GUARDIAN_PEM_INDEX 🔑 the index in the PEM file (default: 0) - --guardian-keyfile GUARDIAN_KEYFILE 🔑 a JSON keyfile, if PEM not provided - --guardian-passfile GUARDIAN_PASSFILE 🔑 a file containing keyfile's password, if keyfile provided - --guardian-ledger 🔐 bool flag for signing transaction using ledger - --guardian-ledger-account-index GUARDIAN_LEDGER_ACCOUNT_INDEX - 🔐 the index of the account when using Ledger - --guardian-ledger-address-index GUARDIAN_LEDGER_ADDRESS_INDEX - 🔐 the index of the address when using Ledger + -h, --help show this help message and exit + --set set automatic activation True + --unset set automatic activation False + --delegation-contract DELEGATION_CONTRACT address of the delegation contract + --proxy PROXY 🔗 the URL of the proxy + --pem PEM 🔑 the PEM file, if keyfile not provided + --keyfile KEYFILE 🔑 a JSON keyfile, if PEM not provided + --passfile PASSFILE 🔑 a file containing keyfile's password, if keyfile provided. If not + provided, you'll be prompted to enter the password. + --ledger 🔐 bool flag for signing transaction using ledger + --sender-wallet-index SENDER_WALLET_INDEX 🔑 the address index; can be used for PEM files, keyfiles of type + mnemonic or Ledger devices (default: 0) + --sender-username SENDER_USERNAME 🖄 the username of the sender + --nonce NONCE # the nonce for the transaction + --recall-nonce ⭮ whether to recall the nonce when creating the transaction (default: + False) + --gas-price GAS_PRICE ⛽ the gas price (default: 1000000000) + --gas-limit GAS_LIMIT ⛽ the gas limit + --value VALUE the value to transfer (default: 0) + --chain CHAIN the chain identifier + --version VERSION the transaction version (default: 2) + --options OPTIONS the transaction options (default: 0) + --relayer RELAYER the bech32 address of the relayer + --guardian GUARDIAN the bech32 address of the guardian + --send ✓ whether to broadcast the transaction (default: False) + --simulate whether to simulate the transaction (default: False) + --outfile OUTFILE where to save the output (signed transaction, hash) (default: stdout) + --guardian-service-url GUARDIAN_SERVICE_URL the url of the guardian service + --guardian-2fa-code GUARDIAN_2FA_CODE the 2fa code for the guardian + --guardian-pem GUARDIAN_PEM 🔑 the PEM file, if keyfile not provided + --guardian-keyfile GUARDIAN_KEYFILE 🔑 a JSON keyfile, if PEM not provided + --guardian-passfile GUARDIAN_PASSFILE 🔑 a file containing keyfile's password, if keyfile provided. If not + provided, you'll be prompted to enter the password. + --guardian-ledger 🔐 bool flag for signing transaction using ledger + --guardian-wallet-index GUARDIAN_WALLET_INDEX 🔑 the address index; can be used for PEM files, keyfiles of type + mnemonic or Ledger devices (default: 0) + --relayer-pem RELAYER_PEM 🔑 the PEM file, if keyfile not provided + --relayer-keyfile RELAYER_KEYFILE 🔑 a JSON keyfile, if PEM not provided + --relayer-passfile RELAYER_PASSFILE 🔑 a file containing keyfile's password, if keyfile provided. If not + provided, you'll be prompted to enter the password. + --relayer-ledger 🔐 bool flag for signing transaction using ledger + --relayer-wallet-index RELAYER_WALLET_INDEX 🔑 the address index; can be used for PEM files, keyfiles of type + mnemonic or Ledger devices (default: 0) ``` ### StakingProvider.RedelegateCap @@ -1728,54 +2211,49 @@ usage: mxpy staking-provider redelegate-cap [-h] ... Redelegate cap must be called by the contract owner options: - -h, --help show this help message and exit - --set set redelegate cap True - --unset set redelegate cap False - --delegation-contract DELEGATION_CONTRACT address of the delegation contract - --proxy PROXY 🔗 the URL of the proxy - --pem PEM 🔑 the PEM file, if keyfile not provided - --pem-index PEM_INDEX 🔑 the index in the PEM file (default: 0) - --keyfile KEYFILE 🔑 a JSON keyfile, if PEM not provided - --passfile PASSFILE 🔑 a file containing keyfile's password, if keyfile provided - --ledger 🔐 bool flag for signing transaction using ledger - --ledger-account-index LEDGER_ACCOUNT_INDEX 🔐 the index of the account when using Ledger - --ledger-address-index LEDGER_ADDRESS_INDEX 🔐 the index of the address when using Ledger - --sender-username SENDER_USERNAME 🖄 the username of the sender - --nonce NONCE # the nonce for the transaction - --recall-nonce ⭮ whether to recall the nonce when creating the transaction (default: - False) - --gas-price GAS_PRICE ⛽ the gas price (default: 1000000000) - --gas-limit GAS_LIMIT ⛽ the gas limit - --estimate-gas ⛽ whether to estimate the gas limit (default: 0) - --value VALUE the value to transfer (default: 0) - --chain CHAIN the chain identifier - --version VERSION the transaction version (default: 2) - --relayer RELAYER the bech32 address of the relayer - --relayer-pem RELAYER_PEM 🔑 the PEM file, if keyfile not provided - --relayer-pem-index RELAYER_PEM_INDEX 🔑 the index in the PEM file (default: 0) - --relayer-keyfile RELAYER_KEYFILE 🔑 a JSON keyfile, if PEM not provided - --relayer-passfile RELAYER_PASSFILE 🔑 a file containing keyfile's password, if keyfile provided - --relayer-ledger 🔐 bool flag for signing transaction using ledger - --relayer-ledger-account-index RELAYER_LEDGER_ACCOUNT_INDEX - 🔐 the index of the account when using Ledger - --relayer-ledger-address-index RELAYER_LEDGER_ADDRESS_INDEX - 🔐 the index of the address when using Ledger - --guardian GUARDIAN the address of the guradian - --guardian-service-url GUARDIAN_SERVICE_URL the url of the guardian service - --guardian-2fa-code GUARDIAN_2FA_CODE the 2fa code for the guardian - --options OPTIONS the transaction options (default: 0) - --send ✓ whether to broadcast the transaction (default: False) - --simulate whether to simulate the transaction (default: False) - --outfile OUTFILE where to save the output (signed transaction, hash) (default: stdout) - --guardian-pem GUARDIAN_PEM 🔑 the PEM file, if keyfile not provided - --guardian-pem-index GUARDIAN_PEM_INDEX 🔑 the index in the PEM file (default: 0) - --guardian-keyfile GUARDIAN_KEYFILE 🔑 a JSON keyfile, if PEM not provided - --guardian-passfile GUARDIAN_PASSFILE 🔑 a file containing keyfile's password, if keyfile provided - --guardian-ledger 🔐 bool flag for signing transaction using ledger - --guardian-ledger-account-index GUARDIAN_LEDGER_ACCOUNT_INDEX - 🔐 the index of the account when using Ledger - --guardian-ledger-address-index GUARDIAN_LEDGER_ADDRESS_INDEX - 🔐 the index of the address when using Ledger + -h, --help show this help message and exit + --set set redelegate cap True + --unset set redelegate cap False + --delegation-contract DELEGATION_CONTRACT address of the delegation contract + --proxy PROXY 🔗 the URL of the proxy + --pem PEM 🔑 the PEM file, if keyfile not provided + --keyfile KEYFILE 🔑 a JSON keyfile, if PEM not provided + --passfile PASSFILE 🔑 a file containing keyfile's password, if keyfile provided. If not + provided, you'll be prompted to enter the password. + --ledger 🔐 bool flag for signing transaction using ledger + --sender-wallet-index SENDER_WALLET_INDEX 🔑 the address index; can be used for PEM files, keyfiles of type + mnemonic or Ledger devices (default: 0) + --sender-username SENDER_USERNAME 🖄 the username of the sender + --nonce NONCE # the nonce for the transaction + --recall-nonce ⭮ whether to recall the nonce when creating the transaction (default: + False) + --gas-price GAS_PRICE ⛽ the gas price (default: 1000000000) + --gas-limit GAS_LIMIT ⛽ the gas limit + --value VALUE the value to transfer (default: 0) + --chain CHAIN the chain identifier + --version VERSION the transaction version (default: 2) + --options OPTIONS the transaction options (default: 0) + --relayer RELAYER the bech32 address of the relayer + --guardian GUARDIAN the bech32 address of the guardian + --send ✓ whether to broadcast the transaction (default: False) + --simulate whether to simulate the transaction (default: False) + --outfile OUTFILE where to save the output (signed transaction, hash) (default: stdout) + --guardian-service-url GUARDIAN_SERVICE_URL the url of the guardian service + --guardian-2fa-code GUARDIAN_2FA_CODE the 2fa code for the guardian + --guardian-pem GUARDIAN_PEM 🔑 the PEM file, if keyfile not provided + --guardian-keyfile GUARDIAN_KEYFILE 🔑 a JSON keyfile, if PEM not provided + --guardian-passfile GUARDIAN_PASSFILE 🔑 a file containing keyfile's password, if keyfile provided. If not + provided, you'll be prompted to enter the password. + --guardian-ledger 🔐 bool flag for signing transaction using ledger + --guardian-wallet-index GUARDIAN_WALLET_INDEX 🔑 the address index; can be used for PEM files, keyfiles of type + mnemonic or Ledger devices (default: 0) + --relayer-pem RELAYER_PEM 🔑 the PEM file, if keyfile not provided + --relayer-keyfile RELAYER_KEYFILE 🔑 a JSON keyfile, if PEM not provided + --relayer-passfile RELAYER_PASSFILE 🔑 a file containing keyfile's password, if keyfile provided. If not + provided, you'll be prompted to enter the password. + --relayer-ledger 🔐 bool flag for signing transaction using ledger + --relayer-wallet-index RELAYER_WALLET_INDEX 🔑 the address index; can be used for PEM files, keyfiles of type + mnemonic or Ledger devices (default: 0) ``` ### StakingProvider.SetMetadata @@ -1788,95 +2266,106 @@ usage: mxpy staking-provider set-metadata [-h] ... Set metadata must be called by the contract owner options: - -h, --help show this help message and exit - --name NAME name field in staking provider metadata - --website WEBSITE website field in staking provider metadata - --identifier IDENTIFIER identifier field in staking provider metadata - --delegation-contract DELEGATION_CONTRACT address of the delegation contract - --proxy PROXY 🔗 the URL of the proxy - --pem PEM 🔑 the PEM file, if keyfile not provided - --pem-index PEM_INDEX 🔑 the index in the PEM file (default: 0) - --keyfile KEYFILE 🔑 a JSON keyfile, if PEM not provided - --passfile PASSFILE 🔑 a file containing keyfile's password, if keyfile provided - --ledger 🔐 bool flag for signing transaction using ledger - --ledger-account-index LEDGER_ACCOUNT_INDEX 🔐 the index of the account when using Ledger - --ledger-address-index LEDGER_ADDRESS_INDEX 🔐 the index of the address when using Ledger - --sender-username SENDER_USERNAME 🖄 the username of the sender - --nonce NONCE # the nonce for the transaction - --recall-nonce ⭮ whether to recall the nonce when creating the transaction (default: - False) - --gas-price GAS_PRICE ⛽ the gas price (default: 1000000000) - --gas-limit GAS_LIMIT ⛽ the gas limit - --estimate-gas ⛽ whether to estimate the gas limit (default: 0) - --value VALUE the value to transfer (default: 0) - --chain CHAIN the chain identifier - --version VERSION the transaction version (default: 2) - --relayer RELAYER the bech32 address of the relayer - --relayer-pem RELAYER_PEM 🔑 the PEM file, if keyfile not provided - --relayer-pem-index RELAYER_PEM_INDEX 🔑 the index in the PEM file (default: 0) - --relayer-keyfile RELAYER_KEYFILE 🔑 a JSON keyfile, if PEM not provided - --relayer-passfile RELAYER_PASSFILE 🔑 a file containing keyfile's password, if keyfile provided - --relayer-ledger 🔐 bool flag for signing transaction using ledger - --relayer-ledger-account-index RELAYER_LEDGER_ACCOUNT_INDEX - 🔐 the index of the account when using Ledger - --relayer-ledger-address-index RELAYER_LEDGER_ADDRESS_INDEX - 🔐 the index of the address when using Ledger - --guardian GUARDIAN the address of the guradian - --guardian-service-url GUARDIAN_SERVICE_URL the url of the guardian service - --guardian-2fa-code GUARDIAN_2FA_CODE the 2fa code for the guardian - --options OPTIONS the transaction options (default: 0) - --send ✓ whether to broadcast the transaction (default: False) - --simulate whether to simulate the transaction (default: False) - --outfile OUTFILE where to save the output (signed transaction, hash) (default: stdout) - --guardian-pem GUARDIAN_PEM 🔑 the PEM file, if keyfile not provided - --guardian-pem-index GUARDIAN_PEM_INDEX 🔑 the index in the PEM file (default: 0) - --guardian-keyfile GUARDIAN_KEYFILE 🔑 a JSON keyfile, if PEM not provided - --guardian-passfile GUARDIAN_PASSFILE 🔑 a file containing keyfile's password, if keyfile provided - --guardian-ledger 🔐 bool flag for signing transaction using ledger - --guardian-ledger-account-index GUARDIAN_LEDGER_ACCOUNT_INDEX - 🔐 the index of the account when using Ledger - --guardian-ledger-address-index GUARDIAN_LEDGER_ADDRESS_INDEX - 🔐 the index of the address when using Ledger - -``` -## Group **Account** - - -``` -$ mxpy account --help -usage: mxpy account COMMAND [-h] ... - -Get Account data (nonce, balance) from the Network - -COMMANDS: - {get} - -OPTIONS: - -h, --help show this help message and exit - ----------------- -COMMANDS summary ----------------- -get Query account details (nonce, balance etc.) - -``` -### Account.Get - - -``` -$ mxpy account get --help -usage: mxpy account get [-h] ... - -Query account details (nonce, balance etc.) + -h, --help show this help message and exit + --name NAME name field in staking provider metadata + --website WEBSITE website field in staking provider metadata + --identifier IDENTIFIER identifier field in staking provider metadata + --delegation-contract DELEGATION_CONTRACT address of the delegation contract + --proxy PROXY 🔗 the URL of the proxy + --pem PEM 🔑 the PEM file, if keyfile not provided + --keyfile KEYFILE 🔑 a JSON keyfile, if PEM not provided + --passfile PASSFILE 🔑 a file containing keyfile's password, if keyfile provided. If not + provided, you'll be prompted to enter the password. + --ledger 🔐 bool flag for signing transaction using ledger + --sender-wallet-index SENDER_WALLET_INDEX 🔑 the address index; can be used for PEM files, keyfiles of type + mnemonic or Ledger devices (default: 0) + --sender-username SENDER_USERNAME 🖄 the username of the sender + --nonce NONCE # the nonce for the transaction + --recall-nonce ⭮ whether to recall the nonce when creating the transaction (default: + False) + --gas-price GAS_PRICE ⛽ the gas price (default: 1000000000) + --gas-limit GAS_LIMIT ⛽ the gas limit + --value VALUE the value to transfer (default: 0) + --chain CHAIN the chain identifier + --version VERSION the transaction version (default: 2) + --options OPTIONS the transaction options (default: 0) + --relayer RELAYER the bech32 address of the relayer + --guardian GUARDIAN the bech32 address of the guardian + --send ✓ whether to broadcast the transaction (default: False) + --simulate whether to simulate the transaction (default: False) + --outfile OUTFILE where to save the output (signed transaction, hash) (default: stdout) + --guardian-service-url GUARDIAN_SERVICE_URL the url of the guardian service + --guardian-2fa-code GUARDIAN_2FA_CODE the 2fa code for the guardian + --guardian-pem GUARDIAN_PEM 🔑 the PEM file, if keyfile not provided + --guardian-keyfile GUARDIAN_KEYFILE 🔑 a JSON keyfile, if PEM not provided + --guardian-passfile GUARDIAN_PASSFILE 🔑 a file containing keyfile's password, if keyfile provided. If not + provided, you'll be prompted to enter the password. + --guardian-ledger 🔐 bool flag for signing transaction using ledger + --guardian-wallet-index GUARDIAN_WALLET_INDEX 🔑 the address index; can be used for PEM files, keyfiles of type + mnemonic or Ledger devices (default: 0) + --relayer-pem RELAYER_PEM 🔑 the PEM file, if keyfile not provided + --relayer-keyfile RELAYER_KEYFILE 🔑 a JSON keyfile, if PEM not provided + --relayer-passfile RELAYER_PASSFILE 🔑 a file containing keyfile's password, if keyfile provided. If not + provided, you'll be prompted to enter the password. + --relayer-ledger 🔐 bool flag for signing transaction using ledger + --relayer-wallet-index RELAYER_WALLET_INDEX 🔑 the address index; can be used for PEM files, keyfiles of type + mnemonic or Ledger devices (default: 0) + +``` +### StakingProvider.MakeDelegationContractFromValidator + + +``` +$ mxpy staking-provider make-delegation-contract-from-validator --help +usage: mxpy staking-provider make-delegation-contract-from-validator [-h] ... + +Create a delegation contract from validator data. Must be called by the node operator options: - -h, --help show this help message and exit - --proxy PROXY 🔗 the URL of the proxy - --address ADDRESS 🖄 the address to query - --balance whether to only fetch the balance - --nonce whether to only fetch the nonce - --username whether to only fetch the username - --omit-fields OMIT_FIELDS omit fields in the output payload (default: []) + -h, --help show this help message and exit + --max-cap MAX_CAP total delegation cap in EGLD, fully denominated. Use value 0 for + uncapped + --fee FEE service fee as hundredths of percents. (e.g. a service fee of 37.45 + percent is expressed by the integer 3745) + --proxy PROXY 🔗 the URL of the proxy + --pem PEM 🔑 the PEM file, if keyfile not provided + --keyfile KEYFILE 🔑 a JSON keyfile, if PEM not provided + --passfile PASSFILE 🔑 a file containing keyfile's password, if keyfile provided. If not + provided, you'll be prompted to enter the password. + --ledger 🔐 bool flag for signing transaction using ledger + --sender-wallet-index SENDER_WALLET_INDEX 🔑 the address index; can be used for PEM files, keyfiles of type + mnemonic or Ledger devices (default: 0) + --sender-username SENDER_USERNAME 🖄 the username of the sender + --nonce NONCE # the nonce for the transaction + --recall-nonce ⭮ whether to recall the nonce when creating the transaction (default: + False) + --gas-price GAS_PRICE ⛽ the gas price (default: 1000000000) + --gas-limit GAS_LIMIT ⛽ the gas limit + --value VALUE the value to transfer (default: 0) + --chain CHAIN the chain identifier + --version VERSION the transaction version (default: 2) + --options OPTIONS the transaction options (default: 0) + --relayer RELAYER the bech32 address of the relayer + --guardian GUARDIAN the bech32 address of the guardian + --send ✓ whether to broadcast the transaction (default: False) + --simulate whether to simulate the transaction (default: False) + --outfile OUTFILE where to save the output (signed transaction, hash) (default: stdout) + --guardian-service-url GUARDIAN_SERVICE_URL the url of the guardian service + --guardian-2fa-code GUARDIAN_2FA_CODE the 2fa code for the guardian + --guardian-pem GUARDIAN_PEM 🔑 the PEM file, if keyfile not provided + --guardian-keyfile GUARDIAN_KEYFILE 🔑 a JSON keyfile, if PEM not provided + --guardian-passfile GUARDIAN_PASSFILE 🔑 a file containing keyfile's password, if keyfile provided. If not + provided, you'll be prompted to enter the password. + --guardian-ledger 🔐 bool flag for signing transaction using ledger + --guardian-wallet-index GUARDIAN_WALLET_INDEX 🔑 the address index; can be used for PEM files, keyfiles of type + mnemonic or Ledger devices (default: 0) + --relayer-pem RELAYER_PEM 🔑 the PEM file, if keyfile not provided + --relayer-keyfile RELAYER_KEYFILE 🔑 a JSON keyfile, if PEM not provided + --relayer-passfile RELAYER_PASSFILE 🔑 a file containing keyfile's password, if keyfile provided. If not + provided, you'll be prompted to enter the password. + --relayer-ledger 🔐 bool flag for signing transaction using ledger + --relayer-wallet-index RELAYER_WALLET_INDEX 🔑 the address index; can be used for PEM files, keyfiles of type + mnemonic or Ledger devices (default: 0) ``` ## Group **Wallet** @@ -1964,6 +2453,7 @@ options: -h, --help show this help message and exit --encode whether to encode --decode whether to decode + --hrp HRP the human readable part; only used for encoding to bech32 (default: erd) ``` ### Wallet.SignMessage @@ -1976,16 +2466,16 @@ usage: mxpy wallet sign-message [-h] ... Sign a message options: - -h, --help show this help message and exit - --message MESSAGE the message you want to sign - --pem PEM 🔑 the PEM file, if keyfile not provided - --pem-index PEM_INDEX 🔑 the index in the PEM file (default: 0) - --keyfile KEYFILE 🔑 a JSON keyfile, if PEM not provided - --passfile PASSFILE 🔑 a file containing keyfile's password, if keyfile provided - --ledger 🔐 bool flag for signing transaction using ledger - --ledger-account-index LEDGER_ACCOUNT_INDEX 🔐 the index of the account when using Ledger - --ledger-address-index LEDGER_ADDRESS_INDEX 🔐 the index of the address when using Ledger - --sender-username SENDER_USERNAME 🖄 the username of the sender + -h, --help show this help message and exit + --message MESSAGE the message you want to sign + --pem PEM 🔑 the PEM file, if keyfile not provided + --keyfile KEYFILE 🔑 a JSON keyfile, if PEM not provided + --passfile PASSFILE 🔑 a file containing keyfile's password, if keyfile provided. If not + provided, you'll be prompted to enter the password. + --ledger 🔐 bool flag for signing transaction using ledger + --sender-wallet-index SENDER_WALLET_INDEX 🔑 the address index; can be used for PEM files, keyfiles of type mnemonic + or Ledger devices (default: 0) + --sender-username SENDER_USERNAME 🖄 the username of the sender ``` ### Wallet.VerifyMessage @@ -2003,6 +2493,91 @@ options: --message MESSAGE the previously signed message(readable text, as it was signed) --signature SIGNATURE the signature in hex format +``` +## Group **ValidatorWallet** + + +``` +$ mxpy validator-wallet --help +usage: mxpy validator-wallet COMMAND [-h] ... + +Create a validator wallet, sign and verify messages and convert a validator wallet to a hex secret key. + +COMMANDS: + {new,sign-message,verify-message-signature,convert} + +OPTIONS: + -h, --help show this help message and exit + +---------------- +COMMANDS summary +---------------- +new Create a new validator wallet and save it as a PEM file. +sign-message Sign a message. +verify-message-signature Verify a previously signed message. +convert Convert a validator pem file to a hex secret key. + +``` +### Wallet.New + + +``` +$ mxpy validator-wallet new --help +usage: mxpy validator-wallet new [-h] ... + +Create a new validator wallet and save it as a PEM file. + +options: + -h, --help show this help message and exit + --outfile OUTFILE the output path and file name for the generated wallet + +``` +### Wallet.Convert + + +``` +$ mxpy validator-wallet convert --help +usage: mxpy validator-wallet convert [-h] ... + +Convert a validator pem file to a hex secret key. + +options: + -h, --help show this help message and exit + --infile INFILE the pem file of the wallet + --index INDEX the index of the validator in case the file contains multiple validators (default: 0) + +``` +### Wallet.SignMessage + + +``` +$ mxpy validator-wallet sign-message --help +usage: mxpy validator-wallet sign-message [-h] ... + +Sign a message. + +options: + -h, --help show this help message and exit + --message MESSAGE the message you want to sign + --pem PEM the path to a validator pem file + --index INDEX the index of the validator in case the file contains multiple validators (default: 0) + +``` +### Wallet.VerifyMessage + + +``` +$ mxpy validator-wallet verify-message-signature --help +usage: mxpy validator-wallet verify-message-signature [-h] ... + +Verify a previously signed message. + +options: + -h, --help show this help message and exit + --pubkey PUBKEY the hex string representing the validator's public key + --message MESSAGE the previously signed message(readable text, as it was signed) + --signature SIGNATURE the signature in hex format + ``` ## Group **Localnet** @@ -2153,11 +2728,11 @@ usage: mxpy deps install [-h] ... Install dependencies or multiversx-sdk modules. positional arguments: - {all,rust,golang,testwallets} the dependency to install + {all,golang,testwallets} the dependency to install options: - -h, --help show this help message and exit - --overwrite whether to overwrite an existing installation + -h, --help show this help message and exit + --overwrite whether to overwrite an existing installation ``` ### Dependencies.Check @@ -2170,10 +2745,10 @@ usage: mxpy deps check [-h] ... Check whether a dependency is installed. positional arguments: - {all,rust,golang,testwallets} the dependency to check + {all,golang,testwallets} the dependency to check options: - -h, --help show this help message and exit + -h, --help show this help message and exit ``` ## Group **Configuration** @@ -2412,15 +2987,15 @@ usage: mxpy faucet request [-h] ... Request xEGLD. options: - -h, --help show this help message and exit - --pem PEM 🔑 the PEM file, if keyfile not provided - --pem-index PEM_INDEX 🔑 the index in the PEM file (default: 0) - --keyfile KEYFILE 🔑 a JSON keyfile, if PEM not provided - --passfile PASSFILE 🔑 a file containing keyfile's password, if keyfile provided - --ledger 🔐 bool flag for signing transaction using ledger - --ledger-account-index LEDGER_ACCOUNT_INDEX 🔐 the index of the account when using Ledger - --ledger-address-index LEDGER_ADDRESS_INDEX 🔐 the index of the address when using Ledger - --sender-username SENDER_USERNAME 🖄 the username of the sender - --chain CHAIN the chain identifier + -h, --help show this help message and exit + --pem PEM 🔑 the PEM file, if keyfile not provided + --keyfile KEYFILE 🔑 a JSON keyfile, if PEM not provided + --passfile PASSFILE 🔑 a file containing keyfile's password, if keyfile provided. If not + provided, you'll be prompted to enter the password. + --ledger 🔐 bool flag for signing transaction using ledger + --sender-wallet-index SENDER_WALLET_INDEX 🔑 the address index; can be used for PEM files, keyfiles of type mnemonic + or Ledger devices (default: 0) + --sender-username SENDER_USERNAME 🖄 the username of the sender + --chain {D,T} the chain identifier ``` diff --git a/CLI.md.sh b/CLI.md.sh index 8bb90601..46ebfb16 100755 --- a/CLI.md.sh +++ b/CLI.md.sh @@ -40,20 +40,18 @@ generate() { code group "Contract" "contract" - command "Contract.New" "contract new" - command "Contract.Templates" "contract templates" - command "Contract.Build" "contract build" - command "Contract.Clean" "contract clean" command "Contract.Deploy" "contract deploy" command "Contract.Call" "contract call" command "Contract.Upgrade" "contract upgrade" command "Contract.Query" "contract query" - command "Contract.Report" "contract report" + command "Contract.Verify" "contract verify" + command "Contract.ReproducibleBuild" "contract reproducible-build" group "Transactions" "tx" command "Transactions.New" "tx new" command "Transactions.Send" "tx send" - command "Transactions.Get" "tx get" + command "Transactions.Sign" "tx sign" + command "Transactions.Relay" "tx relay" group "Validator" "validator" command "Validator.Stake" "validator stake" @@ -62,6 +60,12 @@ generate() { command "Validator.Unbond" "validator unbond" command "Validator.ChangeRewardAddress" "validator change-reward-address" command "Validator.Claim" "validator claim" + command "Validator.UnstakeNodes" "validator unstake-nodes" + command "Validator.UnstakeTokens" "validator unstake-tokens" + command "Validator.UnbondNodes" "validator unbond-nodes" + command "Validator.UnbondTokens" "validator unbond-tokens" + command "Validator.CleanRegisteredData" "validator clean-registered-data" + command "Validator.RestakeUnstakedNodes" "validator restake-unstaked-nodes" group "StakingProvider" "staking-provider" command "StakingProvider.CreateNewDelegationContract" "staking-provider create-new-delegation-contract" @@ -72,14 +76,17 @@ generate() { command "StakingProvider.UnbondNodes" "staking-provider unbond-nodes" command "StakingProvider.UnstakeNodes" "staking-provider unstake-nodes" command "StakingProvider.UnjailNodes" "staking-provider unjail-nodes" + command "StakingProvider.Delegate" "staking-provider delegate" + command "StakingProvider.ClaimRewards" "staking-provider claim-rewards" + command "StakingProvider.RedelegateRewards" "staking-provider redelegate-rewards" + command "StakingProvider.Undelegate" "staking-provider undelegate" + command "StakingProvider.Withdraw" "staking-provider withdraw" command "StakingProvider.ChangeServiceFee" "staking-provider change-service-fee" command "StakingProvider.ModifyDelegationCap" "staking-provider modify-delegation-cap" command "StakingProvider.AutomaticActivation" "staking-provider automatic-activation" command "StakingProvider.RedelegateCap" "staking-provider redelegate-cap" command "StakingProvider.SetMetadata" "staking-provider set-metadata" - - group "Account" "account" - command "Account.Get" "account get" + command "StakingProvider.MakeDelegationContractFromValidator" "staking-provider make-delegation-contract-from-validator" group "Wallet" "wallet" command "Wallet.New" "wallet new" @@ -88,6 +95,12 @@ generate() { command "Wallet.SignMessage" "wallet sign-message" command "Wallet.VerifyMessage" "wallet verify-message" + group "ValidatorWallet" "validator-wallet" + command "Wallet.New" "validator-wallet new" + command "Wallet.Convert" "validator-wallet convert" + command "Wallet.SignMessage" "validator-wallet sign-message" + command "Wallet.VerifyMessage" "validator-wallet verify-message-signature" + group "Localnet" "localnet" command "Localnet.Setup" "localnet setup" command "Localnet.New" "localnet new" diff --git a/README.md b/README.md index 89d55637..e3d60aaf 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,12 @@ Install development dependencies, as well: pip install -r ./requirements-dev.txt --upgrade ``` -Above, `requirements.txt` should mirror the **dependencies** section of `setup.py`. +Allow `pre-commit` to automatically run on `git commit`: +``` +pre-commit install +``` + +Above, `requirements.txt` should mirror the **dependencies** section of `pyproject.toml`. If using VSCode, restart it or follow these steps: - `Ctrl + Shift + P` diff --git a/multiversx_sdk_cli/.vscode/settings.json b/multiversx_sdk_cli/.vscode/settings.json deleted file mode 100644 index 4ce5886a..00000000 --- a/multiversx_sdk_cli/.vscode/settings.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "python.linting.pylintEnabled": false, - "python.linting.flake8Enabled": true, - "python.linting.enabled": true, - "python.testing.unittestArgs": [ - "-v", - "-s", - "./tests", - "-p", - "test_*.py" - ], - "python.testing.pytestEnabled": false, - "python.testing.unittestEnabled": true, - "python.testing.promptToConfigure": false, - "python.analysis.extraPaths": [ - ".." - ] -} diff --git a/multiversx_sdk_cli/accounts.py b/multiversx_sdk_cli/accounts.py deleted file mode 100644 index 38060b92..00000000 --- a/multiversx_sdk_cli/accounts.py +++ /dev/null @@ -1,131 +0,0 @@ -import logging -from pathlib import Path -from typing import Any, Optional, Protocol - -from multiversx_sdk import (Address, Message, MessageComputer, - TransactionComputer, UserSigner) -from multiversx_sdk.network_providers.accounts import AccountOnNetwork - -from multiversx_sdk_cli.config import get_address_hrp -from multiversx_sdk_cli.interfaces import IAccount, IAddress, ITransaction -from multiversx_sdk_cli.ledger.config import compare_versions -from multiversx_sdk_cli.ledger.ledger_app_handler import \ - SIGN_USING_HASH_VERSION -from multiversx_sdk_cli.ledger.ledger_functions import ( - TX_HASH_SIGN_OPTIONS, TX_HASH_SIGN_VERSION, do_get_ledger_address, - do_get_ledger_version, do_sign_message_with_ledger, - do_sign_transaction_with_ledger) - -logger = logging.getLogger("accounts") - - -class INetworkProvider(Protocol): - def get_account(self, address: IAddress) -> AccountOnNetwork: - ... - - -class EmptyAddress(IAddress): - def to_hex(self) -> str: - return "" - - def to_bech32(self) -> str: - return "" - - -class AccountBase(IAccount): - def __init__(self, address: Any = EmptyAddress()) -> None: - self.address = address - self.nonce: int = 0 - - def sync_nonce(self, proxy: INetworkProvider): - logger.debug("AccountBase.sync_nonce()") - self.nonce = proxy.get_account(self.address).nonce - logger.debug(f"AccountBase.sync_nonce() done: {self.nonce}") - - def sign_transaction(self, transaction: ITransaction) -> str: - raise NotImplementedError - - def sign_message(self, data: bytes) -> str: - raise NotImplementedError - - -class Account(AccountBase): - def __init__(self, - address: Any = None, - pem_file: Optional[str] = None, - pem_index: int = 0, - key_file: str = "", - password: str = "") -> None: - super().__init__(address) - - if pem_file: - pem_path = Path(pem_file).expanduser().resolve() - self.signer = UserSigner.from_pem_file(pem_path, pem_index) - self.address = Address(self.signer.get_pubkey().buffer, get_address_hrp()) - elif key_file and password: - key_file_path = Path(key_file).expanduser().resolve() - self.signer = UserSigner.from_wallet(key_file_path, password) - self.address = Address(self.signer.get_pubkey().buffer, get_address_hrp()) - - def sign_transaction(self, transaction: ITransaction) -> str: - assert self.signer is not None - - transaction_computer = TransactionComputer() - if transaction.options & TX_HASH_SIGN_OPTIONS == TX_HASH_SIGN_OPTIONS: - return self.signer.sign(transaction_computer.compute_hash_for_signing(transaction)).hex() - - return self.signer.sign(transaction_computer.compute_bytes_for_signing(transaction)).hex() - - def sign_message(self, data: bytes) -> str: - assert self.signer is not None - message = Message(data) - message_computer = MessageComputer() - signature = self.signer.sign(message_computer.compute_bytes_for_signing(message)) - - logger.debug(f"Account.sign_message(): raw_data_to_sign = {data.hex()}, message_data_to_sign = {message_computer.compute_bytes_for_signing(message).hex()}, signature = {signature.hex()}") - return signature.hex() - - -class LedgerAccount(Account): - def __init__(self, account_index: int = 0, address_index: int = 0) -> None: - super().__init__() - self.account_index = account_index - self.address_index = address_index - self.address = Address.new_from_bech32(do_get_ledger_address(account_index=account_index, address_index=address_index)) - - def sign_transaction(self, transaction: ITransaction) -> str: - ledger_version = do_get_ledger_version() - should_use_hash_signing = compare_versions(ledger_version, SIGN_USING_HASH_VERSION) >= 0 - - # TODO: This check will be removed in the next major release. - if should_use_hash_signing: - transaction.version = TX_HASH_SIGN_VERSION - transaction.options = transaction.options | TX_HASH_SIGN_OPTIONS - - transaction_computer = TransactionComputer() - - signature = do_sign_transaction_with_ledger( - transaction_computer.compute_bytes_for_signing(transaction), - account_index=self.account_index, - address_index=self.address_index, - sign_using_hash=should_use_hash_signing - ) - - assert isinstance(signature, str) - return signature - - def sign_message(self, data: bytes) -> str: - message_length = len(data).to_bytes(4, byteorder="big") - message_data_to_sign: bytes = message_length + data - logger.debug(f"LedgerAccount.sign_message(): raw_data_to_sign = {data.hex()}, message_data_to_sign = {message_data_to_sign.hex()}") - - signature = do_sign_message_with_ledger( - message_data_to_sign, - account_index=self.account_index, - address_index=self.address_index - ) - - assert isinstance(signature, str) - - logger.debug(f"LedgerAccount.sign_message(): signature = {signature}") - return signature diff --git a/multiversx_sdk_cli/args_validation.py b/multiversx_sdk_cli/args_validation.py new file mode 100644 index 00000000..1829ccf0 --- /dev/null +++ b/multiversx_sdk_cli/args_validation.py @@ -0,0 +1,71 @@ +from typing import Any + +from multiversx_sdk_cli.errors import InvalidArgumentsError + + +def validate_transaction_args(args: Any): + validate_nonce_args(args) + validate_receiver_args(args) + validate_gas_limit_args(args) + + +def validate_nonce_args(args: Any): + """If nonce is not provided, ensure that recall_nonce is provided. If recall_nonce is provided, ensure that proxy is provided.""" + if hasattr(args, "nonce") and args.nonce is None: + if not args.recall_nonce: + raise InvalidArgumentsError("Either --nonce or --recall-nonce must be provided") + + if hasattr(args, "proxy") and not args.proxy: + raise InvalidArgumentsError("--proxy must be provided if --recall-nonce is used") + + +def validate_receiver_args(args: Any): + """Ensure that receiver is provided.""" + if hasattr(args, "receiver") and not args.receiver: + raise InvalidArgumentsError("--receiver must be provided") + + +def validate_gas_limit_args(args: Any): + """Ensure that gas_limit is provided.""" + if hasattr(args, "gas_limit") and not args.gas_limit: + raise InvalidArgumentsError("--gas-limit must be provided") + + +def ensure_wallet_args_are_provided(args: Any): + signing_methods = [args.pem, args.keyfile, args.ledger] + + if all(signing_methods): + raise InvalidArgumentsError("Only one of --pem, --keyfile, or --ledger must be provided") + + if not any(signing_methods): + raise InvalidArgumentsError("One of --pem, --keyfile, or --ledger must be provided") + + +def ensure_relayer_wallet_args_are_provided(args: Any): + signing_methods = [args.relayer_pem, args.relayer_keyfile, args.relayer_ledger] + + if all(signing_methods): + raise InvalidArgumentsError( + "Only one of --relayer-pem, --relayer-keyfile, or --relayer-ledger must be provided" + ) + + if not any(signing_methods): + raise InvalidArgumentsError("One of --relayer-pem, --relayer-keyfile, or --relayer-ledger must be provided") + + +def validate_broadcast_args(args: Any): + if args.send and args.simulate: + raise InvalidArgumentsError("Cannot both 'simulate' and 'send' a transaction") + + if args.send or args.simulate: + validate_proxy_argument(args) + + +def validate_chain_id_args(args: Any): + if not args.chain and not args.proxy: + raise InvalidArgumentsError("Either --chain or --proxy must be provided") + + +def validate_proxy_argument(args: Any): + if not args.proxy: + raise InvalidArgumentsError("--proxy must be provided") diff --git a/multiversx_sdk_cli/base_transactions_controller.py b/multiversx_sdk_cli/base_transactions_controller.py new file mode 100644 index 00000000..858d81ca --- /dev/null +++ b/multiversx_sdk_cli/base_transactions_controller.py @@ -0,0 +1,86 @@ +from typing import Optional, Union + +from multiversx_sdk import LedgerAccount, Transaction, TransactionComputer + +from multiversx_sdk_cli.constants import ( + EXTRA_GAS_LIMIT_FOR_GUARDED_TRANSACTIONS, + EXTRA_GAS_LIMIT_FOR_RELAYED_TRANSACTIONS, +) +from multiversx_sdk_cli.cosign_transaction import cosign_transaction +from multiversx_sdk_cli.interfaces import IAccount + + +class BaseTransactionsController: + def __init__(self) -> None: + pass + + def sign_transaction( + self, + transaction: Transaction, + sender: Optional[IAccount] = None, + guardian: Optional[IAccount] = None, + relayer: Optional[IAccount] = None, + guardian_service_url: Optional[str] = None, + guardian_2fa_code: Optional[str] = None, + ): + """Signs the transaction using the sender's account and, if required, additionally signs with the guardian's and relayer's accounts. Ensures the appropriate transaction options are set as needed.""" + self._set_options_for_guarded_transaction_if_needed(transaction) + self._set_options_for_hash_signing_if_needed(transaction, sender, guardian, relayer) + + if sender: + transaction.signature = sender.sign_transaction(transaction) + + self._sign_guarded_transaction_if_guardian( + transaction, + guardian, + guardian_service_url, + guardian_2fa_code, + ) + self._sign_relayed_transaction_if_relayer(transaction, relayer) + + def add_extra_gas_limit_if_required(self, transaction: Transaction): + """In case of guarded or relayed transactions, extra gas limit is added.""" + if transaction.guardian: + transaction.gas_limit += EXTRA_GAS_LIMIT_FOR_GUARDED_TRANSACTIONS + + if transaction.relayer: + transaction.gas_limit += EXTRA_GAS_LIMIT_FOR_RELAYED_TRANSACTIONS + + def _set_options_for_guarded_transaction_if_needed(self, transaction: Transaction): + if transaction.guardian: + transaction_computer = TransactionComputer() + transaction_computer.apply_guardian(transaction, transaction.guardian) + + def _set_options_for_hash_signing_if_needed( + self, + transaction: Transaction, + sender: Union[IAccount, None], + guardian: Union[IAccount, None], + relayer: Union[IAccount, None], + ): + if ( + isinstance(sender, LedgerAccount) + or isinstance(guardian, LedgerAccount) + or isinstance(relayer, LedgerAccount) + ): + transaction_computer = TransactionComputer() + transaction_computer.apply_options_for_hash_signing(transaction) + + def _sign_guarded_transaction_if_guardian( + self, + transaction: Transaction, + guardian: Union[IAccount, None], + guardian_service_url: Union[str, None], + guardian_2fa_code: Union[str, None], + ) -> Transaction: + # If the guardian account is provided, we sign locally. Otherwise, we reach for the trusted cosign service. + if guardian: + transaction.guardian_signature = guardian.sign_transaction(transaction) + elif transaction.guardian and guardian_service_url and guardian_2fa_code: + cosign_transaction(transaction, guardian_service_url, guardian_2fa_code) + + return transaction + + def _sign_relayed_transaction_if_relayer(self, transaction: Transaction, relayer: Union[IAccount, None]): + if relayer and transaction.relayer: + transaction.relayer_signature = relayer.sign_transaction(transaction) diff --git a/multiversx_sdk_cli/cli.py b/multiversx_sdk_cli/cli.py index 76fe95e1..8bf57fd8 100644 --- a/multiversx_sdk_cli/cli.py +++ b/multiversx_sdk_cli/cli.py @@ -3,13 +3,12 @@ import logging import sys from argparse import ArgumentParser -from typing import Any, List +from typing import Any import argcomplete from multiversx_sdk import LibraryConfig from rich.logging import RichHandler -import multiversx_sdk_cli.cli_accounts import multiversx_sdk_cli.cli_config import multiversx_sdk_cli.cli_contracts import multiversx_sdk_cli.cli_data @@ -20,6 +19,7 @@ import multiversx_sdk_cli.cli_ledger import multiversx_sdk_cli.cli_localnet import multiversx_sdk_cli.cli_transactions +import multiversx_sdk_cli.cli_validator_wallet import multiversx_sdk_cli.cli_validators import multiversx_sdk_cli.cli_wallet import multiversx_sdk_cli.version @@ -28,7 +28,7 @@ logger = logging.getLogger("cli") -def main(cli_args: List[str] = sys.argv[1:]): +def main(cli_args: list[str] = sys.argv[1:]): try: _do_main(cli_args) except errors.KnownError as err: @@ -41,7 +41,7 @@ def main(cli_args: List[str] = sys.argv[1:]): return 0 -def _do_main(cli_args: List[str]): +def _do_main(cli_args: list[str]): utils.ensure_folder(config.SDK_PATH) argv_with_config_args = config.add_config_args(cli_args) parser = setup_parser(argv_with_config_args) @@ -49,9 +49,18 @@ def _do_main(cli_args: List[str]): args = parser.parse_args(argv_with_config_args) if args.verbose: - logging.basicConfig(level="DEBUG", force=True, format='%(name)s: %(message)s', handlers=[RichHandler(show_time=False, rich_tracebacks=True)]) + logging.basicConfig( + level="DEBUG", + force=True, + format="%(name)s: %(message)s", + handlers=[RichHandler(show_time=False, rich_tracebacks=True)], + ) else: - logging.basicConfig(level="INFO", format='%(name)s: %(message)s', handlers=[RichHandler(show_time=False, rich_tracebacks=True)]) + logging.basicConfig( + level="INFO", + format="%(name)s: %(message)s", + handlers=[RichHandler(show_time=False, rich_tracebacks=True)], + ) verify_deprecated_entries_in_config_file() default_hrp = config.get_address_hrp() @@ -63,7 +72,7 @@ def _do_main(cli_args: List[str]): args.func(args) -def setup_parser(args: List[str]): +def setup_parser(args: list[str]): parser = ArgumentParser( prog="mxpy", usage="mxpy [-h] [-v] [--verbose] COMMAND-GROUP [-h] COMMAND ...", @@ -80,24 +89,29 @@ def setup_parser(args: List[str]): - https://docs.multiversx.com/sdk-and-tools/sdk-py - https://docs.multiversx.com/sdk-and-tools/sdk-py/mxpy-cli """, - formatter_class=argparse.RawDescriptionHelpFormatter + formatter_class=argparse.RawDescriptionHelpFormatter, ) parser._positionals.title = "COMMAND GROUPS" parser._optionals.title = "TOP-LEVEL OPTIONS" version = multiversx_sdk_cli.version.get_version() - parser.add_argument("-v", "--version", action="version", version=f"MultiversX Python CLI (mxpy) {version}") + parser.add_argument( + "-v", + "--version", + action="version", + version=f"MultiversX Python CLI (mxpy) {version}", + ) parser.add_argument("--verbose", action="store_true", default=False) subparsers = parser.add_subparsers() - commands: List[Any] = [] + commands: list[Any] = [] commands.append(multiversx_sdk_cli.cli_contracts.setup_parser(args, subparsers)) commands.append(multiversx_sdk_cli.cli_transactions.setup_parser(args, subparsers)) commands.append(multiversx_sdk_cli.cli_validators.setup_parser(args, subparsers)) - commands.append(multiversx_sdk_cli.cli_accounts.setup_parser(subparsers)) commands.append(multiversx_sdk_cli.cli_ledger.setup_parser(subparsers)) commands.append(multiversx_sdk_cli.cli_wallet.setup_parser(args, subparsers)) + commands.append(multiversx_sdk_cli.cli_validator_wallet.setup_parser(args, subparsers)) commands.append(multiversx_sdk_cli.cli_deps.setup_parser(subparsers)) commands.append(multiversx_sdk_cli.cli_config.setup_parser(subparsers)) commands.append(multiversx_sdk_cli.cli_localnet.setup_parser(args, subparsers)) @@ -112,7 +126,7 @@ def setup_parser(args: List[str]): ---------------------- """ for choice, sub in subparsers.choices.items(): - parser.epilog += (f"{choice.ljust(30)} {sub.description}\n") + parser.epilog += f"{choice.ljust(30)} {sub.description}\n" return parser @@ -127,7 +141,7 @@ def verify_deprecated_entries_in_config_file(): for entry in deprecated_keys: message += f"-> {entry} \n" - ux.show_warning(message.rstrip('\n')) + ux.show_warning(message.rstrip("\n")) if __name__ == "__main__": diff --git a/multiversx_sdk_cli/cli_accounts.py b/multiversx_sdk_cli/cli_accounts.py deleted file mode 100644 index f7a5afe5..00000000 --- a/multiversx_sdk_cli/cli_accounts.py +++ /dev/null @@ -1,48 +0,0 @@ -import logging -from typing import Any - -from multiversx_sdk import Address, ProxyNetworkProvider - -from multiversx_sdk_cli import cli_shared, utils -from multiversx_sdk_cli.config import get_config_for_network_providers - -logger = logging.getLogger("cli.accounts") - - -def setup_parser(subparsers: Any) -> Any: - parser = cli_shared.add_group_subparser(subparsers, "account", "Get Account data (nonce, balance) from the Network") - subparsers = parser.add_subparsers() - - sub = cli_shared.add_command_subparser(subparsers, "account", "get", "Query account details (nonce, balance etc.)") - cli_shared.add_proxy_arg(sub) - _add_address_arg(sub) - mutex = sub.add_mutually_exclusive_group() - mutex.add_argument("--balance", action="store_true", help="whether to only fetch the balance") - mutex.add_argument("--nonce", action="store_true", help="whether to only fetch the nonce") - mutex.add_argument("--username", action="store_true", help="whether to only fetch the username") - cli_shared.add_omit_fields_arg(sub) - sub.set_defaults(func=get_account) - - parser.epilog = cli_shared.build_group_epilog(subparsers) - return subparsers - - -def _add_address_arg(sub: Any): - sub.add_argument("--address", required=True, help="🖄 the address to query") - - -def get_account(args: Any): - proxy_url = args.proxy - address = args.address - config = get_config_for_network_providers() - proxy = ProxyNetworkProvider(url=proxy_url, config=config) - account = proxy.get_account(Address.new_from_bech32(address)) - - if args.balance: - print(account.balance) - elif args.nonce: - print(account.nonce) - elif args.username: - print(account.username) - else: - utils.dump_out_json(account.to_dictionary()) diff --git a/multiversx_sdk_cli/cli_config.py b/multiversx_sdk_cli/cli_config.py index 4e03879d..3d536af0 100644 --- a/multiversx_sdk_cli/cli_config.py +++ b/multiversx_sdk_cli/cli_config.py @@ -14,7 +14,12 @@ def setup_parser(subparsers: Any) -> Any: subparsers = parser.add_subparsers() sub = cli_shared.add_command_subparser(subparsers, "config", "dump", "Dumps configuration.") - sub.add_argument('--defaults', required=False, help='dump defaults instead of local config', action='store_true') + sub.add_argument( + "--defaults", + required=False, + help="dump defaults instead of local config", + action="store_true", + ) sub.set_defaults(func=dump) sub = cli_shared.add_command_subparser(subparsers, "config", "get", "Gets a configuration value.") @@ -32,7 +37,11 @@ def setup_parser(subparsers: Any) -> Any: sub = cli_shared.add_command_subparser(subparsers, "config", "new", "Creates a new configuration.") _add_name_arg(sub) - sub.add_argument("--template", required=False, help="template from which to create the new config") + sub.add_argument( + "--template", + required=False, + help="template from which to create the new config", + ) sub.set_defaults(func=new_config) sub = cli_shared.add_command_subparser(subparsers, "config", "switch", "Switch to a different config") @@ -42,7 +51,12 @@ def setup_parser(subparsers: Any) -> Any: sub = cli_shared.add_command_subparser(subparsers, "config", "list", "List available configs") sub.set_defaults(func=list_configs) - sub = cli_shared.add_command_subparser(subparsers, "config", "reset", "Deletes the config file. Default config will be used.") + sub = cli_shared.add_command_subparser( + subparsers, + "config", + "reset", + "Deletes the config file. Default config will be used.", + ) sub.set_defaults(func=delete_config) parser.epilog = cli_shared.build_group_epilog(subparsers) @@ -93,7 +107,7 @@ def switch_config(args: Any): def list_configs(args: Any): data = config.read_file() - configurations = data.get('configurations', {}) + configurations = data.get("configurations", {}) for config_name in configurations.keys(): if config_name == data.get("active", "default"): config_name += "*" diff --git a/multiversx_sdk_cli/cli_contracts.py b/multiversx_sdk_cli/cli_contracts.py index 9516bcf4..cb9df27c 100644 --- a/multiversx_sdk_cli/cli_contracts.py +++ b/multiversx_sdk_cli/cli_contracts.py @@ -2,78 +2,55 @@ import logging import os from pathlib import Path -from typing import Any, List, Tuple - -from multiversx_sdk import (Address, AddressComputer, ProxyNetworkProvider, - Transaction, TransactionsFactoryConfig) +from typing import Any + +from multiversx_sdk import ( + Address, + AddressComputer, + ProxyNetworkProvider, + Transaction, + TransactionsFactoryConfig, +) from multiversx_sdk.abi import Abi -from multiversx_sdk_cli import cli_shared, projects, utils +from multiversx_sdk_cli import cli_shared, utils +from multiversx_sdk_cli.args_validation import ( + ensure_wallet_args_are_provided, + validate_broadcast_args, + validate_chain_id_args, + validate_proxy_argument, + validate_transaction_args, +) from multiversx_sdk_cli.cli_output import CLIOutputBuilder from multiversx_sdk_cli.config import get_config_for_network_providers from multiversx_sdk_cli.constants import NUMBER_OF_SHARDS -from multiversx_sdk_cli.contract_verification import \ - trigger_contract_verification +from multiversx_sdk_cli.contract_verification import trigger_contract_verification from multiversx_sdk_cli.contracts import SmartContract -from multiversx_sdk_cli.cosign_transaction import cosign_transaction -from multiversx_sdk_cli.dependency_checker import check_if_rust_is_installed from multiversx_sdk_cli.docker import is_docker_installed, run_docker -from multiversx_sdk_cli.errors import DockerMissingError, NoWalletProvided -from multiversx_sdk_cli.interfaces import IAddress -from multiversx_sdk_cli.projects.core import get_project_paths_recursively -from multiversx_sdk_cli.projects.templates import Contract -from multiversx_sdk_cli.ux import show_message, show_warning +from multiversx_sdk_cli.errors import DockerMissingError +from multiversx_sdk_cli.ux import show_warning logger = logging.getLogger("cli.contracts") -def setup_parser(args: List[str], subparsers: Any) -> Any: - parser = cli_shared.add_group_subparser(subparsers, "contract", "Build, deploy, upgrade and interact with Smart Contracts") +def setup_parser(args: list[str], subparsers: Any) -> Any: + parser = cli_shared.add_group_subparser( + subparsers, + "contract", + "Deploy, upgrade and interact with Smart Contracts", + ) subparsers = parser.add_subparsers() - sub = cli_shared.add_command_subparser(subparsers, "contract", "new", - "Create a new Smart Contract project based on a template.") - sub.add_argument("--name", help="The name of the contract. If missing, the name of the template will be used.") - sub.add_argument("--template", required=True, help="the template to use") - sub.add_argument("--tag", help="the framework version on which the contract should be created") - sub.add_argument("--path", type=str, default=os.getcwd(), - help="the parent directory of the project (default: current directory)") - sub.set_defaults(func=create) - - sub = cli_shared.add_command_subparser(subparsers, "contract", "templates", - "List the available Smart Contract templates.") - sub.add_argument("--tag", help="the sc-meta framework version referred to") - sub.set_defaults(func=list_templates) - - sub = cli_shared.add_command_subparser(subparsers, "contract", "build", - "Build a Smart Contract project.") - _add_build_options_sc_meta(sub) - sub.set_defaults(func=build) + output_description = CLIOutputBuilder.describe( + with_contract=True, with_transaction_on_network=True, with_simulation=True + ) - sub = cli_shared.add_command_subparser(subparsers, "contract", "clean", "Clean a Smart Contract project.") - sub.add_argument("--path", default=os.getcwd(), help="the project directory (default: current directory)") - sub.set_defaults(func=clean) - - sub = cli_shared.add_command_subparser(subparsers, "contract", "test", "Run tests.") - sub.add_argument("--path", default=os.getcwd(), - help="the directory of the contract (default: %(default)s)") - sub.add_argument("--go", action="store_true", - help="this arg runs rust and go tests (default: false)") - sub.add_argument("--scen", action="store_true", help="this arg runs scenarios (default: false). If `--scen` and `--go` are both specified, scen overrides the go argument") - sub.add_argument("--nocapture", action="store_true", help="this arg prints the entire output of the vm (default: false)") - sub.set_defaults(func=run_tests) - - sub = cli_shared.add_command_subparser(subparsers, "contract", "report", "Print a detailed report of the smart contracts.") - sub.add_argument("--skip-build", action="store_true", default=False, help="skips the step of building of the wasm contracts") - sub.add_argument("--skip-twiggy", action="store_true", default=False, help="skips the steps of building the debug wasm files and running twiggy") - sub.add_argument("--output-format", type=str, default="text-markdown", choices=["github-markdown", "text-markdown", "json"], help="report output format (default: %(default)s)") - sub.add_argument("--output-file", type=Path, help="if specified, the output is written to a file, otherwise it's written to the standard output") - sub.add_argument("--compare", type=Path, nargs='+', metavar=("report-1.json", "report-2.json"), help="create a comparison from two or more reports") - _add_build_options_sc_meta(sub) - sub.set_defaults(func=do_report) - - output_description = CLIOutputBuilder.describe(with_contract=True, with_transaction_on_network=True, with_simulation=True) - sub = cli_shared.add_command_subparser(subparsers, "contract", "deploy", f"Deploy a Smart Contract.{output_description}") + sub = cli_shared.add_command_subparser( + subparsers, + "contract", + "deploy", + f"Deploy a Smart Contract.{output_description}", + ) _add_bytecode_arg(sub) _add_contract_abi_arg(sub) _add_metadata_arg(sub) @@ -82,17 +59,29 @@ def setup_parser(args: List[str], subparsers: Any) -> Any: cli_shared.add_proxy_arg(sub) cli_shared.add_tx_args(args, sub, with_receiver=False, with_data=False) _add_arguments_arg(sub) - sub.add_argument("--wait-result", action="store_true", default=False, - help="signal to wait for the transaction result - only valid if --send is set") - sub.add_argument("--timeout", default=100, help="max num of seconds to wait for result" - " - only valid if --wait-result is set") + sub.add_argument( + "--wait-result", + action="store_true", + default=False, + help="signal to wait for the transaction result - only valid if --send is set", + ) + sub.add_argument( + "--timeout", + default=100, + help="max num of seconds to wait for result" " - only valid if --wait-result is set", + ) cli_shared.add_broadcast_args(sub) cli_shared.add_guardian_wallet_args(args, sub) + cli_shared.add_relayed_v3_wallet_args(args, sub) sub.set_defaults(func=deploy) - sub = cli_shared.add_command_subparser(subparsers, "contract", "call", - f"Interact with a Smart Contract (execute function).{output_description}") + sub = cli_shared.add_command_subparser( + subparsers, + "contract", + "call", + f"Interact with a Smart Contract (execute function).{output_description}", + ) _add_contract_arg(sub) _add_contract_abi_arg(sub) cli_shared.add_outfile_arg(sub) @@ -102,17 +91,29 @@ def setup_parser(args: List[str], subparsers: Any) -> Any: _add_function_arg(sub) _add_arguments_arg(sub) cli_shared.add_token_transfers_args(sub) - sub.add_argument("--wait-result", action="store_true", default=False, - help="signal to wait for the transaction result - only valid if --send is set") - sub.add_argument("--timeout", default=100, help="max num of seconds to wait for result" - " - only valid if --wait-result is set") - cli_shared.add_broadcast_args(sub, relay=True) + sub.add_argument( + "--wait-result", + action="store_true", + default=False, + help="signal to wait for the transaction result - only valid if --send is set", + ) + sub.add_argument( + "--timeout", + default=100, + help="max num of seconds to wait for result" " - only valid if --wait-result is set", + ) + cli_shared.add_broadcast_args(sub) cli_shared.add_guardian_wallet_args(args, sub) + cli_shared.add_relayed_v3_wallet_args(args, sub) sub.set_defaults(func=call) - sub = cli_shared.add_command_subparser(subparsers, "contract", "upgrade", - f"Upgrade a previously-deployed Smart Contract.{output_description}") + sub = cli_shared.add_command_subparser( + subparsers, + "contract", + "upgrade", + f"Upgrade a previously-deployed Smart Contract.{output_description}", + ) _add_contract_arg(sub) _add_contract_abi_arg(sub) cli_shared.add_outfile_arg(sub) @@ -122,17 +123,26 @@ def setup_parser(args: List[str], subparsers: Any) -> Any: cli_shared.add_proxy_arg(sub) cli_shared.add_tx_args(args, sub, with_receiver=False, with_data=False) _add_arguments_arg(sub) - sub.add_argument("--wait-result", action="store_true", default=False, - help="signal to wait for the transaction result - only valid if --send is set") - sub.add_argument("--timeout", default=100, help="max num of seconds to wait for result" - " - only valid if --wait-result is set") + sub.add_argument( + "--wait-result", + action="store_true", + default=False, + help="signal to wait for the transaction result - only valid if --send is set", + ) + sub.add_argument( + "--timeout", + default=100, + help="max num of seconds to wait for result" " - only valid if --wait-result is set", + ) cli_shared.add_broadcast_args(sub) cli_shared.add_guardian_wallet_args(args, sub) + cli_shared.add_relayed_v3_wallet_args(args, sub) sub.set_defaults(func=upgrade) - sub = cli_shared.add_command_subparser(subparsers, "contract", "query", - "Query a Smart Contract (call a pure function)") + sub = cli_shared.add_command_subparser( + subparsers, "contract", "query", "Query a Smart Contract (call a pure function)" + ) _add_contract_arg(sub) _add_contract_abi_arg(sub) cli_shared.add_proxy_arg(sub) @@ -140,12 +150,17 @@ def setup_parser(args: List[str], subparsers: Any) -> Any: _add_arguments_arg(sub) sub.set_defaults(func=query) - sub = cli_shared.add_command_subparser(subparsers, "contract", "verify", - "Verify the authenticity of the code of a deployed Smart Contract", - ) + sub = cli_shared.add_command_subparser( + subparsers, + "contract", + "verify", + "Verify the authenticity of the code of a deployed Smart Contract", + ) sub.add_argument( - "--packaged-src", required=True, help="JSON file containing the source code of the contract" + "--packaged-src", + required=True, + help="JSON file containing the source code of the contract", ) _add_contract_arg(sub) @@ -155,81 +170,119 @@ def setup_parser(args: List[str], subparsers: Any) -> Any: help="the url of the service that validates the contract", ) sub.add_argument("--docker-image", required=True, help="the docker image used for the build") - sub.add_argument("--contract-variant", required=False, default=None, help="in case of a multicontract, specify the contract variant you want to verify") + sub.add_argument( + "--contract-variant", + required=False, + default=None, + help="in case of a multicontract, specify the contract variant you want to verify", + ) cli_shared.add_wallet_args(args, sub) sub.set_defaults(func=verify) - sub = cli_shared.add_command_subparser(subparsers, "contract", "reproducible-build", - "Build a Smart Contract and get the same output as a previously built Smart Contract") + sub = cli_shared.add_command_subparser( + subparsers, + "contract", + "reproducible-build", + "Build a Smart Contract and get the same output as a previously built Smart Contract", + ) _add_project_arg(sub) _add_build_options_args(sub) - sub.add_argument("--docker-image", required=True, type=str, - help="the docker image tag used to build the contract") - sub.add_argument("--contract", type=str, help="contract to build (contract name, as found in Cargo.toml)") + sub.add_argument( + "--docker-image", + required=True, + type=str, + help="the docker image tag used to build the contract", + ) + sub.add_argument( + "--contract", + type=str, + help="contract to build (contract name, as found in Cargo.toml)", + ) sub.add_argument("--no-docker-interactive", action="store_true", default=False) sub.add_argument("--no-docker-tty", action="store_true", default=False) - sub.add_argument("--no-default-platform", action="store_true", default=False, - help="do not set DOCKER_DEFAULT_PLATFORM environment variable to 'linux/amd64'") + sub.add_argument( + "--no-default-platform", + action="store_true", + default=False, + help="do not set DOCKER_DEFAULT_PLATFORM environment variable to 'linux/amd64'", + ) sub.set_defaults(func=do_reproducible_build) + sub = cli_shared.add_command_subparser( + subparsers, "contract", "build", "Build a Smart Contract project. This command is DISABLED." + ) + sub.set_defaults(func=build) + parser.epilog = cli_shared.build_group_epilog(subparsers) return subparsers def _add_project_arg(sub: Any): - sub.add_argument("project", nargs='?', default=os.getcwd(), - help="the project directory (default: current directory)") - - -def _add_build_options_sc_meta(sub: Any): - sub.add_argument("--path", default=os.getcwd(), help="the project directory (default: current directory)") - sub.add_argument("--no-wasm-opt", action="store_true", default=False, - help="do not optimize wasm files after the build (default: %(default)s)") - sub.add_argument("--wasm-symbols", action="store_true", default=False, - help="for rust projects, does not strip the symbols from the wasm output. Useful for analysing the bytecode. Creates larger wasm files. Avoid in production (default: %(default)s)") - sub.add_argument("--wasm-name", type=str, - help="for rust projects, optionally specify the name of the wasm bytecode output file") - sub.add_argument("--wasm-suffix", type=str, - help="for rust projects, optionally specify the suffix of the wasm bytecode output file") - sub.add_argument("--target-dir", type=str, help="for rust projects, forward the parameter to Cargo") - sub.add_argument("--wat", action="store_true", help="also generate a WAT file when building", default=False) - sub.add_argument("--mir", action="store_true", help="also emit MIR files when building", default=False) - sub.add_argument("--llvm-ir", action="store_true", help="also emit LL (LLVM) files when building", default=False) - sub.add_argument("--ignore", help="ignore all directories with these names. [default: target]") - sub.add_argument("--no-imports", action="store_true", default=False, help="skips extracting the EI imports after building the contracts") - sub.add_argument("--no-abi-git-version", action="store_true", default=False, help="skips loading the Git version into the ABI") - sub.add_argument("--twiggy-top", action="store_true", default=False, help="generate a twiggy top report after building") - sub.add_argument("--twiggy-paths", action="store_true", default=False, help="generate a twiggy paths report after building") - sub.add_argument("--twiggy-monos", action="store_true", default=False, help="generate a twiggy monos report after building") - sub.add_argument("--twiggy-dominators", action="store_true", default=False, help="generate a twiggy dominators report after building") + sub.add_argument( + "project", + nargs="?", + default=os.getcwd(), + help="the project directory (default: current directory)", + ) def _add_build_options_args(sub: Any): - sub.add_argument("--debug", action="store_true", default=False, help="set debug flag (default: %(default)s)") - sub.add_argument("--no-optimization", action="store_true", default=False, - help="bypass optimizations (for clang) (default: %(default)s)") - sub.add_argument("--no-wasm-opt", action="store_true", default=False, - help="do not optimize wasm files after the build (default: %(default)s)") - sub.add_argument("--cargo-target-dir", type=str, help="for rust projects, forward the parameter to Cargo") - sub.add_argument("--wasm-symbols", action="store_true", default=False, - help="for rust projects, does not strip the symbols from the wasm output. Useful for analysing the bytecode. Creates larger wasm files. Avoid in production (default: %(default)s)") - sub.add_argument("--wasm-name", type=str, - help="for rust projects, optionally specify the name of the wasm bytecode output file") - sub.add_argument("--wasm-suffix", type=str, - help="for rust projects, optionally specify the suffix of the wasm bytecode output file") + sub.add_argument( + "--debug", + action="store_true", + default=False, + help="set debug flag (default: %(default)s)", + ) + sub.add_argument( + "--no-optimization", + action="store_true", + default=False, + help="bypass optimizations (for clang) (default: %(default)s)", + ) + sub.add_argument( + "--no-wasm-opt", + action="store_true", + default=False, + help="do not optimize wasm files after the build (default: %(default)s)", + ) + sub.add_argument( + "--cargo-target-dir", + type=str, + help="for rust projects, forward the parameter to Cargo", + ) + sub.add_argument( + "--wasm-symbols", + action="store_true", + default=False, + help="for rust projects, does not strip the symbols from the wasm output. Useful for analysing the bytecode. Creates larger wasm files. Avoid in production (default: %(default)s)", + ) + sub.add_argument( + "--wasm-name", + type=str, + help="for rust projects, optionally specify the name of the wasm bytecode output file", + ) + sub.add_argument( + "--wasm-suffix", + type=str, + help="for rust projects, optionally specify the suffix of the wasm bytecode output file", + ) def _add_bytecode_arg(sub: Any): - sub.add_argument("--bytecode", type=str, required=True, - help="the file containing the WASM bytecode") + sub.add_argument( + "--bytecode", + type=str, + required=True, + help="the file containing the WASM bytecode", + ) def _add_contract_arg(sub: Any): - sub.add_argument("contract", help="🖄 the address of the Smart Contract") + sub.add_argument("contract", type=str, help="🖄 the bech32 address of the Smart Contract") def _add_contract_abi_arg(sub: Any): - sub.add_argument("--abi", type=str, help="the ABI of the Smart Contract") + sub.add_argument("--abi", type=str, help="the ABI file of the Smart Contract") def _add_function_arg(sub: Any): @@ -237,90 +290,74 @@ def _add_function_arg(sub: Any): def _add_arguments_arg(sub: Any): - sub.add_argument("--arguments", nargs='+', - help="arguments for the contract transaction, as [number, bech32-address, ascii string, " - "boolean] or hex-encoded. E.g. --arguments 42 0x64 1000 0xabba str:TOK-a1c2ef true erd1[..]") - sub.add_argument("--arguments-file", type=str, help="a json file containing the arguments. ONLY if abi file is provided. " - "E.g. [{ 'to': 'erd1...', 'amount': 10000000000 }]") + sub.add_argument( + "--arguments", + nargs="+", + help="arguments for the contract transaction, as [number, bech32-address, ascii string, " + "boolean] or hex-encoded. E.g. --arguments 42 0x64 1000 0xabba str:TOK-a1c2ef true addr:erd1[..]", + ) + sub.add_argument( + "--arguments-file", + type=str, + help="a json file containing the arguments. ONLY if abi file is provided. " + "E.g. [{ 'to': 'erd1...', 'amount': 10000000000 }]", + ) def _add_metadata_arg(sub: Any): - sub.add_argument("--metadata-not-upgradeable", dest="metadata_upgradeable", action="store_false", - help="‼ mark the contract as NOT upgradeable (default: upgradeable)") - sub.add_argument("--metadata-not-readable", dest="metadata_readable", action="store_false", - help="‼ mark the contract as NOT readable (default: readable)") - sub.add_argument("--metadata-payable", dest="metadata_payable", action="store_true", - help="‼ mark the contract as payable (default: not payable)") - sub.add_argument("--metadata-payable-by-sc", dest="metadata_payable_by_sc", action="store_true", - help="‼ mark the contract as payable by SC (default: not payable by SC)") + sub.add_argument( + "--metadata-not-upgradeable", + dest="metadata_upgradeable", + action="store_false", + help="‼ mark the contract as NOT upgradeable (default: upgradeable)", + ) + sub.add_argument( + "--metadata-not-readable", + dest="metadata_readable", + action="store_false", + help="‼ mark the contract as NOT readable (default: readable)", + ) + sub.add_argument( + "--metadata-payable", + dest="metadata_payable", + action="store_true", + help="‼ mark the contract as payable (default: not payable)", + ) + sub.add_argument( + "--metadata-payable-by-sc", + dest="metadata_payable_by_sc", + action="store_true", + help="‼ mark the contract as payable by SC (default: not payable by SC)", + ) sub.set_defaults(metadata_upgradeable=True, metadata_payable=False) -def list_templates(args: Any): - tag = args.tag - contract = Contract(tag) - templates = contract.get_contract_templates() - show_message(templates) - - -def create(args: Any): - name = args.name - template = args.template - tag = args.tag - path = Path(args.path) - - contract = Contract(tag, name, template, path) - contract.create_from_template() - - -def get_project_paths(args: Any) -> List[Path]: - base_path = Path(args.project) - recursive = bool(args.recursive) - if recursive: - return get_project_paths_recursively(base_path) - return [base_path] - - -def clean(args: Any): - check_if_rust_is_installed() - project_path = args.path - projects.clean_project(Path(project_path)) - - def build(args: Any): - project_paths = [Path(args.path)] - arg_list = cli_shared.convert_args_object_to_args_list(args) - - for project in project_paths: - projects.build_project(project, arg_list) + message = """This command cannot build smart contracts anymore. - show_warning("The primary tool for building smart contracts is `sc-meta`. Try using the `sc-meta all build` command.") +The primary tool for building smart contracts is `sc-meta`. +To install `sc-meta` check out the documentation: https://docs.multiversx.com/sdk-and-tools/troubleshooting/rust-setup. +After installing, use the `sc-meta all build` command. To lear more about `sc-meta`, check out this page: https://docs.multiversx.com/developers/meta/sc-meta-cli/#calling-build.""" + show_warning(message) -def do_report(args: Any): - deprecation_message = "`mxpy contract report` is deprecated. Please use `sc-meta report` instead." - logger.warning(deprecation_message) - - args_dict = args.__dict__ - projects.do_report(args, args_dict) - - show_warning(deprecation_message) +def deploy(args: Any): + logger.debug("deploy") + validate_transaction_args(args) + ensure_wallet_args_are_provided(args) + validate_broadcast_args(args) + validate_chain_id_args(args) -def run_tests(args: Any): - check_if_rust_is_installed() - projects.run_tests(args) + sender = cli_shared.prepare_sender(args) + guardian_and_relayer_data = cli_shared.get_guardian_and_relayer_data( + sender=sender.address.to_bech32(), + args=args, + ) + chain_id = cli_shared.get_chain_id(args.chain, args.proxy) + config = TransactionsFactoryConfig(chain_id) -def deploy(args: Any): - logger.debug("deploy") - cli_shared.check_guardian_and_options_args(args) - cli_shared.check_broadcast_args(args) - cli_shared.prepare_chain_id_in_args(args) - cli_shared.prepare_nonce_in_args(args) - - sender = cli_shared.prepare_account(args) - config = TransactionsFactoryConfig(args.chain) abi = Abi.load(Path(args.abi)) if args.abi else None contract = SmartContract(config, abi) @@ -337,14 +374,14 @@ def deploy(args: Any): payable_by_sc=args.metadata_payable_by_sc, gas_limit=int(args.gas_limit), value=int(args.value), - nonce=int(args.nonce), + nonce=sender.nonce, version=int(args.version), options=int(args.options), - guardian=args.guardian) - tx = _sign_guarded_tx(args, tx) + guardian_and_relayer_data=guardian_and_relayer_data, + ) address_computer = AddressComputer(NUMBER_OF_SHARDS) - contract_address = address_computer.compute_contract_address(deployer=sender.address, deployment_nonce=args.nonce) + contract_address = address_computer.compute_contract_address(deployer=sender.address, deployment_nonce=tx.nonce) logger.info("Contract address: %s", contract_address.to_bech32()) utils.log_explorer_contract_address(args.chain, contract_address.to_bech32()) @@ -352,30 +389,23 @@ def deploy(args: Any): _send_or_simulate(tx, contract_address, args) -def _sign_guarded_tx(args: Any, tx: Transaction) -> Transaction: - try: - guardian_account = cli_shared.prepare_guardian_account(args) - except NoWalletProvided: - guardian_account = None - - if guardian_account: - tx.guardian_signature = bytes.fromhex(guardian_account.sign_transaction(tx)) - elif args.guardian: - tx = cosign_transaction(tx, args.guardian_service_url, args.guardian_2fa_code) # type: ignore - - return tx - - def call(args: Any): logger.debug("call") - cli_shared.check_guardian_and_options_args(args) - cli_shared.check_broadcast_args(args) - cli_shared.prepare_chain_id_in_args(args) - cli_shared.prepare_nonce_in_args(args) - sender = cli_shared.prepare_account(args) + validate_transaction_args(args) + ensure_wallet_args_are_provided(args) + validate_broadcast_args(args) + validate_chain_id_args(args) + + sender = cli_shared.prepare_sender(args) + guardian_and_relayer_data = cli_shared.get_guardian_and_relayer_data( + sender=sender.address.to_bech32(), + args=args, + ) + + chain_id = cli_shared.get_chain_id(args.chain, args.proxy) + config = TransactionsFactoryConfig(chain_id) - config = TransactionsFactoryConfig(args.chain) abi = Abi.load(Path(args.abi)) if args.abi else None contract = SmartContract(config, abi) @@ -391,24 +421,32 @@ def call(args: Any): gas_limit=int(args.gas_limit), value=int(args.value), transfers=args.token_transfers, - nonce=int(args.nonce), + nonce=sender.nonce, version=int(args.version), options=int(args.options), - guardian=args.guardian) - tx = _sign_guarded_tx(args, tx) + guardian_and_relayer_data=guardian_and_relayer_data, + ) _send_or_simulate(tx, contract_address, args) def upgrade(args: Any): logger.debug("upgrade") - cli_shared.check_guardian_and_options_args(args) - cli_shared.check_broadcast_args(args) - cli_shared.prepare_chain_id_in_args(args) - cli_shared.prepare_nonce_in_args(args) - sender = cli_shared.prepare_account(args) - config = TransactionsFactoryConfig(args.chain) + validate_transaction_args(args) + ensure_wallet_args_are_provided(args) + validate_broadcast_args(args) + validate_chain_id_args(args) + + sender = cli_shared.prepare_sender(args) + guardian_and_relayer_data = cli_shared.get_guardian_and_relayer_data( + sender=sender.address.to_bech32(), + args=args, + ) + + chain_id = cli_shared.get_chain_id(args.chain, args.proxy) + config = TransactionsFactoryConfig(chain_id) + abi = Abi.load(Path(args.abi)) if args.abi else None contract = SmartContract(config, abi) @@ -427,11 +465,11 @@ def upgrade(args: Any): payable_by_sc=args.metadata_payable_by_sc, gas_limit=int(args.gas_limit), value=int(args.value), - nonce=int(args.nonce), + nonce=sender.nonce, version=int(args.version), options=int(args.options), - guardian=args.guardian) - tx = _sign_guarded_tx(args, tx) + guardian_and_relayer_data=guardian_and_relayer_data, + ) _send_or_simulate(tx, contract_address, args) @@ -439,11 +477,10 @@ def upgrade(args: Any): def query(args: Any): logger.debug("query") - # workaround so we can use the function below to set chainID - args.chain = "" - cli_shared.prepare_chain_id_in_args(args) + validate_proxy_argument(args) - factory_config = TransactionsFactoryConfig(args.chain) + # we don't need chainID to query a contract; we use the provided proxy + factory_config = TransactionsFactoryConfig("") abi = Abi.load(Path(args.abi)) if args.abi else None contract = SmartContract(factory_config, abi) @@ -459,13 +496,13 @@ def query(args: Any): proxy=proxy, function=function, arguments=arguments, - should_prepare_args=should_prepare_args + should_prepare_args=should_prepare_args, ) utils.dump_out_json(result) -def _get_contract_arguments(args: Any) -> Tuple[List[Any], bool]: +def _get_contract_arguments(args: Any) -> tuple[list[Any], bool]: json_args = json.loads(Path(args.arguments_file).expanduser().read_text()) if args.arguments_file else None if json_args and args.arguments: @@ -480,14 +517,14 @@ def _get_contract_arguments(args: Any) -> Tuple[List[Any], bool]: return args.arguments, True -def _send_or_simulate(tx: Transaction, contract_address: IAddress, args: Any): +def _send_or_simulate(tx: Transaction, contract_address: Address, args: Any): output_builder = cli_shared.send_or_simulate(tx, args, dump_output=False) output_builder.set_contract_address(contract_address) utils.dump_out_json(output_builder.build(), outfile=args.outfile) def verify(args: Any) -> None: - contract = Address.from_bech32(args.contract) + contract = Address.new_from_bech32(args.contract) verifier_url = args.verifier_url packaged_src = Path(args.packaged_src).expanduser().resolve() @@ -496,9 +533,7 @@ def verify(args: Any) -> None: docker_image = args.docker_image contract_variant = args.contract_variant - trigger_contract_verification( - packaged_src, owner, contract, verifier_url, docker_image, contract_variant - ) + trigger_contract_verification(packaged_src, owner, contract, verifier_url, docker_image, contract_variant) logger.info("Contract verification request completed!") @@ -523,7 +558,16 @@ def do_reproducible_build(args: Any): raise DockerMissingError() logger.info("Starting the docker run...") - run_docker(docker_image, project_path, contract_path, output_path, no_wasm_opt, docker_interactive, docker_tty, no_default_platform) + run_docker( + docker_image, + project_path, + contract_path, + output_path, + no_wasm_opt, + docker_interactive, + docker_tty, + no_default_platform, + ) logger.info("Docker build ran successfully!") logger.info(f"Inspect summary of generated artifacts here: {artifacts_path}") diff --git a/multiversx_sdk_cli/cli_data.py b/multiversx_sdk_cli/cli_data.py index fc62fc0b..69ed6ca1 100644 --- a/multiversx_sdk_cli/cli_data.py +++ b/multiversx_sdk_cli/cli_data.py @@ -1,7 +1,7 @@ import logging import os from pathlib import Path -from typing import Any, Dict +from typing import Any from multiversx_sdk_cli import cli_shared, errors, utils, workstation @@ -16,20 +16,36 @@ def setup_parser(subparsers: Any) -> Any: sub = cli_shared.add_command_subparser(subparsers, "data", "parse", "Parses values from a given file") sub.add_argument("--file", required=True, help="path of the file to parse") - sub.add_argument("--expression", required=True, help="the Python-Dictionary expression to evaluate in order to extract the data") + sub.add_argument( + "--expression", + required=True, + help="the Python-Dictionary expression to evaluate in order to extract the data", + ) sub.set_defaults(func=parse) sub = cli_shared.add_command_subparser(subparsers, "data", "store", "Stores a key-value pair within a partition") sub.add_argument("--key", required=True, help="the key") sub.add_argument("--value", required=True, help="the value to save") sub.add_argument("--partition", default="*", help="the storage partition (default: %(default)s)") - sub.add_argument("--use-global", action="store_true", default=False, help="use the global storage (default: %(default)s)") + sub.add_argument( + "--use-global", + action="store_true", + default=False, + help="use the global storage (default: %(default)s)", + ) sub.set_defaults(func=store) - sub = cli_shared.add_command_subparser(subparsers, "data", "load", "Loads a key-value pair from a storage partition") + sub = cli_shared.add_command_subparser( + subparsers, "data", "load", "Loads a key-value pair from a storage partition" + ) sub.add_argument("--key", required=True, help="the key") sub.add_argument("--partition", default="*", help="the storage partition (default: %(default)s)") - sub.add_argument("--use-global", action="store_true", default=False, help="use the global storage (default: %(default)s)") + sub.add_argument( + "--use-global", + action="store_true", + default=False, + help="use the global storage (default: %(default)s)", + ) sub.set_defaults(func=load) parser.epilog = cli_shared.build_group_epilog(subparsers) @@ -48,9 +64,7 @@ def parse(args: Any): raise errors.BadUsage(f"File isn't parsable: {file}") try: - result = eval(expression, { - "data": data - }) + result = eval(expression, {"data": data}) except KeyError: result = "" @@ -87,16 +101,16 @@ def load(args: Any): print(value) -def _read_file(use_global: bool) -> Dict[str, Any]: +def _read_file(use_global: bool) -> dict[str, Any]: filename = _get_filename(use_global) if not os.path.isfile(filename): return dict() - data: Dict[str, Any] = utils.read_json_file(filename) + data: dict[str, Any] = utils.read_json_file(filename) return data -def _write_file(use_global: bool, data: Dict[str, Any]): +def _write_file(use_global: bool, data: dict[str, Any]): filename = _get_filename(use_global) utils.write_json_file(str(filename), data) diff --git a/multiversx_sdk_cli/cli_delegation.py b/multiversx_sdk_cli/cli_delegation.py index cc6dcaa2..ab7e27bb 100644 --- a/multiversx_sdk_cli/cli_delegation.py +++ b/multiversx_sdk_cli/cli_delegation.py @@ -1,178 +1,374 @@ -from typing import Any, List - -from multiversx_sdk import ProxyNetworkProvider, TransactionsFactoryConfig +from pathlib import Path +from typing import Any + +from multiversx_sdk import ( + Address, + DelegationTransactionsOutcomeParser, + ProxyNetworkProvider, + TransactionsFactoryConfig, + ValidatorPublicKey, + ValidatorsSigners, +) from multiversx_sdk_cli import cli_shared, errors, utils +from multiversx_sdk_cli.args_validation import ( + ensure_wallet_args_are_provided, + validate_broadcast_args, + validate_chain_id_args, + validate_nonce_args, + validate_proxy_argument, + validate_receiver_args, +) from multiversx_sdk_cli.config import get_config_for_network_providers from multiversx_sdk_cli.delegation import DelegationOperations -def setup_parser(args: List[str], subparsers: Any) -> Any: +def setup_parser(args: list[str], subparsers: Any) -> Any: parser = cli_shared.add_group_subparser(subparsers, "staking-provider", "Staking provider omnitool") subparsers = parser.add_subparsers() # create new delegation contract - sub = cli_shared.add_command_subparser(subparsers, "staking-provider", "create-new-delegation-contract", - "Create a new delegation system smart contract, transferred value must be " - "greater than baseIssuingCost + min deposit value") + sub = cli_shared.add_command_subparser( + subparsers, + "staking-provider", + "create-new-delegation-contract", + "Create a new delegation system smart contract, transferred value must be " + "greater than baseIssuingCost + min deposit value", + ) _add_common_arguments(args, sub) - sub.add_argument("--total-delegation-cap", required=True, help="the total delegation contract capacity") - sub.add_argument("--service-fee", required=True, help="the delegation contract service fee") + sub.add_argument( + "--total-delegation-cap", + required=True, + type=int, + help="the total delegation contract capacity", + ) + sub.add_argument("--service-fee", required=True, type=int, help="the delegation contract service fee") sub.set_defaults(func=do_create_delegation_contract) # get contract address - sub = cli_shared.add_command_subparser(subparsers, "staking-provider", "get-contract-address", - "Get create contract address by transaction hash") - sub.add_argument("--create-tx-hash", required=True, help="the hash") - sub.add_argument("--sender", required=False, help="the sender address") + sub = cli_shared.add_command_subparser( + subparsers, + "staking-provider", + "get-contract-address", + "Get create contract address by transaction hash", + ) + sub.add_argument("--create-tx-hash", required=True, type=str, help="the hash") cli_shared.add_proxy_arg(sub) sub.set_defaults(func=get_contract_address_by_deploy_tx_hash) # add a new node - sub = cli_shared.add_command_subparser(subparsers, "staking-provider", "add-nodes", - "Add new nodes must be called by the contract owner") - sub.add_argument("--validators-file", required=True, help="a JSON file describing the Nodes") - sub.add_argument("--delegation-contract", required=True, help="address of the delegation contract") + sub = cli_shared.add_command_subparser( + subparsers, + "staking-provider", + "add-nodes", + "Add new nodes must be called by the contract owner", + ) + sub.add_argument( + "--validators-pem", required=True, type=str, help="a PEM file holding the BLS keys; can contain multiple nodes" + ) + sub.add_argument( + "--delegation-contract", + required=True, + type=str, + help="bech32 address of the delegation contract", + ) _add_common_arguments(args, sub) sub.set_defaults(func=add_new_nodes) # remove nodes - sub = cli_shared.add_command_subparser(subparsers, "staking-provider", "remove-nodes", - "Remove nodes must be called by the contract owner") - sub.add_argument("--bls-keys", help="a list with the bls keys of the nodes") - sub.add_argument("--validators-file", help="a JSON file describing the Nodes") - sub.add_argument("--delegation-contract", required=True, help="address of the delegation contract") + sub = cli_shared.add_command_subparser( + subparsers, + "staking-provider", + "remove-nodes", + "Remove nodes must be called by the contract owner", + ) + sub.add_argument("--bls-keys", help="a list with the bls keys of the nodes as CSV (addrA,addrB)") + sub.add_argument("--validators-pem", type=str, help="a PEM file holding the BLS keys; can contain multiple nodes") + sub.add_argument( + "--delegation-contract", + required=True, + type=str, + help="address of the delegation contract", + ) _add_common_arguments(args, sub) sub.set_defaults(func=remove_nodes) # stake nodes - sub = cli_shared.add_command_subparser(subparsers, "staking-provider", "stake-nodes", - "Stake nodes must be called by the contract owner") - sub.add_argument("--bls-keys", help="a list with the bls keys of the nodes") - sub.add_argument("--validators-file", help="a JSON file describing the Nodes") - sub.add_argument("--delegation-contract", required=True, help="address of the delegation contract") + sub = cli_shared.add_command_subparser( + subparsers, + "staking-provider", + "stake-nodes", + "Stake nodes must be called by the contract owner", + ) + sub.add_argument("--bls-keys", help="a list with the bls keys of the nodes as CSV (addrA,addrB)") + sub.add_argument("--validators-pem", type=str, help="a PEM file holding the BLS keys; can contain multiple nodes") + sub.add_argument( + "--delegation-contract", + required=True, + type=str, + help="bech32 address of the delegation contract", + ) _add_common_arguments(args, sub) sub.set_defaults(func=stake_nodes) # unbond nodes - sub = cli_shared.add_command_subparser(subparsers, "staking-provider", "unbond-nodes", - "Unbond nodes must be called by the contract owner") - sub.add_argument("--bls-keys", help="a list with the bls keys of the nodes") - sub.add_argument("--validators-file", help="a JSON file describing the Nodes") - sub.add_argument("--delegation-contract", required=True, help="address of the delegation contract") + sub = cli_shared.add_command_subparser( + subparsers, + "staking-provider", + "unbond-nodes", + "Unbond nodes must be called by the contract owner", + ) + sub.add_argument("--bls-keys", help="a list with the bls keys of the nodes as CSV (addrA,addrB)") + sub.add_argument("--validators-pem", type=str, help="a PEM file holding the BLS keys; can contain multiple nodes") + sub.add_argument( + "--delegation-contract", + required=True, + help="address of the delegation contract", + ) _add_common_arguments(args, sub) sub.set_defaults(func=unbond_nodes) # unstake nodes - sub = cli_shared.add_command_subparser(subparsers, "staking-provider", "unstake-nodes", - "Unstake nodes must be called by the contract owner") - sub.add_argument("--bls-keys", help="a list with the bls keys of the nodes") - sub.add_argument("--validators-file", help="a JSON file describing the Nodes") - sub.add_argument("--delegation-contract", required=True, help="address of the delegation contract") + sub = cli_shared.add_command_subparser( + subparsers, + "staking-provider", + "unstake-nodes", + "Unstake nodes must be called by the contract owner", + ) + sub.add_argument("--bls-keys", help="a list with the bls keys of the nodes as CSV (addrA,addrB)") + sub.add_argument("--validators-pem", type=str, help="a PEM file holding the BLS keys; can contain multiple nodes") + sub.add_argument( + "--delegation-contract", + required=True, + help="address of the delegation contract", + ) _add_common_arguments(args, sub) sub.set_defaults(func=unstake_nodes) # unjail nodes - sub = cli_shared.add_command_subparser(subparsers, "staking-provider", "unjail-nodes", - "Unjail nodes must be called by the contract owner") - sub.add_argument("--bls-keys", help="a list with the bls keys of the nodes") - sub.add_argument("--validators-file", help="a JSON file describing the Nodes") - sub.add_argument("--delegation-contract", required=True, help="address of the delegation contract") + sub = cli_shared.add_command_subparser( + subparsers, + "staking-provider", + "unjail-nodes", + "Unjail nodes must be called by the contract owner", + ) + sub.add_argument("--bls-keys", help="a list with the bls keys of the nodes as CSV (addrA,addrB)") + sub.add_argument("--validators-pem", type=str, help="a PEM file holding the BLS keys; can contain multiple nodes") + sub.add_argument( + "--delegation-contract", + required=True, + help="address of the delegation contract", + ) _add_common_arguments(args, sub) sub.set_defaults(func=unjail_nodes) # delegate - sub = cli_shared.add_command_subparser(subparsers, "staking-provider", "delegate", - "Delegate funds to a delegation contract") - sub.add_argument("--delegation-contract", required=True, help="address of the delegation contract") + sub = cli_shared.add_command_subparser( + subparsers, + "staking-provider", + "delegate", + "Delegate funds to a delegation contract", + ) + sub.add_argument( + "--delegation-contract", + required=True, + help="address of the delegation contract", + ) _add_common_arguments(args, sub) sub.set_defaults(func=delegate) # claim rewards - sub = cli_shared.add_command_subparser(subparsers, "staking-provider", "claim-rewards", - "Claim the rewards earned for delegating") - sub.add_argument("--delegation-contract", required=True, help="address of the delegation contract") + sub = cli_shared.add_command_subparser( + subparsers, + "staking-provider", + "claim-rewards", + "Claim the rewards earned for delegating", + ) + sub.add_argument( + "--delegation-contract", + required=True, + help="address of the delegation contract", + ) _add_common_arguments(args, sub) sub.set_defaults(func=claim_rewards) # redelegate rewards - sub = cli_shared.add_command_subparser(subparsers, "staking-provider", "redelegate-rewards", - "Redelegate the rewards earned for delegating") - sub.add_argument("--delegation-contract", required=True, help="address of the delegation contract") + sub = cli_shared.add_command_subparser( + subparsers, + "staking-provider", + "redelegate-rewards", + "Redelegate the rewards earned for delegating", + ) + sub.add_argument( + "--delegation-contract", + required=True, + help="address of the delegation contract", + ) _add_common_arguments(args, sub) sub.set_defaults(func=redelegate_rewards) # undelegate - sub = cli_shared.add_command_subparser(subparsers, "staking-provider", "undelegate", - "Undelegate funds from a delegation contract") - sub.add_argument("--delegation-contract", required=True, help="address of the delegation contract") + sub = cli_shared.add_command_subparser( + subparsers, + "staking-provider", + "undelegate", + "Undelegate funds from a delegation contract", + ) + sub.add_argument( + "--delegation-contract", + required=True, + help="address of the delegation contract", + ) _add_common_arguments(args, sub) sub.set_defaults(func=undelegate) # withdraw - sub = cli_shared.add_command_subparser(subparsers, "staking-provider", "withdraw", - "Withdraw funds from a delegation contract") - sub.add_argument("--delegation-contract", required=True, help="address of the delegation contract") + sub = cli_shared.add_command_subparser( + subparsers, + "staking-provider", + "withdraw", + "Withdraw funds from a delegation contract", + ) + sub.add_argument( + "--delegation-contract", + required=True, + help="address of the delegation contract", + ) _add_common_arguments(args, sub) sub.set_defaults(func=withdraw) # change service fee - sub = cli_shared.add_command_subparser(subparsers, "staking-provider", "change-service-fee", - "Change service fee must be called by the contract owner") - sub.add_argument("--service-fee", required=True, help="new service fee value") - sub.add_argument("--delegation-contract", required=True, help="address of the delegation contract") + sub = cli_shared.add_command_subparser( + subparsers, + "staking-provider", + "change-service-fee", + "Change service fee must be called by the contract owner", + ) + sub.add_argument("--service-fee", required=True, type=int, help="new service fee value") + sub.add_argument( + "--delegation-contract", + required=True, + help="address of the delegation contract", + ) _add_common_arguments(args, sub) sub.set_defaults(func=change_service_fee) # modify total delegation cap - sub = cli_shared.add_command_subparser(subparsers, "staking-provider", "modify-delegation-cap", - "Modify delegation cap must be called by the contract owner") + sub = cli_shared.add_command_subparser( + subparsers, + "staking-provider", + "modify-delegation-cap", + "Modify delegation cap must be called by the contract owner", + ) sub.add_argument("--delegation-cap", required=True, help="new delegation contract capacity") - sub.add_argument("--delegation-contract", required=True, help="address of the delegation contract") + sub.add_argument( + "--delegation-contract", + required=True, + help="address of the delegation contract", + ) _add_common_arguments(args, sub) sub.set_defaults(func=modify_delegation_cap) # set automatic activation - sub = cli_shared.add_command_subparser(subparsers, "staking-provider", "automatic-activation", - "Automatic activation must be called by the contract owner") - - sub.add_argument("--set", action="store_true", required=not (utils.is_arg_present(args, "--unset")), - help="set automatic activation True") - sub.add_argument("--unset", action="store_true", required=not (utils.is_arg_present(args, "--set")), - help="set automatic activation False") - sub.add_argument("--delegation-contract", required=True, help="address of the delegation contract") + sub = cli_shared.add_command_subparser( + subparsers, + "staking-provider", + "automatic-activation", + "Automatic activation must be called by the contract owner", + ) + + sub.add_argument( + "--set", + action="store_true", + required=not (utils.is_arg_present(args, "--unset")), + help="set automatic activation True", + ) + sub.add_argument( + "--unset", + action="store_true", + required=not (utils.is_arg_present(args, "--set")), + help="set automatic activation False", + ) + sub.add_argument( + "--delegation-contract", + required=True, + help="address of the delegation contract", + ) _add_common_arguments(args, sub) sub.set_defaults(func=automatic_activation) # set redelegate cap - sub = cli_shared.add_command_subparser(subparsers, "staking-provider", "redelegate-cap", - "Redelegate cap must be called by the contract owner") - - sub.add_argument("--set", action="store_true", required=not (utils.is_arg_present(args, "--unset")), - help="set redelegate cap True") - sub.add_argument("--unset", action="store_true", required=not (utils.is_arg_present(args, "--set")), - help="set redelegate cap False") - sub.add_argument("--delegation-contract", required=True, help="address of the delegation contract") + sub = cli_shared.add_command_subparser( + subparsers, + "staking-provider", + "redelegate-cap", + "Redelegate cap must be called by the contract owner", + ) + + sub.add_argument( + "--set", + action="store_true", + required=not (utils.is_arg_present(args, "--unset")), + help="set redelegate cap True", + ) + sub.add_argument( + "--unset", + action="store_true", + required=not (utils.is_arg_present(args, "--set")), + help="set redelegate cap False", + ) + sub.add_argument( + "--delegation-contract", + required=True, + help="address of the delegation contract", + ) _add_common_arguments(args, sub) sub.set_defaults(func=redelegate_cap) # set metadata - sub = cli_shared.add_command_subparser(subparsers, "staking-provider", "set-metadata", - "Set metadata must be called by the contract owner") - - sub.add_argument("--name", required=True, help="name field in staking provider metadata") - sub.add_argument("--website", required=True, help="website field in staking provider metadata") - sub.add_argument("--identifier", required=True, help="identifier field in staking provider metadata") - sub.add_argument("--delegation-contract", required=True, help="address of the delegation contract") + sub = cli_shared.add_command_subparser( + subparsers, + "staking-provider", + "set-metadata", + "Set metadata must be called by the contract owner", + ) + + sub.add_argument("--name", required=True, type=str, help="name field in staking provider metadata") + sub.add_argument("--website", required=True, type=str, help="website field in staking provider metadata") + sub.add_argument( + "--identifier", + required=True, + type=str, + help="identifier field in staking provider metadata", + ) + sub.add_argument( + "--delegation-contract", + required=True, + help="address of the delegation contract", + ) _add_common_arguments(args, sub) sub.set_defaults(func=set_metadata) # convert validator to delegation contract - sub = cli_shared.add_command_subparser(subparsers, "staking-provider", "make-delegation-contract-from-validator", - "Create a delegation contract from validator data. Must be called by the node operator") - - sub.add_argument("--max-cap", required=True, help="total delegation cap in EGLD, fully denominated. Use value 0 for uncapped") - sub.add_argument("--fee", required=True, help="service fee as hundredths of percents. (e.g. a service fee of 37.45 percent is expressed by the integer 3745)") + sub = cli_shared.add_command_subparser( + subparsers, + "staking-provider", + "make-delegation-contract-from-validator", + "Create a delegation contract from validator data. Must be called by the node operator", + ) + + sub.add_argument( + "--max-cap", + required=True, + type=int, + help="total delegation cap in EGLD, fully denominated. Use value 0 for uncapped", + ) + sub.add_argument( + "--fee", + required=True, + type=int, + help="service fee as hundredths of percents. (e.g. a service fee of 37.45 percent is expressed by the integer 3745)", + ) _add_common_arguments(args, sub) sub.set_defaults(func=make_new_contract_from_validator_data) @@ -180,249 +376,603 @@ def setup_parser(args: List[str], subparsers: Any) -> Any: return subparsers -def _add_common_arguments(args: List[str], sub: Any): +def _add_common_arguments(args: list[str], sub: Any): cli_shared.add_proxy_arg(sub) cli_shared.add_wallet_args(args, sub) - cli_shared.add_tx_args(args, sub, with_receiver=False, with_data=False, with_estimate_gas=True) - cli_shared.add_broadcast_args(sub, relay=False) + cli_shared.add_tx_args(args, sub, with_receiver=False, with_data=False) + cli_shared.add_broadcast_args(sub) cli_shared.add_outfile_arg(sub, what="signed transaction, hash") cli_shared.add_guardian_wallet_args(args, sub) + cli_shared.add_relayed_v3_wallet_args(args, sub) -def ensure_arguments_are_provided_and_prepared(args: Any): - cli_shared.check_guardian_and_options_args(args) - cli_shared.check_broadcast_args(args) - cli_shared.prepare_chain_id_in_args(args) - cli_shared.prepare_nonce_in_args(args) - +def validate_arguments(args: Any): + validate_nonce_args(args) + validate_receiver_args(args) + ensure_wallet_args_are_provided(args) + validate_broadcast_args(args) + validate_chain_id_args(args) -def do_create_delegation_contract(args: Any): - ensure_arguments_are_provided_and_prepared(args) - sender = cli_shared.prepare_account(args) - config = TransactionsFactoryConfig(args.chain) +def _get_delegation_controller(args: Any): + chain_id = cli_shared.get_chain_id(args.chain, args.proxy) + config = TransactionsFactoryConfig(chain_id) delegation = DelegationOperations(config) + return delegation + + +def do_create_delegation_contract(args: Any): + validate_arguments(args) + + sender = cli_shared.prepare_sender(args) + guardian_and_relayer_data = cli_shared.get_guardian_and_relayer_data( + sender=sender.address.to_bech32(), + args=args, + ) + + gas_limit = args.gas_limit if args.gas_limit else 0 + delegation = _get_delegation_controller(args) + + tx = delegation.prepare_transaction_for_new_delegation_contract( + owner=sender, + native_amount=int(args.value), + total_delegation_cap=int(args.total_delegation_cap), + service_fee=int(args.service_fee), + gas_limit=gas_limit, + gas_price=int(args.gas_price), + nonce=sender.nonce, + version=int(args.version), + options=int(args.options), + guardian_and_relayer_data=guardian_and_relayer_data, + ) - tx = delegation.prepare_transaction_for_new_delegation_contract(sender, args) cli_shared.send_or_simulate(tx, args) def get_contract_address_by_deploy_tx_hash(args: Any): - args = utils.as_object(args) + validate_proxy_argument(args) config = get_config_for_network_providers() proxy = ProxyNetworkProvider(url=args.proxy, config=config) - transaction = proxy.get_transaction(args.create_tx_hash) - transaction_events = transaction.logs.events - if len(transaction_events) == 1: - contract_address = transaction_events[0].address - print(contract_address.to_bech32()) - else: - raise errors.ProgrammingError("Tx has more than one event. Make sure it's a staking provider SC Deploy transaction.") + parser = DelegationTransactionsOutcomeParser() + outcome = parser.parse_create_new_delegation_contract(transaction) -def add_new_nodes(args: Any): - ensure_arguments_are_provided_and_prepared(args) + if len(outcome) > 1: + print("This transaction created more than one delegation contract.") + + for i in range(len(outcome)): + print(f"Delegation contract address: {outcome[i].contract_address.to_bech32()}") - sender = cli_shared.prepare_account(args) - config = TransactionsFactoryConfig(args.chain) - delegation = DelegationOperations(config) - tx = delegation.prepare_transaction_for_adding_nodes(sender, args) +def add_new_nodes(args: Any): + validate_arguments(args) + + sender = cli_shared.prepare_sender(args) + guardian_and_relayer_data = cli_shared.get_guardian_and_relayer_data( + sender=sender.address.to_bech32(), + args=args, + ) + + gas_limit = args.gas_limit if args.gas_limit else 0 + public_keys, signed_messages = _get_public_keys_and_signed_messages(args) + + delegation = _get_delegation_controller(args) + tx = delegation.prepare_transaction_for_adding_nodes( + owner=sender, + delegation_contract=Address.new_from_bech32(args.delegation_contract), + public_keys=public_keys, + signed_messages=signed_messages, + gas_limit=gas_limit, + gas_price=int(args.gas_price), + value=int(args.value), + nonce=sender.nonce, + version=int(args.version), + options=int(args.options), + guardian_and_relayer_data=guardian_and_relayer_data, + ) + cli_shared.send_or_simulate(tx, args) +def _get_public_keys_and_signed_messages(args: Any) -> tuple[list[ValidatorPublicKey], list[bytes]]: + validators_file_path = Path(args.validators_pem).expanduser() + validators_file = ValidatorsSigners.new_from_pem(validators_file_path) + signers = validators_file.get_signers() + + pubkey = Address.new_from_bech32(args.delegation_contract).get_public_key() + + public_keys: list[ValidatorPublicKey] = [] + signed_messages: list[bytes] = [] + for signer in signers: + signed_message = signer.sign(pubkey) + + public_keys.append(signer.secret_key.generate_public_key()) + signed_messages.append(signed_message) + + return public_keys, signed_messages + + def remove_nodes(args: Any): + validate_arguments(args) _check_if_either_bls_keys_or_validators_file_are_provided(args) - ensure_arguments_are_provided_and_prepared(args) - sender = cli_shared.prepare_account(args) - config = TransactionsFactoryConfig(args.chain) - delegation = DelegationOperations(config) + sender = cli_shared.prepare_sender(args) + guardian_and_relayer_data = cli_shared.get_guardian_and_relayer_data( + sender=sender.address.to_bech32(), + args=args, + ) + + gas_limit = args.gas_limit if args.gas_limit else 0 + public_keys = _load_validators_public_keys(args) + + delegation = _get_delegation_controller(args) + tx = delegation.prepare_transaction_for_removing_nodes( + owner=sender, + delegation_contract=Address.new_from_bech32(args.delegation_contract), + public_keys=public_keys, + gas_limit=gas_limit, + gas_price=int(args.gas_price), + value=int(args.value), + nonce=sender.nonce, + version=int(args.version), + options=int(args.options), + guardian_and_relayer_data=guardian_and_relayer_data, + ) - tx = delegation.prepare_transaction_for_removing_nodes(sender, args) cli_shared.send_or_simulate(tx, args) +def _load_validators_public_keys(args: Any) -> list[ValidatorPublicKey]: + if args.bls_keys: + return _parse_public_bls_keys(args.bls_keys) + + validators_file_path = Path(args.validators_pem).expanduser() + validators_file = ValidatorsSigners.new_from_pem(validators_file_path) + return validators_file.get_public_keys() + + +def _parse_public_bls_keys(public_bls_keys: str) -> list[ValidatorPublicKey]: + keys = public_bls_keys.split(",") + validator_public_keys: list[ValidatorPublicKey] = [] + + for key in keys: + validator_public_keys.append(ValidatorPublicKey(bytes.fromhex(key))) + + return validator_public_keys + + def stake_nodes(args: Any): _check_if_either_bls_keys_or_validators_file_are_provided(args) - ensure_arguments_are_provided_and_prepared(args) + validate_arguments(args) + + sender = cli_shared.prepare_sender(args) + guardian_and_relayer_data = cli_shared.get_guardian_and_relayer_data( + sender=sender.address.to_bech32(), + args=args, + ) + + gas_limit = args.gas_limit if args.gas_limit else 0 + public_keys = _load_validators_public_keys(args) + + delegation = _get_delegation_controller(args) + tx = delegation.prepare_transaction_for_staking_nodes( + owner=sender, + delegation_contract=Address.new_from_bech32(args.delegation_contract), + public_keys=public_keys, + gas_limit=gas_limit, + gas_price=int(args.gas_price), + value=int(args.value), + nonce=sender.nonce, + version=int(args.version), + options=int(args.options), + guardian_and_relayer_data=guardian_and_relayer_data, + ) - sender = cli_shared.prepare_account(args) - config = TransactionsFactoryConfig(args.chain) - delegation = DelegationOperations(config) - - tx = delegation.prepare_transaction_for_staking_nodes(sender, args) cli_shared.send_or_simulate(tx, args) def _check_if_either_bls_keys_or_validators_file_are_provided(args: Any): bls_keys = args.bls_keys - validators_file = args.validators_file + validators_file = args.validators_pem if not bls_keys and not validators_file: - raise errors.BadUsage("No bls keys or validators file provided. Use either `--bls-keys` or `--validators-file`") + raise errors.BadUsage("No bls keys or validators file provided. Use either `--bls-keys` or `--validators-pem`") def unbond_nodes(args: Any): _check_if_either_bls_keys_or_validators_file_are_provided(args) - ensure_arguments_are_provided_and_prepared(args) - - sender = cli_shared.prepare_account(args) - config = TransactionsFactoryConfig(args.chain) - delegation = DelegationOperations(config) + validate_arguments(args) + + sender = cli_shared.prepare_sender(args) + guardian_and_relayer_data = cli_shared.get_guardian_and_relayer_data( + sender=sender.address.to_bech32(), + args=args, + ) + + gas_limit = args.gas_limit if args.gas_limit else 0 + public_keys = _load_validators_public_keys(args) + + delegation = _get_delegation_controller(args) + tx = delegation.prepare_transaction_for_unbonding_nodes( + owner=sender, + delegation_contract=Address.new_from_bech32(args.delegation_contract), + public_keys=public_keys, + gas_limit=gas_limit, + gas_price=int(args.gas_price), + value=int(args.value), + nonce=sender.nonce, + version=int(args.version), + options=int(args.options), + guardian_and_relayer_data=guardian_and_relayer_data, + ) - tx = delegation.prepare_transaction_for_unbonding_nodes(sender, args) cli_shared.send_or_simulate(tx, args) def unstake_nodes(args: Any): _check_if_either_bls_keys_or_validators_file_are_provided(args) - ensure_arguments_are_provided_and_prepared(args) - - sender = cli_shared.prepare_account(args) - config = TransactionsFactoryConfig(args.chain) - delegation = DelegationOperations(config) + validate_arguments(args) + + sender = cli_shared.prepare_sender(args) + guardian_and_relayer_data = cli_shared.get_guardian_and_relayer_data( + sender=sender.address.to_bech32(), + args=args, + ) + + gas_limit = args.gas_limit if args.gas_limit else 0 + public_keys = _load_validators_public_keys(args) + + delegation = _get_delegation_controller(args) + tx = delegation.prepare_transaction_for_unstaking_nodes( + owner=sender, + delegation_contract=Address.new_from_bech32(args.delegation_contract), + public_keys=public_keys, + gas_limit=gas_limit, + gas_price=int(args.gas_price), + value=int(args.value), + nonce=sender.nonce, + version=int(args.version), + options=int(args.options), + guardian_and_relayer_data=guardian_and_relayer_data, + ) - tx = delegation.prepare_transaction_for_unstaking_nodes(sender, args) cli_shared.send_or_simulate(tx, args) def unjail_nodes(args: Any): _check_if_either_bls_keys_or_validators_file_are_provided(args) - ensure_arguments_are_provided_and_prepared(args) - - sender = cli_shared.prepare_account(args) - config = TransactionsFactoryConfig(args.chain) - delegation = DelegationOperations(config) + validate_arguments(args) + + sender = cli_shared.prepare_sender(args) + guardian_and_relayer_data = cli_shared.get_guardian_and_relayer_data( + sender=sender.address.to_bech32(), + args=args, + ) + + gas_limit = args.gas_limit if args.gas_limit else 0 + public_keys = _load_validators_public_keys(args) + + delegation = _get_delegation_controller(args) + tx = delegation.prepare_transaction_for_unjailing_nodes( + owner=sender, + delegation_contract=Address.new_from_bech32(args.delegation_contract), + public_keys=public_keys, + gas_limit=gas_limit, + gas_price=int(args.gas_price), + value=int(args.value), + nonce=sender.nonce, + version=int(args.version), + options=int(args.options), + guardian_and_relayer_data=guardian_and_relayer_data, + ) - tx = delegation.prepare_transaction_for_unjailing_nodes(sender, args) cli_shared.send_or_simulate(tx, args) def delegate(args: Any): - ensure_arguments_are_provided_and_prepared(args) - - if not (int(args.value)): - raise errors.BadUrlError("Value not provided. Minimum value to delegate is 1 EGLD") - - sender = cli_shared.prepare_account(args) - config = TransactionsFactoryConfig(args.chain) - delegation = DelegationOperations(config) + validate_arguments(args) + + sender = cli_shared.prepare_sender(args) + guardian_and_relayer_data = cli_shared.get_guardian_and_relayer_data( + sender=sender.address.to_bech32(), + args=args, + ) + + gas_limit = args.gas_limit if args.gas_limit else 0 + value = int(args.value) + + delegation = _get_delegation_controller(args) + tx = delegation.prepare_transaction_for_delegating( + owner=sender, + delegation_contract=Address.new_from_bech32(args.delegation_contract), + gas_limit=gas_limit, + gas_price=int(args.gas_price), + value=value, + nonce=sender.nonce, + version=int(args.version), + options=int(args.options), + guardian_and_relayer_data=guardian_and_relayer_data, + ) - tx = delegation.prepare_transaction_for_delegating(sender, args) cli_shared.send_or_simulate(tx, args) def claim_rewards(args: Any): - ensure_arguments_are_provided_and_prepared(args) - - sender = cli_shared.prepare_account(args) - config = TransactionsFactoryConfig(args.chain) - delegation = DelegationOperations(config) - - tx = delegation.prepare_transaction_for_claiming_rewards(sender, args) + validate_arguments(args) + + sender = cli_shared.prepare_sender(args) + guardian_and_relayer_data = cli_shared.get_guardian_and_relayer_data( + sender=sender.address.to_bech32(), + args=args, + ) + + gas_limit = args.gas_limit if args.gas_limit else 0 + + delegation = _get_delegation_controller(args) + tx = delegation.prepare_transaction_for_claiming_rewards( + owner=sender, + delegation_contract=Address.new_from_bech32(args.delegation_contract), + gas_limit=gas_limit, + gas_price=int(args.gas_price), + value=int(args.value), + nonce=sender.nonce, + version=int(args.version), + options=int(args.options), + guardian_and_relayer_data=guardian_and_relayer_data, + ) cli_shared.send_or_simulate(tx, args) def redelegate_rewards(args: Any): - ensure_arguments_are_provided_and_prepared(args) - - sender = cli_shared.prepare_account(args) - config = TransactionsFactoryConfig(args.chain) - delegation = DelegationOperations(config) - - tx = delegation.prepare_transaction_for_redelegating_rewards(sender, args) + validate_arguments(args) + + sender = cli_shared.prepare_sender(args) + guardian_and_relayer_data = cli_shared.get_guardian_and_relayer_data( + sender=sender.address.to_bech32(), + args=args, + ) + + gas_limit = args.gas_limit if args.gas_limit else 0 + + delegation = _get_delegation_controller(args) + tx = delegation.prepare_transaction_for_redelegating_rewards( + owner=sender, + delegation_contract=Address.new_from_bech32(args.delegation_contract), + gas_limit=gas_limit, + gas_price=int(args.gas_price), + value=int(args.value), + nonce=sender.nonce, + version=int(args.version), + options=int(args.options), + guardian_and_relayer_data=guardian_and_relayer_data, + ) cli_shared.send_or_simulate(tx, args) def undelegate(args: Any): - ensure_arguments_are_provided_and_prepared(args) + validate_arguments(args) + + sender = cli_shared.prepare_sender(args) + guardian_and_relayer_data = cli_shared.get_guardian_and_relayer_data( + sender=sender.address.to_bech32(), + args=args, + ) + + gas_limit = args.gas_limit if args.gas_limit else 0 + value = int(args.value) + + delegation = _get_delegation_controller(args) + tx = delegation.prepare_transaction_for_undelegating( + owner=sender, + delegation_contract=Address.new_from_bech32(args.delegation_contract), + gas_limit=gas_limit, + gas_price=int(args.gas_price), + value=value, + nonce=sender.nonce, + version=int(args.version), + options=int(args.options), + guardian_and_relayer_data=guardian_and_relayer_data, + ) - if not (int(args.value)): - raise errors.BadUrlError("Value not provided. Minimum value to undelegate is 1 EGLD") - - sender = cli_shared.prepare_account(args) - config = TransactionsFactoryConfig(args.chain) - delegation = DelegationOperations(config) - - tx = delegation.prepare_transaction_for_undelegating(sender, args) cli_shared.send_or_simulate(tx, args) def withdraw(args: Any): - ensure_arguments_are_provided_and_prepared(args) - - sender = cli_shared.prepare_account(args) - config = TransactionsFactoryConfig(args.chain) - delegation = DelegationOperations(config) + validate_arguments(args) + + sender = cli_shared.prepare_sender(args) + guardian_and_relayer_data = cli_shared.get_guardian_and_relayer_data( + sender=sender.address.to_bech32(), + args=args, + ) + + gas_limit = args.gas_limit if args.gas_limit else 0 + + delegation = _get_delegation_controller(args) + tx = delegation.prepare_transaction_for_withdrawing( + owner=sender, + delegation_contract=Address.new_from_bech32(args.delegation_contract), + gas_limit=gas_limit, + gas_price=int(args.gas_price), + value=int(args.value), + nonce=sender.nonce, + version=int(args.version), + options=int(args.options), + guardian_and_relayer_data=guardian_and_relayer_data, + ) - tx = delegation.prepare_transaction_for_withdrawing(sender, args) cli_shared.send_or_simulate(tx, args) def change_service_fee(args: Any): - ensure_arguments_are_provided_and_prepared(args) - - sender = cli_shared.prepare_account(args) - config = TransactionsFactoryConfig(args.chain) - delegation = DelegationOperations(config) + validate_arguments(args) + + sender = cli_shared.prepare_sender(args) + guardian_and_relayer_data = cli_shared.get_guardian_and_relayer_data( + sender=sender.address.to_bech32(), + args=args, + ) + + gas_limit = args.gas_limit if args.gas_limit else 0 + + delegation = _get_delegation_controller(args) + tx = delegation.prepare_transaction_for_changing_service_fee( + owner=sender, + delegation_contract=Address.new_from_bech32(args.delegation_contract), + service_fee=int(args.service_fee), + gas_limit=gas_limit, + gas_price=int(args.gas_price), + value=int(args.value), + nonce=sender.nonce, + version=int(args.version), + options=int(args.options), + guardian_and_relayer_data=guardian_and_relayer_data, + ) - tx = delegation.prepare_transaction_for_changing_service_fee(sender, args) cli_shared.send_or_simulate(tx, args) def modify_delegation_cap(args: Any): - ensure_arguments_are_provided_and_prepared(args) - - sender = cli_shared.prepare_account(args) - config = TransactionsFactoryConfig(args.chain) - delegation = DelegationOperations(config) + validate_arguments(args) + + sender = cli_shared.prepare_sender(args) + guardian_and_relayer_data = cli_shared.get_guardian_and_relayer_data( + sender=sender.address.to_bech32(), + args=args, + ) + + gas_limit = args.gas_limit if args.gas_limit else 0 + + delegation = _get_delegation_controller(args) + tx = delegation.prepare_transaction_for_modifying_delegation_cap( + owner=sender, + delegation_contract=Address.new_from_bech32(args.delegation_contract), + delegation_cap=int(args.delegation_cap), + gas_limit=gas_limit, + gas_price=int(args.gas_price), + value=int(args.value), + nonce=sender.nonce, + version=int(args.version), + options=int(args.options), + guardian_and_relayer_data=guardian_and_relayer_data, + ) - tx = delegation.prepare_transaction_for_modifying_delegation_cap(sender, args) cli_shared.send_or_simulate(tx, args) def automatic_activation(args: Any): - ensure_arguments_are_provided_and_prepared(args) + validate_arguments(args) + + sender = cli_shared.prepare_sender(args) + guardian_and_relayer_data = cli_shared.get_guardian_and_relayer_data( + sender=sender.address.to_bech32(), + args=args, + ) + + gas_limit = args.gas_limit if args.gas_limit else 0 + + delegation = _get_delegation_controller(args) + tx = delegation.prepare_transaction_for_automatic_activation( + owner=sender, + delegation_contract=Address.new_from_bech32(args.delegation_contract), + set=args.set, + unset=args.unset, + gas_limit=gas_limit, + gas_price=int(args.gas_price), + value=int(args.value), + nonce=sender.nonce, + version=int(args.version), + options=int(args.options), + guardian_and_relayer_data=guardian_and_relayer_data, + ) - sender = cli_shared.prepare_account(args) - config = TransactionsFactoryConfig(args.chain) - delegation = DelegationOperations(config) - - tx = delegation.prepare_transaction_for_automatic_activation(sender, args) cli_shared.send_or_simulate(tx, args) def redelegate_cap(args: Any): - ensure_arguments_are_provided_and_prepared(args) - - sender = cli_shared.prepare_account(args) - config = TransactionsFactoryConfig(args.chain) - delegation = DelegationOperations(config) + validate_arguments(args) + + sender = cli_shared.prepare_sender(args) + guardian_and_relayer_data = cli_shared.get_guardian_and_relayer_data( + sender=sender.address.to_bech32(), + args=args, + ) + + gas_limit = args.gas_limit if args.gas_limit else 0 + + delegation = _get_delegation_controller(args) + tx = delegation.prepare_transaction_for_redelegate_cap( + owner=sender, + delegation_contract=Address.new_from_bech32(args.delegation_contract), + set=args.set, + unset=args.unset, + gas_limit=gas_limit, + gas_price=int(args.gas_price), + value=int(args.value), + nonce=sender.nonce, + version=int(args.version), + options=int(args.options), + guardian_and_relayer_data=guardian_and_relayer_data, + ) - tx = delegation.prepare_transaction_for_redelegate_cap(sender, args) cli_shared.send_or_simulate(tx, args) def set_metadata(args: Any): - ensure_arguments_are_provided_and_prepared(args) + validate_arguments(args) + + sender = cli_shared.prepare_sender(args) + guardian_and_relayer_data = cli_shared.get_guardian_and_relayer_data( + sender=sender.address.to_bech32(), + args=args, + ) + + gas_limit = args.gas_limit if args.gas_limit else 0 + + delegation = _get_delegation_controller(args) + tx = delegation.prepare_transaction_for_setting_metadata( + owner=sender, + delegation_contract=Address.new_from_bech32(args.delegation_contract), + name=args.name, + website=args.website, + identifier=args.identifier, + gas_limit=gas_limit, + gas_price=int(args.gas_price), + value=int(args.value), + nonce=sender.nonce, + version=int(args.version), + options=int(args.options), + guardian_and_relayer_data=guardian_and_relayer_data, + ) - sender = cli_shared.prepare_account(args) - config = TransactionsFactoryConfig(args.chain) - delegation = DelegationOperations(config) - - tx = delegation.prepare_transaction_for_setting_metadata(sender, args) cli_shared.send_or_simulate(tx, args) def make_new_contract_from_validator_data(args: Any): - ensure_arguments_are_provided_and_prepared(args) - - sender = cli_shared.prepare_account(args) - config = TransactionsFactoryConfig(args.chain) - delegation = DelegationOperations(config) + validate_arguments(args) + + sender = cli_shared.prepare_sender(args) + guardian_and_relayer_data = cli_shared.get_guardian_and_relayer_data( + sender=sender.address.to_bech32(), + args=args, + ) + + gas_limit = args.gas_limit if args.gas_limit else 0 + + delegation = _get_delegation_controller(args) + tx = delegation.prepare_transaction_for_creating_delegation_contract_from_validator( + owner=sender, + max_cap=args.max_cap, + service_fee=args.fee, + gas_limit=gas_limit, + gas_price=int(args.gas_price), + value=int(args.value), + nonce=sender.nonce, + version=int(args.version), + options=int(args.options), + guardian_and_relayer_data=guardian_and_relayer_data, + ) - tx = delegation.prepare_transaction_for_creating_delegation_contract_from_validator(sender, args) cli_shared.send_or_simulate(tx, args) diff --git a/multiversx_sdk_cli/cli_deps.py b/multiversx_sdk_cli/cli_deps.py index bf93c8e0..f2638dc9 100644 --- a/multiversx_sdk_cli/cli_deps.py +++ b/multiversx_sdk_cli/cli_deps.py @@ -1,5 +1,5 @@ import logging -from typing import Any, List, Tuple +from typing import Any from multiversx_sdk_cli import cli_shared, config, dependencies, errors from multiversx_sdk_cli.dependencies.install import get_deps_dict @@ -12,11 +12,18 @@ def setup_parser(subparsers: Any) -> Any: parser = cli_shared.add_group_subparser(subparsers, "deps", "Manage dependencies or multiversx-sdk modules") subparsers = parser.add_subparsers() - choices = ['all'] + list(get_deps_dict().keys()) + choices = ["all"] + list(get_deps_dict().keys()) - sub = cli_shared.add_command_subparser(subparsers, "deps", "install", "Install dependencies or multiversx-sdk modules.") + sub = cli_shared.add_command_subparser( + subparsers, "deps", "install", "Install dependencies or multiversx-sdk modules." + ) sub.add_argument("name", choices=choices, help="the dependency to install") - sub.add_argument("--overwrite", action="store_true", default=False, help="whether to overwrite an existing installation") + sub.add_argument( + "--overwrite", + action="store_true", + default=False, + help="whether to overwrite an existing installation", + ) sub.set_defaults(func=install) sub = cli_shared.add_command_subparser(subparsers, "deps", "check", "Check whether a dependency is installed.") @@ -38,7 +45,7 @@ def check(args: Any): if name == "all": all_dependencies = dependencies.get_all_deps() - missing_dependencies: List[Tuple[str, str]] = [] + missing_dependencies: list[tuple[str, str]] = [] for dependency in all_dependencies: tag_to_check: str = config.get_dependency_tag(dependency.key) @@ -50,16 +57,16 @@ def check(args: Any): if len(missing_dependencies): raise errors.DependenciesMissing(missing_dependencies) return - else: - module = dependencies.get_module_by_key(name) - tag_to_check: str = config.get_dependency_tag(module.key) - - is_installed = check_module_is_installed(module, tag_to_check) - if is_installed and name != "rust": - logger.info(f"[{module.key} {tag_to_check}] is installed.") - return - elif not is_installed: - raise errors.DependencyMissing(module.key, tag_to_check) + + module = dependencies.get_module_by_key(name) + tag_to_check = config.get_dependency_tag(module.key) + + is_installed = check_module_is_installed(module, tag_to_check) + if is_installed: + logger.info(f"[{module.key} {tag_to_check}] is installed.") + return + elif not is_installed: + raise errors.DependencyMissing(module.key, tag_to_check) def check_module_is_installed(module: DependencyModule, tag_to_check: str) -> bool: diff --git a/multiversx_sdk_cli/cli_dns.py b/multiversx_sdk_cli/cli_dns.py index dbbc9c9b..2568a06d 100644 --- a/multiversx_sdk_cli/cli_dns.py +++ b/multiversx_sdk_cli/cli_dns.py @@ -1,29 +1,42 @@ -from typing import Any, List +from typing import Any from multiversx_sdk import ProxyNetworkProvider -from prettytable import PrettyTable +from rich.console import Console +from rich.table import Table from multiversx_sdk_cli import cli_shared from multiversx_sdk_cli.config import get_config_for_network_providers from multiversx_sdk_cli.constants import ADDRESS_ZERO_HEX -from multiversx_sdk_cli.dns import (compute_dns_address_for_shard_id, - dns_address_for_name, name_hash, register, - registration_cost, resolve, validate_name, - version) +from multiversx_sdk_cli.dns import ( + compute_dns_address_for_shard_id, + dns_address_for_name, + name_hash, + register, + registration_cost, + resolve, + validate_name, + version, +) from multiversx_sdk_cli.errors import ArgumentsNotProvidedError -def setup_parser(args: List[str], subparsers: Any) -> Any: +def setup_parser(args: list[str], subparsers: Any) -> Any: parser = cli_shared.add_group_subparser(subparsers, "dns", "Operations related to the Domain Name Service") subparsers = parser.add_subparsers() - sub = cli_shared.add_command_subparser(subparsers, "dns", "register", "Send a register transaction to the appropriate DNS contract from given user and with given name") + sub = cli_shared.add_command_subparser( + subparsers, + "dns", + "register", + "Send a register transaction to the appropriate DNS contract from given user and with given name", + ) cli_shared.add_outfile_arg(sub) - cli_shared.add_broadcast_args(sub, relay=True) + cli_shared.add_broadcast_args(sub) cli_shared.add_wallet_args(args, sub) cli_shared.add_proxy_arg(sub) cli_shared.add_tx_args(args, sub, with_receiver=False, with_data=False) cli_shared.add_guardian_wallet_args(args, sub) + cli_shared.add_relayed_v3_wallet_args(args, sub) sub.add_argument("--name", help="the name to register") sub.set_defaults(func=register) @@ -32,35 +45,80 @@ def setup_parser(args: List[str], subparsers: Any) -> Any: cli_shared.add_proxy_arg(sub) sub.set_defaults(func=dns_resolve) - sub = cli_shared.add_command_subparser(subparsers, "dns", "validate-name", "Asks one of the DNS contracts to validate a name. Can be useful before registering it.") + sub = cli_shared.add_command_subparser( + subparsers, + "dns", + "validate-name", + "Asks one of the DNS contracts to validate a name. Can be useful before registering it.", + ) _add_name_arg(sub) - sub.add_argument("--shard-id", type=int, default=0, help="shard id of the contract to call (default: %(default)s)") + sub.add_argument( + "--shard-id", + type=int, + default=0, + help="shard id of the contract to call (default: %(default)s)", + ) cli_shared.add_proxy_arg(sub) sub.set_defaults(func=dns_validate_name) - sub = cli_shared.add_command_subparser(subparsers, "dns", "name-hash", "The hash of a name, as computed by a DNS smart contract") + sub = cli_shared.add_command_subparser( + subparsers, + "dns", + "name-hash", + "The hash of a name, as computed by a DNS smart contract", + ) _add_name_arg(sub) sub.set_defaults(func=get_name_hash) - sub = cli_shared.add_command_subparser(subparsers, "dns", "registration-cost", "Gets the registration cost from a DNS smart contract, by default the one with shard id 0.") - sub.add_argument("--shard-id", type=int, default=0, help="shard id of the contract to call (default: %(default)s)") + sub = cli_shared.add_command_subparser( + subparsers, + "dns", + "registration-cost", + "Gets the registration cost from a DNS smart contract, by default the one with shard id 0.", + ) + sub.add_argument( + "--shard-id", + type=int, + default=0, + help="shard id of the contract to call (default: %(default)s)", + ) cli_shared.add_proxy_arg(sub) sub.set_defaults(func=get_registration_cost) sub = cli_shared.add_command_subparser(subparsers, "dns", "version", "Asks the contract for its version") - sub.add_argument("--shard-id", type=int, default=0, help="shard id of the contract to call (default: %(default)s)") - sub.add_argument("--all", action="store_true", default=False, help="prints a list of all DNS contracts and their current versions (default: %(default)s)") + sub.add_argument( + "--shard-id", + type=int, + default=0, + help="shard id of the contract to call (default: %(default)s)", + ) + sub.add_argument( + "--all", + action="store_true", + default=False, + help="prints a list of all DNS contracts and their current versions (default: %(default)s)", + ) cli_shared.add_proxy_arg(sub) sub.set_defaults(func=get_version) sub = cli_shared.add_command_subparser(subparsers, "dns", "dns-addresses", "Lists all 256 DNS contract addresses") sub.set_defaults(func=print_dns_addresses_table) - sub = cli_shared.add_command_subparser(subparsers, "dns", "dns-address-for-name", "DNS contract address (bech32) that corresponds to a name") + sub = cli_shared.add_command_subparser( + subparsers, + "dns", + "dns-address-for-name", + "DNS contract address (bech32) that corresponds to a name", + ) _add_name_arg(sub) sub.set_defaults(func=get_dns_address_for_name) - sub = cli_shared.add_command_subparser(subparsers, "dns", "dns-address-for-name-hex", "DNS contract address (hex) that corresponds to a name") + sub = cli_shared.add_command_subparser( + subparsers, + "dns", + "dns-address-for-name-hex", + "DNS contract address (hex) that corresponds to a name", + ) _add_name_arg(sub) sub.set_defaults(func=get_dns_address_for_name_hex) @@ -122,20 +180,33 @@ def get_version(args: Any): config = get_config_for_network_providers() proxy = ProxyNetworkProvider(url=args.proxy, config=config) if args.all: - t = PrettyTable(['Shard ID', 'Contract address (bech32)', 'Contract address (hex)', 'Version']) + table = Table(title="DNS Version") + table.add_column("Shard ID") + table.add_column("Contract address (bech32)") + table.add_column("Contract address (hex)") + table.add_column("Version") + for shard_id in range(0, 256): address = compute_dns_address_for_shard_id(shard_id) v = version(shard_id, proxy) - t.add_row([shard_id, address.to_bech32(), address.to_hex(), v]) - print(t) + table.add_row(str(shard_id), address.to_bech32(), address.to_hex(), v) + + console = Console() + console.print(table) else: shard_id = int(args.shard_id) print(version(shard_id, proxy)) def print_dns_addresses_table(args: Any): - t = PrettyTable(['Shard ID', 'Contract address (bech32)', 'Contract address (hex)']) + table = Table(title="DNS Addresses") + table.add_column("Shard ID") + table.add_column("Contract address (bech32)") + table.add_column("Contract address (hex)") + for shard_id in range(0, 256): address = compute_dns_address_for_shard_id(shard_id) - t.add_row([shard_id, address.to_bech32(), address.to_hex()]) - print(t) + table.add_row(str(shard_id), address.to_bech32(), address.to_hex()) + + console = Console() + console.print(table) diff --git a/multiversx_sdk_cli/cli_faucet.py b/multiversx_sdk_cli/cli_faucet.py index 878dc7ad..a9700528 100644 --- a/multiversx_sdk_cli/cli_faucet.py +++ b/multiversx_sdk_cli/cli_faucet.py @@ -1,12 +1,12 @@ import logging import webbrowser from enum import Enum -from typing import Any, List, Tuple +from typing import Any + +from multiversx_sdk import Message, NativeAuthClient, NativeAuthClientConfig from multiversx_sdk_cli import cli_shared from multiversx_sdk_cli.errors import BadUserInput -from multiversx_sdk_cli.native_auth_client import (NativeAuthClient, - NativeAuthClientConfig) logger = logging.getLogger("cli.faucet") @@ -21,13 +21,13 @@ class ApiUrls(Enum): TESTNET = "https://testnet-api.multiversx.com" -def setup_parser(args: List[str], subparsers: Any) -> Any: +def setup_parser(args: list[str], subparsers: Any) -> Any: parser = cli_shared.add_group_subparser(subparsers, "faucet", "Get xEGLD on Devnet or Testnet") subparsers = parser.add_subparsers() sub = cli_shared.add_command_subparser(subparsers, "faucet", "request", "Request xEGLD.") cli_shared.add_wallet_args(args, sub) - sub.add_argument("--chain", required=True, help="the chain identifier") + sub.add_argument("--chain", required=True, choices=["D", "T"], help="the chain identifier") sub.set_defaults(func=faucet) parser.epilog = cli_shared.build_group_epilog(subparsers) @@ -43,13 +43,9 @@ def faucet(args: Any): init_token = client.initialize() token_for_siginig = f"{account.address.to_bech32()}{init_token}" - signature = account.sign_message(token_for_siginig.encode()) + signature = account.sign_message(Message(token_for_siginig.encode())) - access_token = client.get_token( - address=account.address.to_bech32(), - token=init_token, - signature=signature - ) + access_token = client.get_token(address=account.address, token=init_token, signature=signature.hex()) logger.info(f"Requesting funds for address: {account.address.to_bech32()}") call_web_wallet_faucet(wallet_url=wallet, access_token=access_token) @@ -60,7 +56,7 @@ def call_web_wallet_faucet(wallet_url: str, access_token: str): webbrowser.open_new_tab(faucet_url) -def get_wallet_and_api_urls(args: Any) -> Tuple[str, str]: +def get_wallet_and_api_urls(args: Any) -> tuple[str, str]: chain: str = args.chain if chain.upper() == "D": diff --git a/multiversx_sdk_cli/cli_ledger.py b/multiversx_sdk_cli/cli_ledger.py index b5d1f0c8..db6fa2e0 100644 --- a/multiversx_sdk_cli/cli_ledger.py +++ b/multiversx_sdk_cli/cli_ledger.py @@ -1,8 +1,9 @@ import logging from typing import Any +from multiversx_sdk import LedgerApp + from multiversx_sdk_cli import cli_shared -from multiversx_sdk_cli.ledger.ledger_app_handler import LedgerApp logger = logging.getLogger("cli.ledger") @@ -12,10 +13,21 @@ def setup_parser(subparsers: Any) -> Any: subparsers = parser.add_subparsers() sub = cli_shared.add_command_subparser(subparsers, "ledger", "addresses", "Get the addresses within Ledger") - sub.add_argument("--num-addresses", required=False, type=int, default=10, help="The number of addresses to fetch") + sub.add_argument( + "--num-addresses", + required=False, + type=int, + default=10, + help="The number of addresses to fetch", + ) sub.set_defaults(func=print_addresses) - sub = cli_shared.add_command_subparser(subparsers, "ledger", "version", "Get the version of the MultiversX App for Ledger") + sub = cli_shared.add_command_subparser( + subparsers, + "ledger", + "version", + "Get the version of the MultiversX App for Ledger", + ) sub.set_defaults(func=print_version) return subparsers @@ -24,8 +36,8 @@ def setup_parser(subparsers: Any) -> Any: def print_addresses(args: Any): ledger_app = LedgerApp() for i in range(args.num_addresses): - address = ledger_app.get_address(0, i) - print('account index = %d | address index = %d | address: %s' % (0, i, address)) + address = ledger_app.get_address(i) + print("account index = %d | address index = %d | address: %s" % (0, i, address)) ledger_app.close() diff --git a/multiversx_sdk_cli/cli_localnet.py b/multiversx_sdk_cli/cli_localnet.py index ce62507c..2f57a1f0 100644 --- a/multiversx_sdk_cli/cli_localnet.py +++ b/multiversx_sdk_cli/cli_localnet.py @@ -1,23 +1,24 @@ import logging from pathlib import Path -from typing import Any, List +from typing import Any from multiversx_sdk_cli import cli_shared, ux from multiversx_sdk_cli.constants import ONE_YEAR_IN_SECONDS from multiversx_sdk_cli.errors import KnownError -from multiversx_sdk_cli.localnet import (step_build_software, step_clean, - step_config, step_new, - step_prerequisites, step_start) +from multiversx_sdk_cli.localnet import ( + step_build_software, + step_clean, + step_config, + step_new, + step_prerequisites, + step_start, +) logger = logging.getLogger("cli.localnet") -def setup_parser(args: List[str], subparsers: Any) -> Any: - parser = cli_shared.add_group_subparser( - subparsers, - "localnet", - "Set up, start and control localnets" - ) +def setup_parser(args: list[str], subparsers: Any) -> Any: + parser = cli_shared.add_group_subparser(subparsers, "localnet", "Set up, start and control localnets") subparsers = parser.add_subparsers() # Setup @@ -25,18 +26,13 @@ def setup_parser(args: List[str], subparsers: Any) -> Any: subparsers, "localnet", "setup", - "Set up a localnet (runs 'prerequisites', 'build' and 'config' in one go)" + "Set up a localnet (runs 'prerequisites', 'build' and 'config' in one go)", ) add_argument_configfile(sub) sub.set_defaults(func=localnet_setup) # New - sub = cli_shared.add_command_subparser( - subparsers, - "localnet", - "new", - "Create a new localnet configuration" - ) + sub = cli_shared.add_command_subparser(subparsers, "localnet", "new", "Create a new localnet configuration") add_argument_configfile(sub) sub.set_defaults(func=localnet_new) @@ -45,7 +41,7 @@ def setup_parser(args: List[str], subparsers: Any) -> Any: subparsers, "localnet", "prerequisites", - "Download and verify the prerequisites for running a localnet" + "Download and verify the prerequisites for running a localnet", ) add_argument_configfile(sub) sub.set_defaults(func=localnet_prerequisites) @@ -55,21 +51,28 @@ def setup_parser(args: List[str], subparsers: Any) -> Any: subparsers, "localnet", "build", - "Build necessary software for running a localnet" + "Build necessary software for running a localnet", ) add_argument_configfile(sub) - sub.add_argument('--software', choices=["node", "seednode", "proxy"], nargs="+", default=["node", "seednode", "proxy"], help="The software to build (default: %(default)s)") + sub.add_argument( + "--software", + choices=["node", "seednode", "proxy"], + nargs="+", + default=["node", "seednode", "proxy"], + help="The software to build (default: %(default)s)", + ) sub.set_defaults(func=localnet_build) # Start - sub = cli_shared.add_command_subparser( - subparsers, - "localnet", - "start", - "Start a localnet" - ) + sub = cli_shared.add_command_subparser(subparsers, "localnet", "start", "Start a localnet") add_argument_configfile(sub) - sub.add_argument("--stop-after-seconds", type=int, required=False, default=ONE_YEAR_IN_SECONDS, help="Stop the localnet after a given number of seconds (default: %(default)s)") + sub.add_argument( + "--stop-after-seconds", + type=int, + required=False, + default=ONE_YEAR_IN_SECONDS, + help="Stop the localnet after a given number of seconds (default: %(default)s)", + ) sub.set_defaults(func=localnet_start) # Config @@ -77,7 +80,7 @@ def setup_parser(args: List[str], subparsers: Any) -> Any: subparsers, "localnet", "config", - "Configure a localnet (required before starting it the first time or after clean)" + "Configure a localnet (required before starting it the first time or after clean)", ) add_argument_configfile(sub) sub.set_defaults(func=localnet_config) @@ -87,7 +90,7 @@ def setup_parser(args: List[str], subparsers: Any) -> Any: subparsers, "localnet", "clean", - "Erase the currently configured localnet (must be already stopped)" + "Erase the currently configured localnet (must be already stopped)", ) add_argument_configfile(sub) sub.set_defaults(func=localnet_clean) @@ -95,7 +98,13 @@ def setup_parser(args: List[str], subparsers: Any) -> Any: def add_argument_configfile(parser: Any): help_config_file = "An optional configuration file describing the localnet" - parser.add_argument("--configfile", type=Path, required=False, default=Path("localnet.toml"), help=help_config_file) + parser.add_argument( + "--configfile", + type=Path, + required=False, + default=Path("localnet.toml"), + help=help_config_file, + ) def localnet_new(args: Any): @@ -103,7 +112,9 @@ def localnet_new(args: Any): step_new.new_config(args.configfile) - ux.show_message("New localnet configuration file created (or already existing). Make sure to inspect it. In order to fetch localnet prerequisites, run:\n\n$ mxpy localnet prerequisites") + ux.show_message( + "New localnet configuration file created (or already existing). Make sure to inspect it. In order to fetch localnet prerequisites, run:\n\n$ mxpy localnet prerequisites" + ) def localnet_clean(args: Any): @@ -169,5 +180,7 @@ def guard_configfile(args: Any): raise KnownError(f"Localnet config file does not exist: {configfile}") if old_configfile_in_workdir.exists(): - logger.error(f"""For less ambiguity, the old "testnet.toml" config file should be removed: {old_configfile_in_workdir}""") + logger.error( + f"""For less ambiguity, the old "testnet.toml" config file should be removed: {old_configfile_in_workdir}""" + ) raise KnownError(f"""Found old "testnet.toml" config file in working directory: {old_configfile_in_workdir}""") diff --git a/multiversx_sdk_cli/cli_output.py b/multiversx_sdk_cli/cli_output.py index bb2fdc94..2250f489 100644 --- a/multiversx_sdk_cli/cli_output.py +++ b/multiversx_sdk_cli/cli_output.py @@ -1,12 +1,12 @@ import json import logging from collections import OrderedDict -from typing import Any, Dict, List, Optional, Union +from typing import Any, Optional, Union -from multiversx_sdk import TransactionsConverter +from multiversx_sdk import Address, Transaction, TransactionOnNetwork from multiversx_sdk_cli import utils -from multiversx_sdk_cli.interfaces import IAddress, ITransaction +from multiversx_sdk_cli.transactions import transaction_on_network_to_dictionary from multiversx_sdk_cli.utils import ISerializable logger = logging.getLogger("cli.output") @@ -15,30 +15,34 @@ class CLIOutputBuilder: def __init__(self) -> None: self.emitted_transaction_hash: Optional[str] = None - self.emitted_transaction: Union[ITransaction, None] = None - self.emitted_transaction_omitted_fields: List[str] = [] - self.contract_address: Union[IAddress, None] = None - self.transaction_on_network: Union[ISerializable, None] = None - self.transaction_on_network_omitted_fields: List[str] = [] + self.emitted_transaction: Union[Transaction, None] = None + self.emitted_transaction_omitted_fields: list[str] = [] + self.contract_address: Union[Address, None] = None + self.transaction_on_network: Union[TransactionOnNetwork, None] = None + self.transaction_on_network_omitted_fields: list[str] = [] self.simulation_results: Union[ISerializable, None] = None def set_emitted_transaction_hash(self, hash: str): self.emitted_transaction_hash = hash return self - def set_emitted_transaction(self, emitted_transaction: ITransaction, omitted_fields: List[str] = []): + def set_emitted_transaction(self, emitted_transaction: Transaction, omitted_fields: list[str] = []): self.emitted_transaction = emitted_transaction self.emitted_transaction_omitted_fields = omitted_fields return self - def set_contract_address(self, contract_address: IAddress): + def set_contract_address(self, contract_address: Address): self.contract_address = contract_address return self - def set_awaited_transaction(self, awaited_transaction: ISerializable, omitted_fields: List[str] = []): + def set_awaited_transaction(self, awaited_transaction: TransactionOnNetwork, omitted_fields: list[str] = []): return self.set_transaction_on_network(awaited_transaction, omitted_fields) - def set_transaction_on_network(self, transaction_on_network: ISerializable, omitted_fields: List[str] = []): + def set_transaction_on_network( + self, + transaction_on_network: TransactionOnNetwork, + omitted_fields: list[str] = [], + ): self.transaction_on_network = transaction_on_network self.transaction_on_network_omitted_fields = omitted_fields return self @@ -47,12 +51,11 @@ def set_simulation_results(self, simulation_results: ISerializable): self.simulation_results = simulation_results return self - def build(self) -> Dict[str, Any]: - output: Dict[str, Any] = OrderedDict() + def build(self) -> dict[str, Any]: + output: dict[str, Any] = OrderedDict() if self.emitted_transaction: - tx_converter = TransactionsConverter() - emitted_transaction_dict = tx_converter.transaction_to_dictionary(self.emitted_transaction) + emitted_transaction_dict = self.emitted_transaction.to_dictionary() emitted_transaction_hash = self.emitted_transaction_hash or "" emitted_transaction_data = self.emitted_transaction.data.decode() utils.omit_fields(emitted_transaction_dict, self.emitted_transaction_omitted_fields) @@ -66,7 +69,7 @@ def build(self) -> Dict[str, Any]: output["contractAddress"] = contract_address if self.transaction_on_network: - transaction_on_network_dict = self.transaction_on_network.to_dictionary() + transaction_on_network_dict = transaction_on_network_to_dictionary(self.transaction_on_network) utils.omit_fields(transaction_on_network_dict, self.transaction_on_network_omitted_fields) output["transactionOnNetwork"] = transaction_on_network_dict @@ -76,11 +79,22 @@ def build(self) -> Dict[str, Any]: return output @classmethod - def describe(cls, with_emitted: bool = True, with_contract: bool = False, with_transaction_on_network: bool = False, with_simulation: bool = False) -> str: - output: Dict[str, Any] = OrderedDict() + def describe( + cls, + with_emitted: bool = True, + with_contract: bool = False, + with_transaction_on_network: bool = False, + with_simulation: bool = False, + ) -> str: + output: dict[str, Any] = OrderedDict() if with_emitted: - output["emittedTransaction"] = {"nonce": 42, "sender": "alice", "receiver": "bob", "...": "..."} + output["emittedTransaction"] = { + "nonce": 42, + "sender": "alice", + "receiver": "bob", + "...": "...", + } output["emittedTransactionData"] = "the transaction data, not encoded" output["emittedTransactionHash"] = "the transaction hash" @@ -88,13 +102,15 @@ def describe(cls, with_emitted: bool = True, with_contract: bool = False, with_t output["contractAddress"] = "the address of the contract" if with_transaction_on_network: - output["transactionOnNetwork"] = {"nonce": 42, "sender": "alice", "receiver": "bob", "...": "..."} + output["transactionOnNetwork"] = { + "nonce": 42, + "sender": "alice", + "receiver": "bob", + "...": "...", + } if with_simulation: - output["simulation"] = { - "execution": {"...": "..."}, - "cost": {"...": "..."} - } + output["simulation"] = {"execution": {"...": "..."}, "cost": {"...": "..."}} description = json.dumps(output, indent=4) description_wrapped = f""" diff --git a/multiversx_sdk_cli/cli_password.py b/multiversx_sdk_cli/cli_password.py index d7fff56b..98666c31 100644 --- a/multiversx_sdk_cli/cli_password.py +++ b/multiversx_sdk_cli/cli_password.py @@ -14,3 +14,10 @@ def load_guardian_password(args: Any) -> str: with open(args.guardian_passfile) as pass_file: return pass_file.read().strip() return getpass("Keyfile's password: ") + + +def load_relayer_password(args: Any) -> str: + if args.relayer_passfile: + with open(args.relayer_passfile) as pass_file: + return pass_file.read().strip() + return getpass("Keyfile's password: ") diff --git a/multiversx_sdk_cli/cli_shared.py b/multiversx_sdk_cli/cli_shared.py index 5ce01dde..26857ffd 100644 --- a/multiversx_sdk_cli/cli_shared.py +++ b/multiversx_sdk_cli/cli_shared.py @@ -1,27 +1,55 @@ import argparse import ast -import copy import sys from argparse import FileType -from typing import Any, Dict, List, Text, cast +from pathlib import Path +from typing import Any, Text, Union, cast -from multiversx_sdk import Address, ProxyNetworkProvider +from multiversx_sdk import ( + Account, + Address, + LedgerAccount, + ProxyNetworkProvider, + Transaction, +) from multiversx_sdk_cli import config, errors, utils -from multiversx_sdk_cli.accounts import Account, LedgerAccount from multiversx_sdk_cli.cli_output import CLIOutputBuilder -from multiversx_sdk_cli.cli_password import (load_guardian_password, - load_password) -from multiversx_sdk_cli.constants import (DEFAULT_TX_VERSION, - TRANSACTION_OPTIONS_TX_GUARDED) -from multiversx_sdk_cli.errors import ArgumentsNotProvidedError -from multiversx_sdk_cli.interfaces import ITransaction -from multiversx_sdk_cli.ledger.ledger_functions import do_get_ledger_address +from multiversx_sdk_cli.cli_password import ( + load_guardian_password, + load_password, + load_relayer_password, +) +from multiversx_sdk_cli.constants import ( + DEFAULT_GAS_PRICE, + DEFAULT_TX_VERSION, + TCS_SERVICE_ID, +) +from multiversx_sdk_cli.errors import ( + ArgumentsNotProvidedError, + BadUsage, + IncorrectWalletError, +) +from multiversx_sdk_cli.guardian_relayer_data import GuardianRelayerData +from multiversx_sdk_cli.interfaces import IAccount from multiversx_sdk_cli.simulation import Simulator from multiversx_sdk_cli.transactions import send_and_wait_for_result from multiversx_sdk_cli.utils import log_explorer_transaction from multiversx_sdk_cli.ux import show_warning +trusted_cosigner_service_url_by_chain_id = { + "1": "https://tools.multiversx.com/guardian", + "D": "https://devnet-tools.multiversx.com/guardian", + "T": "https://testnet-tools.multiversx.com/guardian", +} + + +def get_trusted_cosigner_service_url_by_chain_id(chain_id: str) -> str: + try: + return trusted_cosigner_service_url_by_chain_id[chain_id] + except: + raise BadUsage(f"Could not get Trusted Cosigner Service Url. No match found for chain id: {chain_id}") + def wider_help_formatter(prog: Text): return argparse.RawDescriptionHelpFormatter(prog, max_help_position=50, width=120) @@ -32,7 +60,7 @@ def add_group_subparser(subparsers: Any, group: str, description: str) -> Any: group, usage=f"mxpy {group} COMMAND [-h] ...", description=description, - formatter_class=argparse.RawDescriptionHelpFormatter + formatter_class=argparse.RawDescriptionHelpFormatter, ) parser._positionals.title = "COMMANDS" parser._optionals.title = "OPTIONS" @@ -58,93 +86,169 @@ def add_command_subparser(subparsers: Any, group: str, command: str, description command, usage=f"mxpy {group} {command} [-h] ...", description=description, - formatter_class=wider_help_formatter + formatter_class=wider_help_formatter, ) def add_tx_args( - args: List[str], - sub: Any, - with_nonce: bool = True, - with_receiver: bool = True, - with_data: bool = True, - with_estimate_gas: bool = False, - with_relayer_wallet_args: bool = True): + args: list[str], + sub: Any, + with_nonce: bool = True, + with_receiver: bool = True, + with_data: bool = True, +): if with_nonce: - sub.add_argument("--nonce", type=int, required=not ("--recall-nonce" in args), help="# the nonce for the transaction") - sub.add_argument("--recall-nonce", action="store_true", default=False, help="⭮ whether to recall the nonce when creating the transaction (default: %(default)s)") + sub.add_argument( + "--nonce", + type=int, + required=False, + default=None, + help="# the nonce for the transaction", + ) + sub.add_argument( + "--recall-nonce", + action="store_true", + default=False, + help="⭮ whether to recall the nonce when creating the transaction (default: %(default)s)", + ) if with_receiver: - sub.add_argument("--receiver", required=True, help="🖄 the address of the receiver") + sub.add_argument("--receiver", required=False, help="🖄 the address of the receiver") sub.add_argument("--receiver-username", required=False, help="🖄 the username of the receiver") - sub.add_argument("--gas-price", default=config.DEFAULT_GAS_PRICE, help="⛽ the gas price (default: %(default)d)") - sub.add_argument("--gas-limit", required=not ("--estimate-gas" in args), help="⛽ the gas limit") - if with_estimate_gas: - sub.add_argument("--estimate-gas", action="store_true", default=False, help="⛽ whether to estimate the gas limit (default: %(default)d)") + sub.add_argument( + "--gas-price", + default=DEFAULT_GAS_PRICE, + type=int, + help="⛽ the gas price (default: %(default)d)", + ) + sub.add_argument("--gas-limit", required=False, type=int, help="⛽ the gas limit") - sub.add_argument("--value", default="0", help="the value to transfer (default: %(default)s)") + sub.add_argument("--value", default="0", type=int, help="the value to transfer (default: %(default)s)") if with_data: - sub.add_argument("--data", default="", help="the payload, or 'memo' of the transaction (default: %(default)s)") - - sub.add_argument("--chain", help="the chain identifier") - sub.add_argument("--version", type=int, default=DEFAULT_TX_VERSION, help="the transaction version (default: %(default)s)") - - sub.add_argument("--relayer", help="the bech32 address of the relayer") - if with_relayer_wallet_args: - add_relayed_v3_wallet_args(args, sub) - - add_guardian_args(sub) - - sub.add_argument("--options", type=int, default=0, help="the transaction options (default: 0)") - + sub.add_argument( + "--data", + default="", + help="the payload, or 'memo' of the transaction (default: %(default)s)", + ) + + sub.add_argument("--chain", type=str, help="the chain identifier") + sub.add_argument( + "--version", + type=int, + default=DEFAULT_TX_VERSION, + help="the transaction version (default: %(default)s)", + ) + sub.add_argument("--options", type=int, default=0, help="the transaction options (default: %(default)s)") -def add_guardian_args(sub: Any): - sub.add_argument("--guardian", type=str, help="the address of the guradian", default="") - sub.add_argument("--guardian-service-url", type=str, help="the url of the guardian service", default="") - sub.add_argument("--guardian-2fa-code", type=str, help="the 2fa code for the guardian", default="") + sub.add_argument("--relayer", type=str, help="the bech32 address of the relayer", default="") + sub.add_argument("--guardian", type=str, help="the bech32 address of the guardian", default="") -def add_wallet_args(args: List[str], sub: Any): - sub.add_argument("--pem", required=check_if_sign_method_required(args, "--pem"), help="🔑 the PEM file, if keyfile not provided") - sub.add_argument("--pem-index", type=int, default=0, help="🔑 the index in the PEM file (default: %(default)s)") - sub.add_argument("--keyfile", required=check_if_sign_method_required(args, "--keyfile"), help="🔑 a JSON keyfile, if PEM not provided") - sub.add_argument("--passfile", help="🔑 a file containing keyfile's password, if keyfile provided") - sub.add_argument("--ledger", action="store_true", required=check_if_sign_method_required(args, "--ledger"), default=False, help="🔐 bool flag for signing transaction using ledger") - sub.add_argument("--ledger-account-index", type=int, default=0, help="🔐 the index of the account when using Ledger") - sub.add_argument("--ledger-address-index", type=int, default=0, help="🔐 the index of the address when using Ledger") +def add_wallet_args(args: list[str], sub: Any): + sub.add_argument( + "--pem", + required=False, + help="🔑 the PEM file, if keyfile not provided", + ) + sub.add_argument( + "--keyfile", + required=False, + help="🔑 a JSON keyfile, if PEM not provided", + ) + sub.add_argument( + "--passfile", + help="🔑 a file containing keyfile's password, if keyfile provided. If not provided, you'll be prompted to enter the password.", + ) + sub.add_argument( + "--ledger", + action="store_true", + required=False, + default=False, + help="🔐 bool flag for signing transaction using ledger", + ) + sub.add_argument( + "--sender-wallet-index", + type=int, + default=0, + help="🔑 the address index; can be used for PEM files, keyfiles of type mnemonic or Ledger devices (default: %(default)s)", + ) sub.add_argument("--sender-username", required=False, help="🖄 the username of the sender") -def add_guardian_wallet_args(args: List[str], sub: Any): - sub.add_argument("--guardian-pem", required=check_if_sign_method_required(args, "--guardian-pem"), help="🔑 the PEM file, if keyfile not provided") - sub.add_argument("--guardian-pem-index", type=int, default=0, help="🔑 the index in the PEM file (default: %(default)s)") - sub.add_argument("--guardian-keyfile", required=check_if_sign_method_required(args, "--guardian-keyfile"), help="🔑 a JSON keyfile, if PEM not provided") - sub.add_argument("--guardian-passfile", help="🔑 a file containing keyfile's password, if keyfile provided") - sub.add_argument("--guardian-ledger", action="store_true", required=check_if_sign_method_required(args, "--guardian-ledger"), default=False, help="🔐 bool flag for signing transaction using ledger") - sub.add_argument("--guardian-ledger-account-index", type=int, default=0, help="🔐 the index of the account when using Ledger") - sub.add_argument("--guardian-ledger-address-index", type=int, default=0, help="🔐 the index of the address when using Ledger") +def add_guardian_wallet_args(args: list[str], sub: Any): + sub.add_argument( + "--guardian-service-url", + type=str, + help="the url of the guardian service", + default="", + ) + sub.add_argument( + "--guardian-2fa-code", + type=str, + help="the 2fa code for the guardian", + default="", + ) + sub.add_argument( + "--guardian-pem", + help="🔑 the PEM file, if keyfile not provided", + ) + sub.add_argument( + "--guardian-keyfile", + help="🔑 a JSON keyfile, if PEM not provided", + ) + sub.add_argument( + "--guardian-passfile", + help="🔑 a file containing keyfile's password, if keyfile provided. If not provided, you'll be prompted to enter the password.", + ) + sub.add_argument( + "--guardian-ledger", + action="store_true", + default=False, + help="🔐 bool flag for signing transaction using ledger", + ) + sub.add_argument( + "--guardian-wallet-index", + type=int, + default=0, + help="🔑 the address index; can be used for PEM files, keyfiles of type mnemonic or Ledger devices (default: %(default)s)", + ) -# Required check not properly working, same for guardian. Will be refactored in the future. -def add_relayed_v3_wallet_args(args: List[str], sub: Any): +def add_relayed_v3_wallet_args(args: list[str], sub: Any): sub.add_argument("--relayer-pem", help="🔑 the PEM file, if keyfile not provided") - sub.add_argument("--relayer-pem-index", type=int, default=0, help="🔑 the index in the PEM file (default: %(default)s)") sub.add_argument("--relayer-keyfile", help="🔑 a JSON keyfile, if PEM not provided") - sub.add_argument("--relayer-passfile", help="🔑 a file containing keyfile's password, if keyfile provided") - sub.add_argument("--relayer-ledger", action="store_true", default=False, help="🔐 bool flag for signing transaction using ledger") - sub.add_argument("--relayer-ledger-account-index", type=int, default=0, help="🔐 the index of the account when using Ledger") - sub.add_argument("--relayer-ledger-address-index", type=int, default=0, help="🔐 the index of the address when using Ledger") + sub.add_argument( + "--relayer-passfile", + help="🔑 a file containing keyfile's password, if keyfile provided. If not provided, you'll be prompted to enter the password.", + ) + sub.add_argument( + "--relayer-ledger", + action="store_true", + default=False, + help="🔐 bool flag for signing transaction using ledger", + ) + sub.add_argument( + "--relayer-wallet-index", + type=int, + default=0, + help="🔑 the address index; can be used for PEM files, keyfiles of type mnemonic or Ledger devices (default: %(default)s)", + ) def add_proxy_arg(sub: Any): - sub.add_argument("--proxy", help="🔗 the URL of the proxy") + sub.add_argument("--proxy", type=str, help="🔗 the URL of the proxy") def add_outfile_arg(sub: Any, what: str = ""): what = f"({what})" if what else "" - sub.add_argument("--outfile", type=FileType("w"), default=sys.stdout, help=f"where to save the output {what} (default: stdout)") + sub.add_argument( + "--outfile", + type=FileType("w"), + default=sys.stdout, + help=f"where to save the output {what} (default: stdout)", + ) def add_infile_arg(sub: Any, what: str = ""): @@ -153,144 +257,252 @@ def add_infile_arg(sub: Any, what: str = ""): def add_omit_fields_arg(sub: Any): - sub.add_argument("--omit-fields", default="[]", type=str, required=False, help="omit fields in the output payload (default: %(default)s)") + sub.add_argument( + "--omit-fields", + default="[]", + type=str, + required=False, + help="omit fields in the output payload (default: %(default)s); fields should be passed as a string containing a list of fields (e.g. \"['field1', 'field2']\")", + ) def add_token_transfers_args(sub: Any): - sub.add_argument("--token-transfers", nargs='+', - help="token transfers for transfer & execute, as [token, amount] " - "E.g. --token-transfers NFT-123456-0a 1 ESDT-987654 100000000") + sub.add_argument( + "--token-transfers", + nargs="+", + help="token transfers for transfer & execute, as [token, amount] " + "E.g. --token-transfers NFT-123456-0a 1 ESDT-987654 100000000", + ) -def parse_omit_fields_arg(args: Any) -> List[str]: +def parse_omit_fields_arg(args: Any) -> list[str]: literal = args.omit_fields parsed = ast.literal_eval(literal) - return cast(List[str], parsed) + return cast(list[str], parsed) def prepare_account(args: Any): + hrp = config.get_address_hrp() + if args.pem: - account = Account(pem_file=args.pem, pem_index=args.pem_index) + return Account.new_from_pem(file_path=Path(args.pem), index=args.sender_wallet_index, hrp=hrp) elif args.keyfile: password = load_password(args) - account = Account(key_file=args.keyfile, password=password) + index = args.sender_wallet_index if args.sender_wallet_index != 0 else None + + return Account.new_from_keystore( + file_path=Path(args.keyfile), + password=password, + address_index=index, + hrp=hrp, + ) elif args.ledger: - account = LedgerAccount(account_index=args.ledger_account_index, address_index=args.ledger_address_index) - else: - raise errors.NoWalletProvided() - - return account - - -def prepare_relayer_account(args: Any) -> Account: - if args.relayer_ledger: - account = LedgerAccount(account_index=args.relayer_ledger_account_index, address_index=args.relayer_ledger_address_index) - if args.relayer_pem: - account = Account(pem_file=args.relayer_pem, pem_index=args.relayer_pem_index) - elif args.relayer_keyfile: - password = load_password(args) - account = Account(key_file=args.relayer_keyfile, password=password) + return LedgerAccount(address_index=args.sender_wallet_index) else: raise errors.NoWalletProvided() - return account +def load_guardian_account(args: Any) -> Union[IAccount, None]: + hrp = config.get_address_hrp() -def prepare_guardian_account(args: Any): if args.guardian_pem: - account = Account(pem_file=args.guardian_pem, pem_index=args.guardian_pem_index) + return Account.new_from_pem(file_path=Path(args.guardian_pem), index=args.guardian_wallet_index, hrp=hrp) elif args.guardian_keyfile: password = load_guardian_password(args) - account = Account(key_file=args.guardian_keyfile, password=password) + index = args.guardian_wallet_index if args.guardian_wallet_index != 0 else None + + return Account.new_from_keystore( + file_path=Path(args.guardian_keyfile), + password=password, + address_index=index, + hrp=hrp, + ) elif args.guardian_ledger: - address = do_get_ledger_address(account_index=args.guardian_ledger_account_index, address_index=args.guardian_ledger_address_index) - account = Account(Address.new_from_bech32(address)) + return LedgerAccount(address_index=args.guardian_wallet_index) + + return None + + +def get_guardian_address(guardian: Union[IAccount, None], args: Any) -> Union[Address, None]: + address_from_account = guardian.address if guardian else None + address_from_args = Address.new_from_bech32(args.guardian) if hasattr(args, "guardian") and args.guardian else None + + if not _is_matching_address(address_from_account, address_from_args): + raise IncorrectWalletError("Guardian wallet does not match the guardian's address set in the arguments.") + + return address_from_account or address_from_args + + +def get_guardian_and_relayer_data(sender: str, args: Any) -> GuardianRelayerData: + guardian = load_guardian_account(args) + + # get guardian address from account or from cli args + guardian_address = get_guardian_address(guardian, args) + + relayer = load_relayer_account(args) + relayer_address = get_relayer_address(relayer, args) + + guardian_and_relayer_data = GuardianRelayerData( + guardian=guardian, + guardian_address=guardian_address, + relayer=relayer, + relayer_address=relayer_address, + ) + + if guardian_and_relayer_data.guardian_address: + guardian_and_relayer_data.guardian_service_url = args.guardian_service_url + guardian_and_relayer_data.guardian_2fa_code = args.guardian_2fa_code else: - raise errors.NoWalletProvided() + _get_guardian_data_from_network(sender, args, guardian_and_relayer_data) - return account + return guardian_and_relayer_data -def prepare_nonce_in_args(args: Any): - if args.recall_nonce and not args.proxy: - raise ArgumentsNotProvidedError("When using `--recall-nonce`, `--proxy` must be provided, as well") +def _get_guardian_data_from_network(sender: str, args: Any, guardian_and_relayer_data: GuardianRelayerData): + """Updates the `guardian_and_relayer_data` parameter, that is later used.""" - if args.recall_nonce: - account = prepare_account(args) - network_provider_config = config.get_config_for_network_providers() - account.sync_nonce(ProxyNetworkProvider(url=args.proxy, config=network_provider_config)) - args.nonce = account.nonce + # if guardian not provided, get guardian from the network + guardian_data = _get_guardian_data(sender, args.proxy) + if guardian_data: + guardian_and_relayer_data.guardian_address = Address.new_from_bech32(guardian_data["guardian_address"]) -def prepare_chain_id_in_args(args: Any): - if not args.chain and not args.proxy: - raise ArgumentsNotProvidedError("chain ID cannot be decided: `--chain` or `--proxy` should be provided") + # if tcs is used, set url, else get service url from args + tcs_url = guardian_data["cosigner_service_url"] + guardian_and_relayer_data.guardian_service_url = tcs_url if tcs_url else args.guardian_service_url - if args.chain and args.proxy: - network_provider_config = config.get_config_for_network_providers() - proxy = ProxyNetworkProvider(url=args.proxy, config=network_provider_config) - fetched_chain_id = proxy.get_network_config().chain_id + if guardian_and_relayer_data.guardian_service_url: + guardian_and_relayer_data.guardian_2fa_code = _ask_for_2fa_code(args) - if args.chain != fetched_chain_id: - show_warning(f"The chain ID you have provided does not match the chain ID you got from the proxy. Will use the proxy's value: '{fetched_chain_id}'") - args.chain = fetched_chain_id - return - # if the CLI provided chain ID is correct, we do not patch the arguments - return - if args.chain: - return - elif args.proxy: - network_provider_config = config.get_config_for_network_providers() - proxy = ProxyNetworkProvider(url=args.proxy, config=network_provider_config) - args.chain = proxy.get_network_config().chain_id +def _get_guardian_data(address: str, proxy_url: str) -> Union[dict[str, str], None]: + if not proxy_url: + return None + guardian_data = _fetch_guardian_data(address, proxy_url) -def add_broadcast_args(sub: Any, simulate: bool = True, relay: bool = False): - sub.add_argument("--send", action="store_true", default=False, help="✓ whether to broadcast the transaction (default: %(default)s)") + if not bool(guardian_data.get("guarded", "")): + return None - if simulate: - sub.add_argument("--simulate", action="store_true", default=False, help="whether to simulate the transaction (default: %(default)s)") - if relay: - sub.add_argument("--relay", action="store_true", default=False, help="whether to relay the transaction (default: %(default)s)") + active_guardian = guardian_data.get("activeGuardian", {}) + + guardian_address = active_guardian.get("address", "") + service_id = active_guardian.get("serviceUID", "") + + cosigner_service_url = "" + + if service_id == TCS_SERVICE_ID: + chain_id = _fetch_chain_id(proxy_url) + cosigner_service_url = get_trusted_cosigner_service_url_by_chain_id(chain_id) + return { + "guardian_address": guardian_address, + "cosigner_service_url": cosigner_service_url, + } -def check_broadcast_args(args: Any): - if hasattr(args, "relay") and args.relay and args.send: - raise errors.BadUsage("Cannot directly send a relayed transaction. Use 'mxpy tx new --relay' first, then 'mxpy tx send --data-file'") - if args.send and args.simulate: - raise errors.BadUsage("Cannot both 'simulate' and 'send' a transaction") + +def _fetch_guardian_data(address: str, proxy_url: str) -> dict[str, Any]: + network_provider_config = config.get_config_for_network_providers() + proxy = ProxyNetworkProvider(url=proxy_url, config=network_provider_config) + + response = proxy.do_get_generic(f"/address/{address}/guardian-data").to_dictionary() + guardian_data: dict[str, Any] = response.get("guardianData", {}) + return guardian_data -def check_guardian_and_options_args(args: Any): - check_guardian_args(args) - if args.guardian: - check_options_for_guarded_tx(args.options) +def _ask_for_2fa_code(args: Any) -> str: + code: str = args.guardian_2fa_code + if not code: + code = input("Please enter the two factor authentication code: ") + return code -def check_guardian_args(args: Any): - if args.guardian: - if should_sign_with_cosigner_service(args) and should_sign_with_guardian_key(args): - raise errors.BadUsage("Guarded tx should be signed using either a cosigning service or a guardian key") +def get_relayer_address(relayer: Union[IAccount, None], args: Any) -> Union[Address, None]: + address_from_account = relayer.address if relayer else None + address_from_args = Address.new_from_bech32(args.relayer) if hasattr(args, "relayer") and args.relayer else None - if not should_sign_with_cosigner_service(args) and not should_sign_with_guardian_key(args): - raise errors.BadUsage("Missing guardian signing arguments") + if not _is_matching_address(address_from_account, address_from_args): + raise IncorrectWalletError("Relayer wallet does not match the relayer's address set in the arguments.") + return address_from_account or address_from_args -def should_sign_with_cosigner_service(args: Any) -> bool: - return all([args.guardian_service_url, args.guardian_2fa_code]) +def _is_matching_address(account_address: Union[Address, None], args_address: Union[Address, None]) -> bool: + if account_address and args_address and account_address != args_address: + return False + return True -def should_sign_with_guardian_key(args: Any) -> bool: - return any([args.guardian_pem, args.guardian_keyfile, args.guardian_ledger]) +def load_relayer_account(args: Any) -> Union[IAccount, None]: + hrp = config.get_address_hrp() -def check_options_for_guarded_tx(options: int): - if not options & TRANSACTION_OPTIONS_TX_GUARDED == TRANSACTION_OPTIONS_TX_GUARDED: - raise errors.BadUsage("Invalid guarded transaction's options. The second least significant bit must be set") + if args.relayer_pem: + return Account.new_from_pem(file_path=Path(args.relayer_pem), index=args.relayer_wallet_index, hrp=hrp) + elif args.relayer_keyfile: + password = load_relayer_password(args) + index = args.relayer_wallet_index if args.relayer_wallet_index != 0 else None + return Account.new_from_keystore( + file_path=Path(args.relayer_keyfile), + password=password, + address_index=index, + hrp=hrp, + ) + elif args.relayer_ledger: + return LedgerAccount(address_index=args.relayer_wallet_index) + + return None + + +def get_current_nonce_for_address(address: Address, proxy_url: Union[str, None]) -> int: + if not proxy_url: + raise ArgumentsNotProvidedError("If `--nonce` is not explicitly provided, `--proxy` must be provided") -def send_or_simulate(tx: ITransaction, args: Any, dump_output: bool = True) -> CLIOutputBuilder: + network_provider_config = config.get_config_for_network_providers() + proxy = ProxyNetworkProvider(url=proxy_url, config=network_provider_config) + return proxy.get_account(address).nonce + + +def get_chain_id(chain_id: str, proxy_url: str) -> str: + if chain_id and proxy_url: + fetched_chain_id = _fetch_chain_id(proxy_url) + + if chain_id != fetched_chain_id: + show_warning( + f"The chain ID you have provided does not match the chain ID you got from the proxy. Will use the proxy's value: '{fetched_chain_id}'" + ) + return fetched_chain_id + + if chain_id: + return chain_id + + return _fetch_chain_id(proxy_url) + + +def _fetch_chain_id(proxy_url: str) -> str: + network_provider_config = config.get_config_for_network_providers() + proxy = ProxyNetworkProvider(url=proxy_url, config=network_provider_config) + return proxy.get_network_config().chain_id + + +def add_broadcast_args(sub: Any, simulate: bool = True): + sub.add_argument( + "--send", + action="store_true", + default=False, + help="✓ whether to broadcast the transaction (default: %(default)s)", + ) + + if simulate: + sub.add_argument( + "--simulate", + action="store_true", + default=False, + help="whether to simulate the transaction (default: %(default)s)", + ) + + +def send_or_simulate(tx: Transaction, args: Any, dump_output: bool = True) -> CLIOutputBuilder: network_provider_config = config.get_config_for_network_providers() proxy = ProxyNetworkProvider(url=args.proxy, config=network_provider_config) @@ -312,7 +524,7 @@ def send_or_simulate(tx: ITransaction, args: Any, dump_output: bool = True) -> C output_builder.set_awaited_transaction(transaction_on_network) elif send_only: hash = proxy.send_transaction(tx) - output_builder.set_emitted_transaction_hash(hash) + output_builder.set_emitted_transaction_hash(hash.hex()) elif simulate: simulation = Simulator(proxy).run(tx) output_builder.set_simulation_results(simulation) @@ -325,42 +537,44 @@ def send_or_simulate(tx: ITransaction, args: Any, dump_output: bool = True) -> C if send_only: log_explorer_transaction( chain=output_transaction["emittedTransaction"]["chainID"], - transaction_hash=output_transaction["emittedTransactionHash"] + transaction_hash=output_transaction["emittedTransactionHash"], ) return output_builder -def check_if_sign_method_required(args: List[str], checked_method: str) -> bool: - methods = ["--pem", "--keyfile", "--ledger"] - rest_of_methods: List[str] = [] - for method in methods: - if method != checked_method: - rest_of_methods.append(method) - - for method in rest_of_methods: - if utils.is_arg_present(args, method): - return False - - return True - +def prepare_sender(args: Any): + """Returns the sender's account. + If no account was provided, will raise an exception.""" + sender = prepare_account(args) + sender.nonce = ( + int(args.nonce) if args.nonce is not None else get_current_nonce_for_address(sender.address, args.proxy) + ) + return sender -def convert_args_object_to_args_list(args: Any) -> List[str]: - arguments = copy.deepcopy(args) - args_dict: Dict[str, Any] = arguments.__dict__ - # delete the function key because we don't need to pass it along - args_dict.pop("func", None) +def prepare_guardian(args: Any) -> tuple[Union[IAccount, None], Union[Address, None]]: + """Reurns a tuple containing the guardians's account and the account's address. + If no account or address were provided, will return (None, None).""" + guardian = load_guardian_account(args) + guardian_address = get_guardian_address(guardian, args) + return guardian, guardian_address - args_list: List[str] = [] - for key, val in args_dict.items(): - modified_key = "--" + key.replace("_", "-") - if isinstance(val, bool) and val: - args_list.extend([modified_key]) - continue +def prepare_relayer(args: Any) -> tuple[Union[IAccount, None], Union[Address, None]]: + """Reurns a tuple containing the relayer's account and the account's address. + If no account or address were provided, will return (None, None).""" + relayer = load_relayer_account(args) + relayer_address = get_relayer_address(relayer, args) + return relayer, relayer_address - if val: - args_list.extend([modified_key, val]) - return args_list +def prepare_guardian_relayer_data(args: Any) -> GuardianRelayerData: + guardian, guardian_address = prepare_guardian(args) + relayer, relayer_address = prepare_relayer(args) + return GuardianRelayerData( + guardian=guardian, + guardian_address=guardian_address, + relayer=relayer, + relayer_address=relayer_address, + ) diff --git a/multiversx_sdk_cli/cli_transactions.py b/multiversx_sdk_cli/cli_transactions.py index 06c7a0b9..27785f43 100644 --- a/multiversx_sdk_cli/cli_transactions.py +++ b/multiversx_sdk_cli/cli_transactions.py @@ -1,63 +1,101 @@ import logging from pathlib import Path -from typing import Any, List +from typing import Any -from multiversx_sdk import ProxyNetworkProvider +from multiversx_sdk import ( + Address, + ProxyNetworkProvider, + Token, + TokenComputer, + TokenTransfer, + TransactionComputer, +) from multiversx_sdk_cli import cli_shared, utils +from multiversx_sdk_cli.args_validation import ( + ensure_relayer_wallet_args_are_provided, + ensure_wallet_args_are_provided, + validate_broadcast_args, + validate_chain_id_args, + validate_nonce_args, + validate_proxy_argument, + validate_receiver_args, +) +from multiversx_sdk_cli.base_transactions_controller import BaseTransactionsController from multiversx_sdk_cli.cli_output import CLIOutputBuilder from multiversx_sdk_cli.config import get_config_for_network_providers -from multiversx_sdk_cli.cosign_transaction import cosign_transaction -from multiversx_sdk_cli.errors import IncorrectWalletError, NoWalletProvided -from multiversx_sdk_cli.transactions import (compute_relayed_v1_data, - do_prepare_transaction, - load_transaction_from_file) +from multiversx_sdk_cli.errors import BadUsage, IncorrectWalletError, NoWalletProvided +from multiversx_sdk_cli.transactions import ( + TransactionsController, + load_transaction_from_file, +) logger = logging.getLogger("cli.transactions") -def setup_parser(args: List[str], subparsers: Any) -> Any: +def setup_parser(args: list[str], subparsers: Any) -> Any: parser = cli_shared.add_group_subparser(subparsers, "tx", "Create and broadcast Transactions") subparsers = parser.add_subparsers() - sub = cli_shared.add_command_subparser(subparsers, "tx", "new", f"Create a new transaction.{CLIOutputBuilder.describe()}") + sub = cli_shared.add_command_subparser( + subparsers, + "tx", + "new", + f"Create a new transaction.{CLIOutputBuilder.describe()}", + ) _add_common_arguments(args, sub) cli_shared.add_token_transfers_args(sub) cli_shared.add_outfile_arg(sub, what="signed transaction, hash") - cli_shared.add_broadcast_args(sub, relay=True) + cli_shared.add_broadcast_args(sub) cli_shared.add_proxy_arg(sub) cli_shared.add_guardian_wallet_args(args, sub) - sub.add_argument("--wait-result", action="store_true", default=False, - help="signal to wait for the transaction result - only valid if --send is set") - sub.add_argument("--timeout", default=100, help="max num of seconds to wait for result" - " - only valid if --wait-result is set") + cli_shared.add_relayed_v3_wallet_args(args, sub) + + sub.add_argument( + "--wait-result", + action="store_true", + default=False, + help="signal to wait for the transaction result - only valid if --send is set", + ) + sub.add_argument( + "--timeout", + default=100, + help="max num of seconds to wait for result" " - only valid if --wait-result is set", + ) sub.set_defaults(func=create_transaction) - sub = cli_shared.add_command_subparser(subparsers, "tx", "send", f"Send a previously saved transaction.{CLIOutputBuilder.describe()}") + sub = cli_shared.add_command_subparser( + subparsers, + "tx", + "send", + f"Send a previously saved transaction.{CLIOutputBuilder.describe()}", + ) cli_shared.add_infile_arg(sub, what="a previously saved transaction") cli_shared.add_outfile_arg(sub, what="the hash") cli_shared.add_proxy_arg(sub) sub.set_defaults(func=send_transaction) - sub = cli_shared.add_command_subparser(subparsers, "tx", "get", f"Get a transaction.{CLIOutputBuilder.describe(with_emitted=False, with_transaction_on_network=True)}") - sub.add_argument("--hash", required=True, help="the hash") - sub.add_argument("--sender", required=False, help="the sender address") - sub.add_argument("--with-results", action="store_true", help="will also return the results of transaction") - cli_shared.add_proxy_arg(sub) - cli_shared.add_omit_fields_arg(sub) - sub.set_defaults(func=get_transaction) - - sub = cli_shared.add_command_subparser(subparsers, "tx", "sign", f"Sign a previously saved transaction.{CLIOutputBuilder.describe()}") - cli_shared.add_wallet_args(args, sub) + sub = cli_shared.add_command_subparser( + subparsers, + "tx", + "sign", + f"Sign a previously saved transaction.{CLIOutputBuilder.describe()}", + ) + cli_shared.add_wallet_args(args=args, sub=sub) cli_shared.add_infile_arg(sub, what="a previously saved transaction") cli_shared.add_outfile_arg(sub, what="the signed transaction") - cli_shared.add_broadcast_args(sub, relay=True) + cli_shared.add_broadcast_args(sub) cli_shared.add_proxy_arg(sub) - cli_shared.add_guardian_args(sub) cli_shared.add_guardian_wallet_args(args, sub) + cli_shared.add_relayed_v3_wallet_args(args, sub) sub.set_defaults(func=sign_transaction) - sub = cli_shared.add_command_subparser(subparsers, "tx", "relay", f"Relay a previously saved transaction.{CLIOutputBuilder.describe()}") + sub = cli_shared.add_command_subparser( + subparsers, + "tx", + "relay", + f"Relay a previously saved transaction.{CLIOutputBuilder.describe()}", + ) cli_shared.add_relayed_v3_wallet_args(args, sub) cli_shared.add_infile_arg(sub, what="a previously saved transaction") cli_shared.add_outfile_arg(sub, what="the relayer signed transaction") @@ -69,35 +107,72 @@ def setup_parser(args: List[str], subparsers: Any) -> Any: return subparsers -def _add_common_arguments(args: List[str], sub: Any): +def _add_common_arguments(args: list[str], sub: Any): cli_shared.add_wallet_args(args, sub) cli_shared.add_tx_args(args, sub) sub.add_argument("--data-file", type=str, default=None, help="a file containing transaction data") def create_transaction(args: Any): - args = utils.as_object(args) - - cli_shared.check_guardian_and_options_args(args) - cli_shared.check_broadcast_args(args) - cli_shared.prepare_chain_id_in_args(args) - cli_shared.prepare_nonce_in_args(args) + validate_nonce_args(args) + validate_receiver_args(args) + ensure_wallet_args_are_provided(args) + validate_broadcast_args(args) + validate_chain_id_args(args) + + sender = cli_shared.prepare_sender(args) + guardian_and_relayer_data = cli_shared.get_guardian_and_relayer_data( + sender=sender.address.to_bech32(), + args=args, + ) if args.data_file: args.data = Path(args.data_file).read_text() - tx = do_prepare_transaction(args) - - if hasattr(args, "relay") and args.relay: - logger.warning("RelayedV1 transactions are deprecated. Please use RelayedV3 instead.") - args.outfile.write(compute_relayed_v1_data(tx)) - return + native_amount = int(args.value) + gas_limit = int(args.gas_limit) if args.gas_limit else 0 + + transfers = getattr(args, "token_transfers", None) + transfers = prepare_token_transfers(transfers) if transfers else None + + chain_id = cli_shared.get_chain_id(args.chain, args.proxy) + tx_controller = TransactionsController(chain_id) + + tx = tx_controller.create_transaction( + sender=sender, + receiver=Address.new_from_bech32(args.receiver), + native_amount=native_amount, + gas_limit=gas_limit, + gas_price=args.gas_price, + nonce=sender.nonce, + version=args.version, + options=args.options, + token_transfers=transfers, + data=args.data, + guardian_and_relayer_data=guardian_and_relayer_data, + ) cli_shared.send_or_simulate(tx, args) +def prepare_token_transfers(transfers: list[Any]) -> list[TokenTransfer]: + token_computer = TokenComputer() + token_transfers: list[TokenTransfer] = [] + + for i in range(0, len(transfers) - 1, 2): + identifier = transfers[i] + amount = int(transfers[i + 1]) + nonce = token_computer.extract_nonce_from_extended_identifier(identifier) + + token = Token(identifier, nonce) + transfer = TokenTransfer(token, amount) + token_transfers.append(transfer) + + return token_transfers + + def send_transaction(args: Any): - args = utils.as_object(args) + validate_proxy_argument(args) tx = load_transaction_from_file(args.infile) output = CLIOutputBuilder() @@ -107,68 +182,64 @@ def send_transaction(args: Any): try: tx_hash = proxy.send_transaction(tx) - output.set_emitted_transaction_hash(tx_hash) + output.set_emitted_transaction_hash(tx_hash.hex()) finally: output = output.set_emitted_transaction(tx).build() utils.dump_out_json(output, outfile=args.outfile) -def get_transaction(args: Any): - args = utils.as_object(args) - omit_fields = cli_shared.parse_omit_fields_arg(args) - - config = get_config_for_network_providers() - proxy = ProxyNetworkProvider(url=args.proxy, config=config) - - transaction = proxy.get_transaction(args.hash, True) - output = CLIOutputBuilder().set_transaction_on_network(transaction, omit_fields).build() - utils.dump_out_json(output) - - def sign_transaction(args: Any): - args = utils.as_object(args) - - cli_shared.check_guardian_args(args) - cli_shared.check_broadcast_args(args) + validate_broadcast_args(args) tx = load_transaction_from_file(args.infile) - if args.guardian: - cli_shared.check_options_for_guarded_tx(tx.options) - - account = cli_shared.prepare_account(args) - tx.signature = bytes.fromhex(account.sign_transaction(tx)) try: - guardian_account = cli_shared.prepare_guardian_account(args) + sender = cli_shared.prepare_account(args) except NoWalletProvided: - guardian_account = None - - if guardian_account: - tx.guardian_signature = bytes.fromhex(guardian_account.sign_transaction(tx)) - elif args.guardian: - tx = cosign_transaction(tx, args.guardian_service_url, args.guardian_2fa_code) + sender = None + + if sender and sender.address != tx.sender: + raise IncorrectWalletError("Sender's wallet does not match transaction's sender.") + + relayer = cli_shared.load_relayer_account(args) + if relayer and relayer.address != tx.relayer: + raise IncorrectWalletError("Relayer's wallet does not match transaction's relayer.") + + guardian = cli_shared.load_guardian_account(args) + if guardian: + if guardian.address != tx.guardian: + raise IncorrectWalletError("Guardian's wallet does not match transaction's guardian.") + + tx_computer = TransactionComputer() + if tx.guardian and not tx_computer.has_options_set_for_guarded_transaction(tx): + raise BadUsage("Guardian wallet provided but the transaction has incorrect options.") + + tx_controller = BaseTransactionsController() + tx_controller.sign_transaction( + transaction=tx, + sender=sender, + guardian=guardian, + relayer=relayer, + guardian_service_url=args.guardian_service_url, + guardian_2fa_code=args.guardian_2fa_code, + ) cli_shared.send_or_simulate(tx, args) def relay_transaction(args: Any): - args = utils.as_object(args) - - if not _is_relayer_wallet_provided(args): - raise NoWalletProvided() - - cli_shared.check_broadcast_args(args) + ensure_relayer_wallet_args_are_provided(args) + validate_broadcast_args(args) tx = load_transaction_from_file(args.infile) - relayer = cli_shared.prepare_relayer_account(args) - if tx.relayer != relayer.address.to_bech32(): + relayer = cli_shared.load_relayer_account(args) + if relayer is None: + raise NoWalletProvided() + + if tx.relayer != relayer.address: raise IncorrectWalletError("Relayer wallet does not match the relayer's address set in the transaction.") - tx.relayer_signature = bytes.fromhex(relayer.sign_transaction(tx)) + tx.relayer_signature = relayer.sign_transaction(tx) cli_shared.send_or_simulate(tx, args) - - -def _is_relayer_wallet_provided(args: Any): - return any([args.relayer_pem, args.relayer_keyfile, args.relayer_ledger]) diff --git a/multiversx_sdk_cli/cli_validator_wallet.py b/multiversx_sdk_cli/cli_validator_wallet.py new file mode 100644 index 00000000..9415a2c9 --- /dev/null +++ b/multiversx_sdk_cli/cli_validator_wallet.py @@ -0,0 +1,123 @@ +import logging +from pathlib import Path +from typing import Any + +from multiversx_sdk import ValidatorPEM, ValidatorSecretKey, ValidatorSigner + +from multiversx_sdk_cli import cli_shared, utils +from multiversx_sdk_cli.errors import BadUserInput +from multiversx_sdk_cli.sign_verify import SignedMessage, sign_message_by_validator +from multiversx_sdk_cli.ux import show_critical_error, show_message + +logger = logging.getLogger("cli.validator_wallet") + + +def setup_parser(args: list[str], subparsers: Any) -> Any: + parser = cli_shared.add_group_subparser( + subparsers, + "validator-wallet", + "Create a validator wallet, sign and verify messages and convert a validator wallet to a hex secret key.", + ) + subparsers = parser.add_subparsers() + + sub = cli_shared.add_command_subparser( + subparsers, + "validator-wallet", + "new", + "Create a new validator wallet and save it as a PEM file.", + ) + sub.add_argument( + "--outfile", + help="the output path and file name for the generated wallet", + type=str, + required=True, + ) + sub.set_defaults(func=create_new_wallet) + + sub = cli_shared.add_command_subparser(subparsers, "validator-wallet", "sign-message", "Sign a message.") + sub.add_argument("--message", required=True, help="the message you want to sign") + sub.add_argument("--pem", required=True, type=str, help="the path to a validator pem file") + sub.add_argument( + "--index", + required=False, + type=int, + default=0, + help="the index of the validator in case the file contains multiple validators (default: %(default)s)", + ) + sub.set_defaults(func=sign_message) + + sub = cli_shared.add_command_subparser( + subparsers, "validator-wallet", "verify-message-signature", "Verify a previously signed message." + ) + sub.add_argument("--pubkey", required=True, help="the hex string representing the validator's public key") + sub.add_argument( + "--message", + required=True, + help="the previously signed message(readable text, as it was signed)", + ) + sub.add_argument("--signature", required=True, help="the signature in hex format") + sub.set_defaults(func=verify_message_signature) + + sub = cli_shared.add_command_subparser( + subparsers, "validator-wallet", "convert", "Convert a validator pem file to a hex secret key." + ) + sub.add_argument("--infile", required=True, help="the pem file of the wallet") + sub.add_argument( + "--index", + required=False, + type=int, + default=0, + help="the index of the validator in case the file contains multiple validators (default: %(default)s)", + ) + sub.set_defaults(func=convert_wallet_to_secret_key) + + parser.epilog = cli_shared.build_group_epilog(subparsers) + return subparsers + + +def create_new_wallet(args: Any): + path = Path(args.outfile).expanduser().resolve() + + if path.exists(): + raise BadUserInput(f"File already exists, will not overwrite: {str(path)}") + + secret_key = ValidatorSecretKey.generate() + public_key = secret_key.generate_public_key() + validator_pem = ValidatorPEM(label=public_key.hex(), secret_key=secret_key) + validator_pem.save(path) + + logger.info(f"Validator wallet saved: {str(path)}") + + +def sign_message(args: Any): + path = Path(args.pem).expanduser().resolve() + validator_signer = ValidatorSigner.from_pem_file(path, args.index) + signed_message = sign_message_by_validator(args.message, validator_signer) + + utils.dump_out_json(signed_message.to_dictionary()) + + +def verify_message_signature(args: Any): + message = args.message + pubkey = args.pubkey + + signed_message = SignedMessage(pubkey, message, args.signature) + is_signed = signed_message.verify_validator_signature() + + if is_signed: + show_message(f"""SUCCESS: The message "{message}" was signed by {pubkey}""") + else: + show_critical_error(f"""FAILED: The message "{message}" was NOT signed by {pubkey}""") + + +def convert_wallet_to_secret_key(args: Any): + file = args.infile + index = args.index + + path = Path(file).expanduser().resolve() + if not path.is_file(): + raise BadUserInput("File not found") + + signer = ValidatorSigner.from_pem_file(path, index) + print(f"Public key: {signer.get_pubkey().hex()}") + print(f"Secret key: {signer.secret_key.hex()}") diff --git a/multiversx_sdk_cli/cli_validators.py b/multiversx_sdk_cli/cli_validators.py index 106f403c..7d06feb9 100644 --- a/multiversx_sdk_cli/cli_validators.py +++ b/multiversx_sdk_cli/cli_validators.py @@ -1,22 +1,42 @@ -from typing import Any, List - -from multiversx_sdk_cli import cli_shared, utils, validators -from multiversx_sdk_cli.transactions import do_prepare_transaction - - -def setup_parser(args: List[str], subparsers: Any) -> Any: - parser = cli_shared.add_group_subparser(subparsers, "validator", "Stake, UnStake, UnBond, Unjail and other " - "actions useful for " - "Validators") +from pathlib import Path +from typing import Any + +from multiversx_sdk import Address, ValidatorPublicKey, ValidatorsSigners + +from multiversx_sdk_cli import cli_shared, utils +from multiversx_sdk_cli.args_validation import ( + ensure_wallet_args_are_provided, + validate_broadcast_args, + validate_chain_id_args, + validate_nonce_args, + validate_receiver_args, +) +from multiversx_sdk_cli.validators import ValidatorsController + + +def setup_parser(args: list[str], subparsers: Any) -> Any: + parser = cli_shared.add_group_subparser( + subparsers, + "validator", + "Stake, UnStake, UnBond, Unjail and other " "actions useful for " "Validators", + ) subparsers = parser.add_subparsers() sub = cli_shared.add_command_subparser(subparsers, "validator", "stake", "Stake value into the Network") _add_common_arguments(args, sub) sub.add_argument("--reward-address", default="", help="the reward address") - sub.add_argument("--validators-file", required=not (utils.is_arg_present(args, "--top-up")), - help="a JSON file describing the Nodes") - sub.add_argument("--top-up", action="store_true", default=False, - required=not (utils.is_arg_present(args, "--validators-file")), help="Stake value for top up") + sub.add_argument( + "--validators-pem", + required=not (utils.is_arg_present(args, "--top-up")), + help="a PEM file describing the nodes; can contain multiple nodes", + ) + sub.add_argument( + "--top-up", + action="store_true", + default=False, + required=not (utils.is_arg_present(args, "--validators-pem")), + help="Stake value for top up", + ) sub.set_defaults(func=do_stake) sub = cli_shared.add_command_subparser(subparsers, "validator", "unstake", "Unstake value") @@ -34,8 +54,9 @@ def setup_parser(args: List[str], subparsers: Any) -> Any: _add_nodes_arg(sub) sub.set_defaults(func=do_unbond) - sub = cli_shared.add_command_subparser(subparsers, "validator", "change-reward-address", - "Change the reward address") + sub = cli_shared.add_command_subparser( + subparsers, "validator", "change-reward-address", "Change the reward address" + ) _add_common_arguments(args, sub) sub.add_argument("--reward-address", required=True, help="the new reward address") sub.set_defaults(func=change_reward_address) @@ -44,17 +65,26 @@ def setup_parser(args: List[str], subparsers: Any) -> Any: _add_common_arguments(args, sub) sub.set_defaults(func=do_claim) - sub = cli_shared.add_command_subparser(subparsers, "validator", "unstake-nodes", "Unstake-nodes will unstake " - "nodes for provided bls keys") + sub = cli_shared.add_command_subparser( + subparsers, + "validator", + "unstake-nodes", + "Unstake-nodes will unstake " "nodes for provided bls keys", + ) _add_common_arguments(args, sub) _add_nodes_arg(sub) sub.set_defaults(func=do_unstake_nodes) - sub = cli_shared.add_command_subparser(subparsers, "validator", "unstake-tokens", "This command will un-stake the " - "given amount (if value is " - "greater than the existing " - "topUp value, it will unStake " - "one or several nodes)") + sub = cli_shared.add_command_subparser( + subparsers, + "validator", + "unstake-tokens", + "This command will un-stake the " + "given amount (if value is " + "greater than the existing " + "topUp value, it will unStake " + "one or several nodes)", + ) _add_common_arguments(args, sub) sub.add_argument("--unstake-value", default=0, help="the unstake value") sub.set_defaults(func=do_unstake_tokens) @@ -64,19 +94,31 @@ def setup_parser(args: List[str], subparsers: Any) -> Any: _add_nodes_arg(sub) sub.set_defaults(func=do_unbond_nodes) - sub = cli_shared.add_command_subparser(subparsers, "validator", "unbond-tokens", "It will unBond tokens, if " - "provided value is bigger that " - "topUp value will unBond nodes") + sub = cli_shared.add_command_subparser( + subparsers, + "validator", + "unbond-tokens", + "It will unBond tokens, if " "provided value is bigger that " "topUp value will unBond nodes", + ) _add_common_arguments(args, sub) sub.add_argument("--unbond-value", default=0, help="the unbond value") sub.set_defaults(func=do_unbond_tokens) - sub = cli_shared.add_command_subparser(subparsers, "validator", "clean-registered-data", "Deletes duplicated keys " - "from registered data") + sub = cli_shared.add_command_subparser( + subparsers, + "validator", + "clean-registered-data", + "Deletes duplicated keys " "from registered data", + ) _add_common_arguments(args, sub) sub.set_defaults(func=do_clean_registered_data) - sub = cli_shared.add_command_subparser(subparsers, "validator", "restake-unstaked-nodes", "It will reStake UnStaked nodes") + sub = cli_shared.add_command_subparser( + subparsers, + "validator", + "restake-unstaked-nodes", + "It will reStake UnStaked nodes", + ) _add_common_arguments(args, sub) _add_nodes_arg(sub) sub.set_defaults(func=do_restake_unstaked_nodes) @@ -85,134 +127,397 @@ def setup_parser(args: List[str], subparsers: Any) -> Any: return subparsers -def _add_common_arguments(args: List[str], sub: Any): +def _add_common_arguments(args: list[str], sub: Any): cli_shared.add_proxy_arg(sub) cli_shared.add_wallet_args(args, sub) - cli_shared.add_tx_args(args, sub, with_receiver=False, with_data=False, with_estimate_gas=True) - cli_shared.add_broadcast_args(sub, relay=False) + cli_shared.add_tx_args(args, sub, with_receiver=False, with_data=False) + cli_shared.add_broadcast_args(sub) cli_shared.add_outfile_arg(sub, what="signed transaction, hash") cli_shared.add_guardian_wallet_args(args, sub) + cli_shared.add_relayed_v3_wallet_args(args, sub) def _add_nodes_arg(sub: Any): - sub.add_argument("--nodes-public-keys", required=True, help="the public keys of the nodes as CSV (addrA,addrB)") + sub.add_argument( + "--nodes-public-keys", + required=True, + help="the public keys of the nodes as CSV (addrA,addrB)", + ) + + +def validate_args(args: Any) -> None: + validate_nonce_args(args) + validate_receiver_args(args) + ensure_wallet_args_are_provided(args) + validate_broadcast_args(args) + validate_chain_id_args(args) def do_stake(args: Any): - cli_shared.check_broadcast_args(args) - cli_shared.prepare_nonce_in_args(args) - cli_shared.prepare_chain_id_in_args(args) - validators.prepare_args_for_stake(args) - tx = do_prepare_transaction(args) + validate_args(args) + sender = cli_shared.prepare_sender(args) + guardian_and_relayer_data = cli_shared.get_guardian_and_relayer_data( + sender=sender.address.to_bech32(), + args=args, + ) + + native_amount = int(args.value) + gas_limit = args.gas_limit if args.gas_limit else 0 + rewards_address = Address.new_from_bech32(args.reward_address) if args.reward_address else None + + controller = _get_validators_controller(args) + + if args.top_up: + tx = controller.create_transaction_for_topping_up( + sender=sender, + native_amount=native_amount, + gas_limit=gas_limit, + gas_price=args.gas_price, + nonce=sender.nonce, + version=args.version, + options=args.options, + guardian_and_relayer_data=guardian_and_relayer_data, + ) + else: + validators_signers = _load_validators_signers(args.validators_pem) + tx = controller.create_transaction_for_staking( + sender=sender, + validators=validators_signers, + native_amount=native_amount, + gas_limit=gas_limit, + gas_price=args.gas_price, + nonce=sender.nonce, + version=args.version, + options=args.options, + rewards_address=rewards_address, + guardian_and_relayer_data=guardian_and_relayer_data, + ) cli_shared.send_or_simulate(tx, args) +def _get_validators_controller(args: Any): + chain_id = cli_shared.get_chain_id(args.chain, args.proxy) + validators = ValidatorsController(chain_id) + return validators + + +def _load_validators_signers(validators_pem: str) -> ValidatorsSigners: + validators_file_path = Path(validators_pem).expanduser() + return ValidatorsSigners.new_from_pem(validators_file_path) + + +def _parse_public_bls_keys(public_bls_keys: str) -> list[ValidatorPublicKey]: + keys = public_bls_keys.split(",") + validator_public_keys: list[ValidatorPublicKey] = [] + + for key in keys: + validator_public_keys.append(ValidatorPublicKey(bytes.fromhex(key))) + + return validator_public_keys + + def do_unstake(args: Any): - cli_shared.check_broadcast_args(args) - cli_shared.prepare_nonce_in_args(args) - cli_shared.prepare_chain_id_in_args(args) - validators.prepare_args_for_unstake(args) - tx = do_prepare_transaction(args) + validate_args(args) + sender = cli_shared.prepare_sender(args) + guardian_and_relayer_data = cli_shared.get_guardian_and_relayer_data( + sender=sender.address.to_bech32(), + args=args, + ) + + native_amount = int(args.value) + gas_limit = args.gas_limit if args.gas_limit else 0 + keys = _parse_public_bls_keys(args.nodes_public_keys) + + controller = _get_validators_controller(args) + tx = controller.create_transaction_for_unstaking( + sender=sender, + keys=keys, + native_amount=native_amount, + gas_limit=gas_limit, + gas_price=args.gas_price, + nonce=sender.nonce, + version=args.version, + options=args.options, + guardian_and_relayer_data=guardian_and_relayer_data, + ) cli_shared.send_or_simulate(tx, args) def do_unjail(args: Any): - cli_shared.check_broadcast_args(args) - cli_shared.prepare_nonce_in_args(args) - cli_shared.prepare_chain_id_in_args(args) - validators.prepare_args_for_unjail(args) - tx = do_prepare_transaction(args) + validate_args(args) + sender = cli_shared.prepare_sender(args) + guardian_and_relayer_data = cli_shared.get_guardian_and_relayer_data( + sender=sender.address.to_bech32(), + args=args, + ) + + native_amount = int(args.value) + gas_limit = args.gas_limit if args.gas_limit else 0 + keys = _parse_public_bls_keys(args.nodes_public_keys) + + controller = _get_validators_controller(args) + tx = controller.create_transaction_for_unjailing( + sender=sender, + keys=keys, + native_amount=native_amount, + gas_limit=gas_limit, + gas_price=args.gas_price, + nonce=sender.nonce, + version=args.version, + options=args.options, + guardian_and_relayer_data=guardian_and_relayer_data, + ) cli_shared.send_or_simulate(tx, args) def do_unbond(args: Any): - cli_shared.check_broadcast_args(args) - cli_shared.prepare_nonce_in_args(args) - cli_shared.prepare_chain_id_in_args(args) - validators.prepare_args_for_unbond(args) - tx = do_prepare_transaction(args) + validate_args(args) + sender = cli_shared.prepare_sender(args) + guardian_and_relayer_data = cli_shared.get_guardian_and_relayer_data( + sender=sender.address.to_bech32(), + args=args, + ) + + native_amount = int(args.value) + gas_limit = args.gas_limit if args.gas_limit else 0 + keys = _parse_public_bls_keys(args.nodes_public_keys) + + controller = _get_validators_controller(args) + tx = controller.create_transaction_for_unbonding( + sender=sender, + keys=keys, + native_amount=native_amount, + gas_limit=gas_limit, + gas_price=args.gas_price, + nonce=sender.nonce, + version=args.version, + options=args.options, + guardian_and_relayer_data=guardian_and_relayer_data, + ) cli_shared.send_or_simulate(tx, args) def change_reward_address(args: Any): - cli_shared.check_broadcast_args(args) - cli_shared.prepare_nonce_in_args(args) - cli_shared.prepare_chain_id_in_args(args) - validators.prepare_args_for_change_reward_address(args) - tx = do_prepare_transaction(args) + validate_args(args) + sender = cli_shared.prepare_sender(args) + guardian_and_relayer_data = cli_shared.get_guardian_and_relayer_data( + sender=sender.address.to_bech32(), + args=args, + ) + + native_amount = int(args.value) + gas_limit = args.gas_limit if args.gas_limit else 0 + rewards_address = Address.new_from_bech32(args.reward_address) + + controller = _get_validators_controller(args) + tx = controller.create_transaction_for_changing_rewards_address( + sender=sender, + rewards_address=rewards_address, + native_amount=native_amount, + gas_limit=gas_limit, + gas_price=args.gas_price, + nonce=sender.nonce, + version=args.version, + options=args.options, + guardian_and_relayer_data=guardian_and_relayer_data, + ) cli_shared.send_or_simulate(tx, args) def do_claim(args: Any): - cli_shared.check_broadcast_args(args) - cli_shared.prepare_nonce_in_args(args) - cli_shared.prepare_chain_id_in_args(args) - validators.prepare_args_for_claim(args) - tx = do_prepare_transaction(args) + validate_args(args) + sender = cli_shared.prepare_sender(args) + guardian_and_relayer_data = cli_shared.get_guardian_and_relayer_data( + sender=sender.address.to_bech32(), + args=args, + ) + + native_amount = int(args.value) + gas_limit = args.gas_limit if args.gas_limit else 0 + + controller = _get_validators_controller(args) + tx = controller.create_transaction_for_claiming( + sender=sender, + native_amount=native_amount, + gas_limit=gas_limit, + gas_price=args.gas_price, + nonce=sender.nonce, + version=args.version, + options=args.options, + guardian_and_relayer_data=guardian_and_relayer_data, + ) cli_shared.send_or_simulate(tx, args) def do_unstake_nodes(args: Any): - cli_shared.check_broadcast_args(args) - cli_shared.prepare_nonce_in_args(args) - cli_shared.prepare_chain_id_in_args(args) - validators.prepare_args_for_unstake_nodes(args) - tx = do_prepare_transaction(args) + validate_args(args) + sender = cli_shared.prepare_sender(args) + guardian_and_relayer_data = cli_shared.get_guardian_and_relayer_data( + sender=sender.address.to_bech32(), + args=args, + ) + + native_amount = int(args.value) + gas_limit = args.gas_limit if args.gas_limit else 0 + + keys = _parse_public_bls_keys(args.nodes_public_keys) + + controller = _get_validators_controller(args) + tx = controller.create_transaction_for_unstaking_nodes( + sender=sender, + keys=keys, + native_amount=native_amount, + gas_limit=gas_limit, + gas_price=args.gas_price, + nonce=sender.nonce, + version=args.version, + options=args.options, + guardian_and_relayer_data=guardian_and_relayer_data, + ) cli_shared.send_or_simulate(tx, args) def do_unstake_tokens(args: Any): - cli_shared.check_broadcast_args(args) - cli_shared.prepare_nonce_in_args(args) - cli_shared.prepare_chain_id_in_args(args) - validators.prepare_args_for_unstake_tokens(args) - tx = do_prepare_transaction(args) + validate_args(args) + sender = cli_shared.prepare_sender(args) + guardian_and_relayer_data = cli_shared.get_guardian_and_relayer_data( + sender=sender.address.to_bech32(), + args=args, + ) + + native_amount = int(args.value) + value = int(args.unstake_value) + gas_limit = args.gas_limit if args.gas_limit else 0 + + controller = _get_validators_controller(args) + tx = controller.create_transaction_for_unstaking_tokens( + sender=sender, + value=value, + native_amount=native_amount, + gas_limit=gas_limit, + gas_price=args.gas_price, + nonce=sender.nonce, + version=args.version, + options=args.options, + guardian_and_relayer_data=guardian_and_relayer_data, + ) cli_shared.send_or_simulate(tx, args) def do_unbond_nodes(args: Any): - cli_shared.check_broadcast_args(args) - cli_shared.prepare_nonce_in_args(args) - cli_shared.prepare_chain_id_in_args(args) - validators.prepare_args_for_unbond_nodes(args) - tx = do_prepare_transaction(args) + validate_args(args) + sender = cli_shared.prepare_sender(args) + guardian_and_relayer_data = cli_shared.get_guardian_and_relayer_data( + sender=sender.address.to_bech32(), + args=args, + ) + + native_amount = int(args.value) + gas_limit = args.gas_limit if args.gas_limit else 0 + + keys = _parse_public_bls_keys(args.nodes_public_keys) + + controller = _get_validators_controller(args) + tx = controller.create_transaction_for_unbonding_nodes( + sender=sender, + keys=keys, + native_amount=native_amount, + gas_limit=gas_limit, + gas_price=args.gas_price, + nonce=sender.nonce, + version=args.version, + options=args.options, + guardian_and_relayer_data=guardian_and_relayer_data, + ) cli_shared.send_or_simulate(tx, args) def do_unbond_tokens(args: Any): - cli_shared.check_broadcast_args(args) - cli_shared.prepare_nonce_in_args(args) - cli_shared.prepare_chain_id_in_args(args) - validators.prepare_args_for_unbond_tokens(args) - tx = do_prepare_transaction(args) + validate_args(args) + sender = cli_shared.prepare_sender(args) + guardian_and_relayer_data = cli_shared.get_guardian_and_relayer_data( + sender=sender.address.to_bech32(), + args=args, + ) + + native_amount = int(args.value) + value = int(args.unbond_value) + gas_limit = args.gas_limit if args.gas_limit else 0 + + controller = _get_validators_controller(args) + tx = controller.create_transaction_for_unbonding_tokens( + sender=sender, + value=value, + native_amount=native_amount, + gas_limit=gas_limit, + gas_price=args.gas_price, + nonce=sender.nonce, + version=args.version, + options=args.options, + guardian_and_relayer_data=guardian_and_relayer_data, + ) cli_shared.send_or_simulate(tx, args) def do_clean_registered_data(args: Any): - cli_shared.check_broadcast_args(args) - cli_shared.prepare_nonce_in_args(args) - cli_shared.prepare_chain_id_in_args(args) - validators.prepare_args_for_clean_registered_data(args) - tx = do_prepare_transaction(args) + validate_args(args) + sender = cli_shared.prepare_sender(args) + guardian_and_relayer_data = cli_shared.get_guardian_and_relayer_data( + sender=sender.address.to_bech32(), + args=args, + ) + + native_amount = int(args.value) + gas_limit = args.gas_limit if args.gas_limit else 0 + + controller = _get_validators_controller(args) + tx = controller.create_transaction_for_cleaning_registered_data( + sender=sender, + native_amount=native_amount, + gas_limit=gas_limit, + gas_price=args.gas_price, + nonce=sender.nonce, + version=args.version, + options=args.options, + guardian_and_relayer_data=guardian_and_relayer_data, + ) cli_shared.send_or_simulate(tx, args) def do_restake_unstaked_nodes(args: Any): - cli_shared.check_broadcast_args(args) - cli_shared.prepare_nonce_in_args(args) - cli_shared.prepare_chain_id_in_args(args) - validators.prepare_args_for_restake_unstaked_nodes(args) - tx = do_prepare_transaction(args) + validate_args(args) + sender = cli_shared.prepare_sender(args) + guardian_and_relayer_data = cli_shared.get_guardian_and_relayer_data( + sender=sender.address.to_bech32(), + args=args, + ) + + native_amount = int(args.value) + gas_limit = args.gas_limit if args.gas_limit else 0 + keys = _parse_public_bls_keys(args.nodes_public_keys) + + controller = _get_validators_controller(args) + tx = controller.create_transaction_for_restaking_unstaked_nodes( + sender=sender, + keys=keys, + native_amount=native_amount, + gas_limit=gas_limit, + gas_price=args.gas_price, + nonce=sender.nonce, + version=args.version, + options=args.options, + guardian_and_relayer_data=guardian_and_relayer_data, + ) cli_shared.send_or_simulate(tx, args) diff --git a/multiversx_sdk_cli/cli_wallet.py b/multiversx_sdk_cli/cli_wallet.py index c3e577ff..558e720a 100644 --- a/multiversx_sdk_cli/cli_wallet.py +++ b/multiversx_sdk_cli/cli_wallet.py @@ -3,17 +3,20 @@ import logging import sys from pathlib import Path -from typing import Any, List, Optional, Tuple +from typing import Any, Optional -from multiversx_sdk import (Address, Mnemonic, UserPEM, UserSecretKey, - UserWallet) +from multiversx_sdk import Address, Mnemonic, UserPEM, UserSecretKey, UserWallet from multiversx_sdk.core.address import get_shard_of_pubkey from multiversx_sdk_cli import cli_shared, utils from multiversx_sdk_cli.config import get_address_hrp from multiversx_sdk_cli.constants import NUMBER_OF_SHARDS -from multiversx_sdk_cli.errors import (BadUserInput, KnownError, - WalletGenerationError) +from multiversx_sdk_cli.errors import ( + BadUsage, + BadUserInput, + KnownError, + WalletGenerationError, +) from multiversx_sdk_cli.sign_verify import SignedMessage, sign_message from multiversx_sdk_cli.ux import show_critical_error, show_message @@ -45,11 +48,11 @@ CURRENT_SHARDS = [i for i in range(NUMBER_OF_SHARDS)] -def setup_parser(args: List[str], subparsers: Any) -> Any: +def setup_parser(args: list[str], subparsers: Any) -> Any: parser = cli_shared.add_group_subparser( subparsers, "wallet", - "Create wallet, derive secret key from mnemonic, bech32 address helpers etc." + "Create wallet, derive secret key from mnemonic, bech32 address helpers etc.", ) subparsers = parser.add_subparsers() @@ -57,59 +60,93 @@ def setup_parser(args: List[str], subparsers: Any) -> Any: subparsers, "wallet", "new", - "Create a new wallet and print its mnemonic; optionally save as password-protected JSON (recommended) or PEM (not recommended)" + "Create a new wallet and print its mnemonic; optionally save as password-protected JSON (recommended) or PEM (not recommended)", + ) + sub.add_argument( + "--format", + choices=WALLET_FORMATS, + help="the format of the generated wallet file (default: %(default)s)", + default=None, + ) + sub.add_argument( + "--outfile", + help="the output path and base file name for the generated wallet files (default: %(default)s)", + type=str, + ) + sub.add_argument( + "--address-hrp", + help=f"the human-readable part of the address, when format is {WALLET_FORMAT_KEYSTORE_SECRET_KEY} or {WALLET_FORMAT_PEM} (default: %(default)s)", + type=str, + default=get_address_hrp(), + ) + sub.add_argument( + "--shard", + type=int, + help="the shard in which the address will be generated; (default: random)", ) - sub.add_argument("--format", choices=WALLET_FORMATS, help="the format of the generated wallet file (default: %(default)s)", default=None) - sub.add_argument("--outfile", help="the output path and base file name for the generated wallet files (default: %(default)s)", type=str) - sub.add_argument("--address-hrp", help=f"the human-readable part of the address, when format is {WALLET_FORMAT_KEYSTORE_SECRET_KEY} or {WALLET_FORMAT_PEM} (default: %(default)s)", type=str, default=get_address_hrp()) - sub.add_argument("--shard", type=int, help="the shard in which the address will be generated; (default: random)") sub.set_defaults(func=wallet_new) sub = cli_shared.add_command_subparser( - subparsers, - "wallet", - "convert", - "Convert a wallet from one format to another" + subparsers, "wallet", "convert", "Convert a wallet from one format to another" ) sub.add_argument("--infile", help="path to the input file") sub.add_argument("--outfile", help="path to the output file") - sub.add_argument("--in-format", required=True, choices=WALLET_FORMATS, help="the format of the input file") - sub.add_argument("--out-format", required=True, choices=WALLET_FORMATS_AND_ADDRESSES, help="the format of the output file") - sub.add_argument("--address-index", help=f"the address index, if input format is {WALLET_FORMAT_RAW_MNEMONIC}, {WALLET_FORMAT_KEYSTORE_MNEMONIC} or {WALLET_FORMAT_PEM} (with multiple entries) and the output format is {WALLET_FORMAT_KEYSTORE_SECRET_KEY} or {WALLET_FORMAT_PEM}", type=int, default=0) - sub.add_argument("--address-hrp", help=f"the human-readable part of the address, when the output format is {WALLET_FORMAT_KEYSTORE_SECRET_KEY} or {WALLET_FORMAT_PEM} (default: %(default)s)", type=str, default=get_address_hrp()) + sub.add_argument( + "--in-format", + required=True, + choices=WALLET_FORMATS, + help="the format of the input file", + ) + sub.add_argument( + "--out-format", + required=True, + choices=WALLET_FORMATS_AND_ADDRESSES, + help="the format of the output file", + ) + sub.add_argument( + "--address-index", + help=f"the address index, if input format is {WALLET_FORMAT_RAW_MNEMONIC}, {WALLET_FORMAT_KEYSTORE_MNEMONIC} or {WALLET_FORMAT_PEM} (with multiple entries) and the output format is {WALLET_FORMAT_KEYSTORE_SECRET_KEY} or {WALLET_FORMAT_PEM}", + type=int, + default=0, + ) + sub.add_argument( + "--address-hrp", + help=f"the human-readable part of the address, when the output format is {WALLET_FORMAT_KEYSTORE_SECRET_KEY} or {WALLET_FORMAT_PEM} (default: %(default)s)", + type=str, + default=get_address_hrp(), + ) sub.set_defaults(func=convert_wallet) sub = cli_shared.add_command_subparser( subparsers, "wallet", "bech32", - "Helper for encoding and decoding bech32 addresses" + "Helper for encoding and decoding bech32 addresses", ) sub.add_argument("value", help="the value to encode or decode") group = sub.add_mutually_exclusive_group(required=True) group.add_argument("--encode", action="store_true", help="whether to encode") group.add_argument("--decode", action="store_true", help="whether to decode") - sub.add_argument("--hrp", type=str, help="the human readable part; only used for encoding to bech32 (default: %(default)s)", default=get_address_hrp()) + sub.add_argument( + "--hrp", + type=str, + help="the human readable part; only used for encoding to bech32 (default: %(default)s)", + default=get_address_hrp(), + ) sub.set_defaults(func=do_bech32) - sub = cli_shared.add_command_subparser( - subparsers, - "wallet", - "sign-message", - "Sign a message" - ) + sub = cli_shared.add_command_subparser(subparsers, "wallet", "sign-message", "Sign a message") sub.add_argument("--message", required=True, help="the message you want to sign") - cli_shared.add_wallet_args(args, sub) + cli_shared.add_wallet_args(args=args, sub=sub) sub.set_defaults(func=sign_user_message) - sub = cli_shared.add_command_subparser( - subparsers, - "wallet", - "verify-message", - "Verify a previously signed message" - ) + sub = cli_shared.add_command_subparser(subparsers, "wallet", "verify-message", "Verify a previously signed message") sub.add_argument("--address", required=True, help="the bech32 address of the signer") - sub.add_argument("--message", required=True, help="the previously signed message(readable text, as it was signed)") + sub.add_argument( + "--message", + required=True, + help="the previously signed message(readable text, as it was signed)", + ) sub.add_argument("--signature", required=True, help="the signature in hex format") sub.set_defaults(func=verify_signed_message) @@ -134,11 +171,11 @@ def wallet_new(args: Any): if format is None: return if outfile is None: - raise KnownError("The --outfile option is required when --format is specified.") + raise BadUsage("The `--outfile` argument is required when `--format` is specified.") outfile = Path(outfile).expanduser().resolve() if outfile.exists(): - raise KnownError(f"File already exists, will not overwrite: {outfile}") + raise BadUserInput(f"File already exists, will not overwrite: {outfile}") if format == WALLET_FORMAT_RAW_MNEMONIC: outfile.write_text(mnemonic.get_text()) @@ -158,13 +195,12 @@ def wallet_new(args: Any): pem_file = UserPEM(address.to_bech32(), secret_key) pem_file.save(outfile) else: - raise KnownError(f"Unknown format: {format}") + raise BadUsage(f"Unknown format: {format}") logger.info(f"Wallet ({format}) saved: {outfile}") def _generate_mnemonic_with_shard_constraint(shard: int) -> Mnemonic: - if shard not in CURRENT_SHARDS: raise BadUserInput(f"Wrong shard provided. Choose between {CURRENT_SHARDS}") @@ -207,7 +243,9 @@ def convert_wallet(args: Any): print(output_text) -def _load_wallet(input_text: str, in_format: str, address_index: int) -> Tuple[Optional[Mnemonic], Optional[UserSecretKey]]: +def _load_wallet( + input_text: str, in_format: str, address_index: int +) -> tuple[Optional[Mnemonic], Optional[UserSecretKey]]: if in_format == WALLET_FORMAT_RAW_MNEMONIC: input_text = " ".join(input_text.split()) mnemonic = Mnemonic(input_text) @@ -229,15 +267,17 @@ def _load_wallet(input_text: str, in_format: str, address_index: int) -> Tuple[O secret_key = UserPEM.from_text(input_text, address_index).secret_key return None, secret_key - raise KnownError(f"Cannot load wallet, unknown input format: <{in_format}>. Make sure to use one of following: {WALLET_FORMATS}.") + raise KnownError( + f"Cannot load wallet, unknown input format: <{in_format}>. Make sure to use one of following: {WALLET_FORMATS}." + ) def _create_wallet_content( - out_format: str, - mnemonic: Optional[Mnemonic], - secret_key: Optional[UserSecretKey], - address_index: int, - address_hrp: str + out_format: str, + mnemonic: Optional[Mnemonic], + secret_key: Optional[UserSecretKey], + address_index: int, + address_hrp: str, ) -> str: if out_format == WALLET_FORMAT_RAW_MNEMONIC: if mnemonic is None: @@ -295,7 +335,9 @@ def _create_wallet_content( return secret_key.hex() - raise KnownError(f"Cannot create wallet, unknown output format: <{out_format}>. Make sure to use one of following: {WALLET_FORMATS}.") + raise KnownError( + f"Cannot create wallet, unknown output format: <{out_format}>. Make sure to use one of following: {WALLET_FORMATS}." + ) def do_bech32(args: Any): @@ -316,8 +358,10 @@ def do_bech32(args: Any): def sign_user_message(args: Any): message: str = args.message + account = cli_shared.prepare_account(args) signed_message = sign_message(message, account) + utils.dump_out_json(signed_message.to_dictionary()) @@ -327,7 +371,8 @@ def verify_signed_message(args: Any): signature: str = args.signature signed_message = SignedMessage(bech32_address, message, signature) - is_signed = signed_message.verify_signature() + is_signed = signed_message.verify_user_signature() + if is_signed: show_message(f"""SUCCESS: The message "{message}" was signed by {bech32_address}""") else: diff --git a/multiversx_sdk_cli/config.py b/multiversx_sdk_cli/config.py index 621ab5b1..222e4c7b 100644 --- a/multiversx_sdk_cli/config.py +++ b/multiversx_sdk_cli/config.py @@ -1,6 +1,6 @@ import os from pathlib import Path -from typing import Any, Dict, List +from typing import Any from multiversx_sdk import NetworkProviderConfig @@ -10,11 +10,6 @@ LOCAL_CONFIG_PATH = Path("mxpy.json").resolve() GLOBAL_CONFIG_PATH = SDK_PATH / "mxpy.json" -DEFAULT_GAS_PRICE = 1000000000 -GAS_PER_DATA_BYTE = 1500 -MIN_GAS_LIMIT = 50000 -MAX_GAS_LIMIT = 600000000 - class MetaChainSystemSCsCost: STAKE = 5000000 @@ -29,6 +24,8 @@ class MetaChainSystemSCsCost: DELEGATION_OPS = 1000000 UNSTAKE_TOKENS = 5000000 UNBOND_TOKENS = 5000000 + CLEAN_REGISTERED_DATA = 5000000 + RESTAKE_UNSTAKED_NODES = 5000000 def get_dependency_resolution(key: str) -> str: @@ -84,12 +81,12 @@ def delete_value(name: str): write_file(data) -def get_active() -> Dict[str, Any]: +def get_active() -> dict[str, Any]: data = read_file() configs = data.get("configurations", {}) active_config_name: str = data.get("active", "default") - empty_config: Dict[str, Any] = dict() - result: Dict[str, Any] = configs.get(active_config_name, empty_config) + empty_config: dict[str, Any] = dict() + result: dict[str, Any] = configs.get(active_config_name, empty_config) return result @@ -110,7 +107,7 @@ def create_new_config(name: str, template: str): new_config = data["configurations"][template] data["active"] = name - data.setdefault('configurations', {}) + data.setdefault("configurations", {}) data["configurations"][name] = new_config write_file(data) @@ -130,13 +127,13 @@ def _guard_valid_name(name: str): def _guard_valid_config_name(config: Any, name: str): - configurations = config.get('configurations', {}) + configurations = config.get("configurations", {}) if name not in configurations: raise errors.UnknownConfigurationError(name) def _guard_config_unique(config: Any, name: str): - configurations = config.get('configurations', {}) + configurations = config.get("configurations", {}) if name in configurations: raise errors.ConfigurationShouldBeUniqueError(name) @@ -146,7 +143,7 @@ def _guard_valid_config_deletion(name: str): raise errors.ConfigurationProtectedError(name) -def get_defaults() -> Dict[str, Any]: +def get_defaults() -> dict[str, Any]: return { "dependencies.vmtools.tag": "v1.5.24", "dependencies.vmtools.urlTemplate.linux": "https://github.com/multiversx/mx-chain-vm-go/archive/{TAG}.tar.gz", @@ -166,7 +163,7 @@ def get_defaults() -> Dict[str, Any]: "dependencies.testwallets.urlTemplate.windows": "https://github.com/multiversx/mx-sdk-testwallets/archive/{TAG}.tar.gz", "dependencies.wasm-opt.tag": "0.112.0", "github_api_token": "", - "default_address_hrp": "erd" + "default_address_hrp": "erd", } @@ -182,20 +179,20 @@ def resolve_config_path() -> Path: return GLOBAL_CONFIG_PATH -def read_file() -> Dict[str, Any]: +def read_file() -> dict[str, Any]: config_path = resolve_config_path() if config_path.exists(): - data: Dict[str, Any] = utils.read_json_file(config_path) + data: dict[str, Any] = utils.read_json_file(config_path) return data return dict() -def write_file(data: Dict[str, Any]): +def write_file(data: dict[str, Any]): config_path = resolve_config_path() utils.write_json_file(str(config_path), data) -def add_config_args(argv: List[str]) -> List[str]: +def add_config_args(argv: list[str]) -> list[str]: try: command, subcommand, *_ = argv except ValueError: @@ -213,10 +210,10 @@ def add_config_args(argv: List[str]) -> List[str]: return final_args -def determine_final_args(argv: List[str], config_args: Dict[str, Any]) -> List[str]: - extra_args: List[str] = [] +def determine_final_args(argv: list[str], config_args: dict[str, Any]) -> list[str]: + extra_args: list[str] = [] for key, value in config_args.items(): - key_arg = f'--{key}' + key_arg = f"--{key}" # arguments from the command line override the config if key_arg in argv: continue @@ -225,15 +222,15 @@ def determine_final_args(argv: List[str], config_args: Dict[str, Any]) -> List[s extra_args.append(key_arg) if value is True: continue - if isinstance(value, List): - for item in value: - extra_args.append(str(item)) + if isinstance(value, list): + for item in value: # type: ignore + extra_args.append(str(item)) # type: ignore else: extra_args.append(str(value)) # the verbose flag is an exception since it has to go before the command and subcommand # eg. mxpy --verbose contract deploy - verbose_flag = '--verbose' + verbose_flag = "--verbose" pre_args = [] if verbose_flag in extra_args: extra_args.remove(verbose_flag) diff --git a/multiversx_sdk_cli/constants.py b/multiversx_sdk_cli/constants.py index 0f0e0dbc..9a997542 100644 --- a/multiversx_sdk_cli/constants.py +++ b/multiversx_sdk_cli/constants.py @@ -12,3 +12,10 @@ ADDRESS_ZERO_HEX = "0000000000000000000000000000000000000000000000000000000000000000" NUMBER_OF_SHARDS = 3 + +DEFAULT_GAS_PRICE = 1000000000 +MIN_GAS_LIMIT = 50000 + +TCS_SERVICE_ID = "MultiversXTCSService" +EXTRA_GAS_LIMIT_FOR_GUARDED_TRANSACTIONS = 50_000 +EXTRA_GAS_LIMIT_FOR_RELAYED_TRANSACTIONS = 50_000 diff --git a/multiversx_sdk_cli/contract_verification.py b/multiversx_sdk_cli/contract_verification.py index cbad2d3d..28fea012 100644 --- a/multiversx_sdk_cli/contract_verification.py +++ b/multiversx_sdk_cli/contract_verification.py @@ -3,12 +3,11 @@ import logging import time from pathlib import Path -from typing import Any, Dict, Optional, Tuple +from typing import Any, Optional, Protocol import requests -from multiversx_sdk import Address +from multiversx_sdk import Address, Message -from multiversx_sdk_cli.accounts import Account from multiversx_sdk_cli.errors import KnownError from multiversx_sdk_cli.utils import dump_out_json, read_json_file @@ -18,14 +17,21 @@ logger = logging.getLogger("cli.contracts.verifier") +# fmt: off +class IAccount(Protocol): + def sign_message(self, message: Message) -> bytes: + ... +# fmt: on + + class ContractVerificationRequest: def __init__( self, contract: Address, - source_code: Dict[str, Any], + source_code: dict[str, Any], signature: bytes, docker_image: str, - contract_variant: Optional[str] + contract_variant: Optional[str], ) -> None: self.contract = contract self.source_code = source_code @@ -33,20 +39,26 @@ def __init__( self.docker_image = docker_image self.contract_variant = contract_variant - def to_dictionary(self) -> Dict[str, Any]: + def to_dictionary(self) -> dict[str, Any]: return { "signature": self.signature.hex(), "payload": { "contract": self.contract.bech32(), "dockerImage": self.docker_image, "sourceCode": self.source_code, - "contractVariant": self.contract_variant - } + "contractVariant": self.contract_variant, + }, } class ContractVerificationPayload: - def __init__(self, contract: Address, source_code: Dict[str, Any], docker_image: str, contract_variant: Optional[str]) -> None: + def __init__( + self, + contract: Address, + source_code: dict[str, Any], + docker_image: str, + contract_variant: Optional[str], + ) -> None: self.contract = contract self.source_code = source_code self.docker_image = docker_image @@ -57,24 +69,27 @@ def serialize(self) -> str: "contract": self.contract.to_bech32(), "dockerImage": self.docker_image, "sourceCode": self.source_code, - "contractVariant": self.contract_variant + "contractVariant": self.contract_variant, } - return json.dumps(payload, separators=(',', ':')) + return json.dumps(payload, separators=(",", ":")) def trigger_contract_verification( - packaged_source: Path, - owner: Account, - contract: Address, - verifier_url: str, - docker_image: str, - contract_variant: Optional[str]): + packaged_source: Path, + owner: IAccount, + contract: Address, + verifier_url: str, + docker_image: str, + contract_variant: Optional[str], +): source_code = read_json_file(packaged_source) payload = ContractVerificationPayload(contract, source_code, docker_image, contract_variant).serialize() signature = _create_request_signature(owner, contract, payload.encode()) - contract_verification = ContractVerificationRequest(contract, source_code, signature, docker_image, contract_variant) + contract_verification = ContractVerificationRequest( + contract, source_code, signature, docker_image, contract_variant + ) request_dictionary = contract_verification.to_dictionary() @@ -101,14 +116,11 @@ def trigger_contract_verification( query_status_with_task_id(verifier_url, task_id) -def _create_request_signature(account: Account, contract_address: Address, request_payload: bytes) -> bytes: +def _create_request_signature(account: IAccount, contract_address: Address, request_payload: bytes) -> bytes: hashed_payload: str = hashlib.sha256(request_payload).hexdigest() raw_data_to_sign = f"{contract_address.to_bech32()}{hashed_payload}" - signature_hex = account.sign_message(raw_data_to_sign.encode()) - signature = bytes.fromhex(signature_hex) - - return signature + return account.sign_message(Message(raw_data_to_sign.encode())) def query_status_with_task_id(url: str, task_id: str, interval: int = 10): @@ -131,7 +143,7 @@ def query_status_with_task_id(url: str, task_id: str, interval: int = 10): time.sleep(interval) -def _do_post(url: str, payload: Any) -> Tuple[int, str, Dict[str, Any]]: +def _do_post(url: str, payload: Any) -> tuple[int, str, dict[str, Any]]: logger.debug(f"_do_post() to {url}") response = requests.post(url, json=payload) @@ -144,7 +156,7 @@ def _do_post(url: str, payload: Any) -> Tuple[int, str, Dict[str, Any]]: raise KnownError(f"Cannot parse response from {url}", error) -def _do_get(url: str) -> Tuple[int, str, Dict[str, Any]]: +def _do_get(url: str) -> tuple[int, str, dict[str, Any]]: logger.debug(f"_do_get() from {url}") response = requests.get(url) diff --git a/multiversx_sdk_cli/contracts.py b/multiversx_sdk_cli/contracts.py index 5f8214b9..a944c224 100644 --- a/multiversx_sdk_cli/contracts.py +++ b/multiversx_sdk_cli/contracts.py @@ -1,18 +1,35 @@ import logging from pathlib import Path -from typing import Any, List, Optional, Protocol, Union - -from multiversx_sdk import (Address, QueryRunnerAdapter, - SmartContractQueriesController, - SmartContractTransactionsFactory, Token, - TokenComputer, TokenTransfer, Transaction, - TransactionPayload) -from multiversx_sdk.abi import Abi +from typing import Any, Optional, Protocol, Union + +from multiversx_sdk import ( + Address, + AwaitingOptions, + SmartContractController, + SmartContractQuery, + SmartContractQueryResponse, + SmartContractTransactionsFactory, + Token, + TokenComputer, + TokenTransfer, + Transaction, + TransactionOnNetwork, + TransactionsFactoryConfig, +) +from multiversx_sdk.abi import ( + Abi, + AddressValue, + BigUIntValue, + BoolValue, + BytesValue, + StringValue, +) from multiversx_sdk_cli import errors -from multiversx_sdk_cli.accounts import Account +from multiversx_sdk_cli.base_transactions_controller import BaseTransactionsController from multiversx_sdk_cli.config import get_address_hrp -from multiversx_sdk_cli.interfaces import IAddress +from multiversx_sdk_cli.guardian_relayer_data import GuardianRelayerData +from multiversx_sdk_cli.interfaces import IAccount logger = logging.getLogger("contracts") @@ -20,51 +37,45 @@ FALSE_STR_LOWER = "false" TRUE_STR_LOWER = "true" STR_PREFIX = "str:" +ADDRESS_PREFIX = "addr:" +MAINCHAIN_ADDRESS_HRP = "erd" +# fmt: off class INetworkProvider(Protocol): - def query_contract(self, query: Any) -> 'IContractQueryResponse': + def query_contract(self, query: SmartContractQuery) -> SmartContractQueryResponse: ... - -class IContractQueryResponse(Protocol): - return_data: List[str] - return_code: str - return_message: str - gas_used: int - - def get_return_data_parts(self) -> List[bytes]: + def await_transaction_completed( + self, transaction_hash: Union[bytes, str], options: Optional[AwaitingOptions] = None + ) -> TransactionOnNetwork: ... +# fmt: on -class IConfig(Protocol): - chain_id: str - min_gas_limit: int - gas_limit_per_byte: int - gas_limit_claim_developer_rewards: int - gas_limit_change_owner_address: int - - -class SmartContract: - def __init__(self, config: IConfig, abi: Optional[Abi] = None): +class SmartContract(BaseTransactionsController): + def __init__(self, config: TransactionsFactoryConfig, abi: Optional[Abi] = None): self._abi = abi + self._config = config self._factory = SmartContractTransactionsFactory(config, abi) - def prepare_deploy_transaction(self, - owner: Account, - bytecode: Path, - arguments: Union[List[Any], None], - should_prepare_args: bool, - upgradeable: bool, - readable: bool, - payable: bool, - payable_by_sc: bool, - gas_limit: int, - value: int, - nonce: int, - version: int, - options: int, - guardian: str) -> Transaction: + def prepare_deploy_transaction( + self, + owner: IAccount, + bytecode: Path, + arguments: Union[list[Any], None], + should_prepare_args: bool, + upgradeable: bool, + readable: bool, + payable: bool, + payable_by_sc: bool, + gas_limit: int, + value: int, + nonce: int, + version: int, + options: int, + guardian_and_relayer_data: GuardianRelayerData, + ) -> Transaction: args = arguments if arguments else [] if should_prepare_args: args = self._prepare_args_for_factory(args) @@ -78,29 +89,40 @@ def prepare_deploy_transaction(self, is_upgradeable=upgradeable, is_readable=readable, is_payable=payable, - is_payable_by_sc=payable_by_sc + is_payable_by_sc=payable_by_sc, ) tx.nonce = nonce tx.version = version tx.options = options - tx.guardian = guardian - tx.signature = bytes.fromhex(owner.sign_transaction(tx)) + tx.guardian = guardian_and_relayer_data.guardian_address + tx.relayer = guardian_and_relayer_data.relayer_address + + self.sign_transaction( + transaction=tx, + sender=owner, + guardian=guardian_and_relayer_data.guardian, + relayer=guardian_and_relayer_data.relayer, + guardian_service_url=guardian_and_relayer_data.guardian_service_url, + guardian_2fa_code=guardian_and_relayer_data.guardian_2fa_code, + ) return tx - def prepare_execute_transaction(self, - caller: Account, - contract: Address, - function: str, - arguments: Union[List[Any], None], - should_prepare_args: bool, - gas_limit: int, - value: int, - transfers: Union[List[str], None], - nonce: int, - version: int, - options: int, - guardian: str) -> Transaction: + def prepare_execute_transaction( + self, + caller: IAccount, + contract: Address, + function: str, + arguments: Union[list[Any], None], + should_prepare_args: bool, + gas_limit: int, + value: int, + transfers: Union[list[str], None], + nonce: int, + version: int, + options: int, + guardian_and_relayer_data: GuardianRelayerData, + ) -> Transaction: token_transfers = self._prepare_token_transfers(transfers) if transfers else [] args = arguments if arguments else [] @@ -114,32 +136,43 @@ def prepare_execute_transaction(self, gas_limit=gas_limit, arguments=args, native_transfer_amount=value, - token_transfers=token_transfers + token_transfers=token_transfers, ) tx.nonce = nonce tx.version = version tx.options = options - tx.guardian = guardian - tx.signature = bytes.fromhex(caller.sign_transaction(tx)) + tx.guardian = guardian_and_relayer_data.guardian_address + tx.relayer = guardian_and_relayer_data.relayer_address + + self.sign_transaction( + transaction=tx, + sender=caller, + guardian=guardian_and_relayer_data.guardian, + relayer=guardian_and_relayer_data.relayer, + guardian_service_url=guardian_and_relayer_data.guardian_service_url, + guardian_2fa_code=guardian_and_relayer_data.guardian_2fa_code, + ) return tx - def prepare_upgrade_transaction(self, - owner: Account, - contract: IAddress, - bytecode: Path, - arguments: Union[List[str], None], - should_prepare_args: bool, - upgradeable: bool, - readable: bool, - payable: bool, - payable_by_sc: bool, - gas_limit: int, - value: int, - nonce: int, - version: int, - options: int, - guardian: str) -> Transaction: + def prepare_upgrade_transaction( + self, + owner: IAccount, + contract: Address, + bytecode: Path, + arguments: Union[list[str], None], + should_prepare_args: bool, + upgradeable: bool, + readable: bool, + payable: bool, + payable_by_sc: bool, + gas_limit: int, + value: int, + nonce: int, + version: int, + options: int, + guardian_and_relayer_data: GuardianRelayerData, + ) -> Transaction: args = arguments if arguments else [] if should_prepare_args: args = self._prepare_args_for_factory(args) @@ -154,43 +187,49 @@ def prepare_upgrade_transaction(self, is_upgradeable=upgradeable, is_readable=readable, is_payable=payable, - is_payable_by_sc=payable_by_sc + is_payable_by_sc=payable_by_sc, ) tx.nonce = nonce tx.version = version tx.options = options - tx.guardian = guardian - tx.signature = bytes.fromhex(owner.sign_transaction(tx)) + tx.guardian = guardian_and_relayer_data.guardian_address + tx.relayer = guardian_and_relayer_data.relayer_address + + self.sign_transaction( + transaction=tx, + sender=owner, + guardian=guardian_and_relayer_data.guardian, + relayer=guardian_and_relayer_data.relayer, + guardian_service_url=guardian_and_relayer_data.guardian_service_url, + guardian_2fa_code=guardian_and_relayer_data.guardian_2fa_code, + ) return tx - def query_contract(self, - contract_address: IAddress, - proxy: INetworkProvider, - function: str, - arguments: Optional[List[Any]], - should_prepare_args: bool) -> List[Any]: + def query_contract( + self, + contract_address: Address, + proxy: INetworkProvider, + function: str, + arguments: Optional[list[Any]], + should_prepare_args: bool, + ) -> list[Any]: args = arguments if arguments else [] if should_prepare_args: args = self._prepare_args_for_factory(args) - query_runner = QueryRunnerAdapter(proxy) - sc_query_controller = SmartContractQueriesController(query_runner, self._abi) + sc_query_controller = SmartContractController(self._config.chain_id, proxy, self._abi) try: - response = sc_query_controller.query( - contract=contract_address.to_bech32(), - function=function, - arguments=args - ) + response = sc_query_controller.query(contract=contract_address, function=function, arguments=args) except Exception as e: raise errors.QueryContractError("Couldn't query contract: ", e) return response - def _prepare_token_transfers(self, transfers: List[str]) -> List[TokenTransfer]: + def _prepare_token_transfers(self, transfers: list[str]) -> list[TokenTransfer]: token_computer = TokenComputer() - token_transfers: List[TokenTransfer] = [] + token_transfers: list[TokenTransfer] = [] for i in range(0, len(transfers) - 1, 2): identifier = transfers[i] @@ -203,95 +242,44 @@ def _prepare_token_transfers(self, transfers: List[str]) -> List[TokenTransfer]: return token_transfers - def _prepare_args_for_factory(self, arguments: List[str]) -> List[Any]: - args: List[Any] = [] + def _prepare_args_for_factory(self, arguments: list[str]) -> list[Any]: + args: list[Any] = [] for arg in arguments: if arg.startswith(HEX_PREFIX): - args.append(self._hex_to_bytes(arg)) + args.append(BytesValue(self._hex_to_bytes(arg))) elif arg.isnumeric(): - args.append(int(arg)) + args.append(BigUIntValue(int(arg))) + elif arg.startswith(ADDRESS_PREFIX): + args.append(AddressValue.new_from_address(Address.new_from_bech32(arg[len(ADDRESS_PREFIX) :]))) + elif arg.startswith(MAINCHAIN_ADDRESS_HRP): + # this flow will be removed in the future + logger.warning( + "Address argument has no prefix. This flow will be removed in the future. Please provide each address using the `addr:` prefix. (e.g. --arguments addr:erd1...)" + ) + args.append(AddressValue.new_from_address(Address.new_from_bech32(arg))) elif arg.startswith(get_address_hrp()): - args.append(Address.new_from_bech32(arg)) + args.append(AddressValue.new_from_address(Address.new_from_bech32(arg))) elif arg.lower() == FALSE_STR_LOWER: - args.append(False) + args.append(BoolValue(False)) elif arg.lower() == TRUE_STR_LOWER: - args.append(True) + args.append(BoolValue(True)) elif arg.startswith(STR_PREFIX): - args.append(arg[len(STR_PREFIX):]) + args.append(StringValue(arg[len(STR_PREFIX) :])) else: - raise errors.BadUserInput(f"Unknown argument type for argument: `{arg}`. Use `mxpy contract --help` to check all supported arguments") + raise errors.BadUserInput( + f"Unknown argument type for argument: `{arg}`. Use `mxpy contract --help` to check all supported arguments" + ) return args def _hex_to_bytes(self, arg: str): - argument = arg[len(HEX_PREFIX):] + argument = arg[len(HEX_PREFIX) :] argument = argument.upper() - argument = ensure_even_length(argument) + argument = self.ensure_even_length(argument) return bytes.fromhex(argument) - -def prepare_execute_transaction_data(function: str, arguments: List[Any]) -> TransactionPayload: - tx_data = function - - for arg in arguments: - tx_data += f"@{_prepare_argument(arg)}" - - return TransactionPayload.from_str(tx_data) - - -# only used for stake operations -def _prepare_argument(argument: Any): - as_str = str(argument) - as_hex = _to_hex(as_str) - return as_hex - - -def _to_hex(arg: str): - if arg.startswith(HEX_PREFIX): - return _prepare_hexadecimal(arg) - - if arg.isnumeric(): - return _prepare_decimal(arg) - elif arg.startswith(get_address_hrp()): - addr = Address.from_bech32(arg) - return _prepare_hexadecimal(f"{HEX_PREFIX}{addr.hex()}") - elif arg.lower() == FALSE_STR_LOWER or arg.lower() == TRUE_STR_LOWER: - as_str = f"{HEX_PREFIX}01" if arg.lower() == TRUE_STR_LOWER else f"{HEX_PREFIX}00" - return _prepare_hexadecimal(as_str) - elif arg.startswith(STR_PREFIX): - as_hex = f"{HEX_PREFIX}{arg[len(STR_PREFIX):].encode('ascii').hex()}" - return _prepare_hexadecimal(as_hex) - else: - raise Exception(f"could not convert {arg} to hex") - - -def _prepare_hexadecimal(argument: str) -> str: - argument = argument[len(HEX_PREFIX):] - argument = argument.upper() - argument = ensure_even_length(argument) - - if argument == "": - return "" - - try: - _ = int(argument, 16) - except ValueError: - raise errors.UnknownArgumentFormat(argument) - return argument - - -def _prepare_decimal(argument: str) -> str: - if not argument.isnumeric(): - raise errors.UnknownArgumentFormat(argument) - - as_number = int(argument) - as_hexstring = hex(as_number)[len(HEX_PREFIX):] - as_hexstring = ensure_even_length(as_hexstring) - return as_hexstring.upper() - - -def ensure_even_length(string: str) -> str: - if len(string) % 2 == 1: - return '0' + string - return string + def ensure_even_length(self, string: str) -> str: + if len(string) % 2 == 1: + return "0" + string + return string diff --git a/multiversx_sdk_cli/cosign_transaction.py b/multiversx_sdk_cli/cosign_transaction.py index d63d71f0..4a383ddf 100644 --- a/multiversx_sdk_cli/cosign_transaction.py +++ b/multiversx_sdk_cli/cosign_transaction.py @@ -1,17 +1,15 @@ from typing import Any import requests -from multiversx_sdk import TransactionsConverter +from multiversx_sdk import Transaction from multiversx_sdk_cli.errors import GuardianServiceError -from multiversx_sdk_cli.interfaces import ITransaction -def cosign_transaction(transaction: ITransaction, service_url: str, guardian_code: str) -> ITransaction: - tx_converter = TransactionsConverter() +def cosign_transaction(transaction: Transaction, service_url: str, guardian_code: str): payload = { "code": f"{guardian_code}", - "transactions": [tx_converter.transaction_to_dictionary(transaction)] + "transactions": [transaction.to_dictionary()], } # we call sign-multiple-transactions to be allowed a bigger payload (e.g. deploying large contracts) @@ -23,8 +21,6 @@ def cosign_transaction(transaction: ITransaction, service_url: str, guardian_cod tx_as_dict = response.json()["data"]["transactions"][0] transaction.guardian_signature = bytes.fromhex(tx_as_dict["guardianSignature"]) - return transaction - def check_for_guardian_error(response: dict[str, Any]): error = response["error"] diff --git a/multiversx_sdk_cli/delegation.py b/multiversx_sdk_cli/delegation.py new file mode 100644 index 00000000..17a8c377 --- /dev/null +++ b/multiversx_sdk_cli/delegation.py @@ -0,0 +1,798 @@ +from multiversx_sdk import ( + Address, + DelegationTransactionsFactory, + Transaction, + TransactionsFactoryConfig, + ValidatorPublicKey, +) +from multiversx_sdk.abi import BigUIntValue, Serializer + +from multiversx_sdk_cli.base_transactions_controller import BaseTransactionsController +from multiversx_sdk_cli.config import get_address_hrp +from multiversx_sdk_cli.errors import BadUsage +from multiversx_sdk_cli.guardian_relayer_data import GuardianRelayerData +from multiversx_sdk_cli.interfaces import IAccount + +DELEGATION_MANAGER_SC_ADDRESS_HEX = "000000000000000000010000000000000000000000000000000000000004ffff" + + +class DelegationOperations(BaseTransactionsController): + def __init__(self, config: TransactionsFactoryConfig) -> None: + self._factory = DelegationTransactionsFactory(config) + + def prepare_transaction_for_new_delegation_contract( + self, + owner: IAccount, + native_amount: int, + total_delegation_cap: int, + service_fee: int, + gas_limit: int, + gas_price: int, + nonce: int, + version: int, + options: int, + guardian_and_relayer_data: GuardianRelayerData, + ) -> Transaction: + tx = self._factory.create_transaction_for_new_delegation_contract( + sender=owner.address, + total_delegation_cap=total_delegation_cap, + service_fee=service_fee, + amount=native_amount, + ) + tx.gas_price = gas_price + tx.nonce = nonce + tx.version = version + tx.options = options + tx.guardian = guardian_and_relayer_data.guardian_address + tx.relayer = guardian_and_relayer_data.relayer_address + + self.add_extra_gas_limit_if_required(tx) + + if gas_limit: + tx.gas_limit = gas_limit + + self.sign_transaction( + transaction=tx, + sender=owner, + guardian=guardian_and_relayer_data.guardian, + relayer=guardian_and_relayer_data.relayer, + guardian_service_url=guardian_and_relayer_data.guardian_service_url, + guardian_2fa_code=guardian_and_relayer_data.guardian_2fa_code, + ) + + return tx + + def prepare_transaction_for_adding_nodes( + self, + owner: IAccount, + delegation_contract: Address, + public_keys: list[ValidatorPublicKey], + signed_messages: list[bytes], + gas_limit: int, + gas_price: int, + value: int, + nonce: int, + version: int, + options: int, + guardian_and_relayer_data: GuardianRelayerData, + ) -> Transaction: + tx = self._factory.create_transaction_for_adding_nodes( + sender=owner.address, + delegation_contract=delegation_contract, + public_keys=public_keys, + signed_messages=signed_messages, + ) + tx.value = value + tx.gas_price = gas_price + tx.nonce = nonce + tx.version = version + tx.options = options + tx.guardian = guardian_and_relayer_data.guardian_address + tx.relayer = guardian_and_relayer_data.relayer_address + + self.add_extra_gas_limit_if_required(tx) + + if gas_limit: + tx.gas_limit = gas_limit + + self.sign_transaction( + transaction=tx, + sender=owner, + guardian=guardian_and_relayer_data.guardian, + relayer=guardian_and_relayer_data.relayer, + guardian_service_url=guardian_and_relayer_data.guardian_service_url, + guardian_2fa_code=guardian_and_relayer_data.guardian_2fa_code, + ) + + return tx + + def prepare_transaction_for_removing_nodes( + self, + owner: IAccount, + delegation_contract: Address, + public_keys: list[ValidatorPublicKey], + gas_limit: int, + gas_price: int, + value: int, + nonce: int, + version: int, + options: int, + guardian_and_relayer_data: GuardianRelayerData, + ) -> Transaction: + tx = self._factory.create_transaction_for_removing_nodes( + sender=owner.address, + delegation_contract=delegation_contract, + public_keys=public_keys, + ) + tx.value = value + tx.gas_price = gas_price + tx.nonce = nonce + tx.version = version + tx.options = options + tx.guardian = guardian_and_relayer_data.guardian_address + tx.relayer = guardian_and_relayer_data.relayer_address + + self.add_extra_gas_limit_if_required(tx) + + if gas_limit: + tx.gas_limit = gas_limit + + self.sign_transaction( + transaction=tx, + sender=owner, + guardian=guardian_and_relayer_data.guardian, + relayer=guardian_and_relayer_data.relayer, + guardian_service_url=guardian_and_relayer_data.guardian_service_url, + guardian_2fa_code=guardian_and_relayer_data.guardian_2fa_code, + ) + + return tx + + def prepare_transaction_for_staking_nodes( + self, + owner: IAccount, + delegation_contract: Address, + public_keys: list[ValidatorPublicKey], + gas_limit: int, + gas_price: int, + value: int, + nonce: int, + version: int, + options: int, + guardian_and_relayer_data: GuardianRelayerData, + ) -> Transaction: + tx = self._factory.create_transaction_for_staking_nodes( + sender=owner.address, + delegation_contract=delegation_contract, + public_keys=public_keys, + ) + tx.value = value + tx.gas_price = gas_price + tx.nonce = nonce + tx.version = version + tx.options = options + tx.guardian = guardian_and_relayer_data.guardian_address + tx.relayer = guardian_and_relayer_data.relayer_address + + self.add_extra_gas_limit_if_required(tx) + + if gas_limit: + tx.gas_limit = gas_limit + + self.sign_transaction( + transaction=tx, + sender=owner, + guardian=guardian_and_relayer_data.guardian, + relayer=guardian_and_relayer_data.relayer, + guardian_service_url=guardian_and_relayer_data.guardian_service_url, + guardian_2fa_code=guardian_and_relayer_data.guardian_2fa_code, + ) + + return tx + + def prepare_transaction_for_unbonding_nodes( + self, + owner: IAccount, + delegation_contract: Address, + public_keys: list[ValidatorPublicKey], + gas_limit: int, + gas_price: int, + value: int, + nonce: int, + version: int, + options: int, + guardian_and_relayer_data: GuardianRelayerData, + ) -> Transaction: + tx = self._factory.create_transaction_for_unbonding_nodes( + sender=owner.address, + delegation_contract=delegation_contract, + public_keys=public_keys, + ) + tx.value = value + tx.gas_price = gas_price + tx.nonce = nonce + tx.version = version + tx.options = options + tx.guardian = guardian_and_relayer_data.guardian_address + tx.relayer = guardian_and_relayer_data.relayer_address + + self.add_extra_gas_limit_if_required(tx) + + if gas_limit: + tx.gas_limit = gas_limit + + self.sign_transaction( + transaction=tx, + sender=owner, + guardian=guardian_and_relayer_data.guardian, + relayer=guardian_and_relayer_data.relayer, + guardian_service_url=guardian_and_relayer_data.guardian_service_url, + guardian_2fa_code=guardian_and_relayer_data.guardian_2fa_code, + ) + + return tx + + def prepare_transaction_for_unstaking_nodes( + self, + owner: IAccount, + delegation_contract: Address, + public_keys: list[ValidatorPublicKey], + gas_limit: int, + gas_price: int, + value: int, + nonce: int, + version: int, + options: int, + guardian_and_relayer_data: GuardianRelayerData, + ) -> Transaction: + tx = self._factory.create_transaction_for_unstaking_nodes( + sender=owner.address, + delegation_contract=delegation_contract, + public_keys=public_keys, + ) + tx.value = value + tx.gas_price = gas_price + tx.nonce = nonce + tx.version = version + tx.options = options + tx.guardian = guardian_and_relayer_data.guardian_address + tx.relayer = guardian_and_relayer_data.relayer_address + + self.add_extra_gas_limit_if_required(tx) + + if gas_limit: + tx.gas_limit = gas_limit + + self.sign_transaction( + transaction=tx, + sender=owner, + guardian=guardian_and_relayer_data.guardian, + relayer=guardian_and_relayer_data.relayer, + guardian_service_url=guardian_and_relayer_data.guardian_service_url, + guardian_2fa_code=guardian_and_relayer_data.guardian_2fa_code, + ) + + return tx + + def prepare_transaction_for_unjailing_nodes( + self, + owner: IAccount, + delegation_contract: Address, + public_keys: list[ValidatorPublicKey], + gas_limit: int, + gas_price: int, + value: int, + nonce: int, + version: int, + options: int, + guardian_and_relayer_data: GuardianRelayerData, + ) -> Transaction: + tx = self._factory.create_transaction_for_unjailing_nodes( + sender=owner.address, + delegation_contract=delegation_contract, + public_keys=public_keys, + amount=value, + ) + tx.gas_price = gas_price + tx.nonce = nonce + tx.version = version + tx.options = options + tx.guardian = guardian_and_relayer_data.guardian_address + tx.relayer = guardian_and_relayer_data.relayer_address + + self.add_extra_gas_limit_if_required(tx) + + if gas_limit: + tx.gas_limit = gas_limit + + self.sign_transaction( + transaction=tx, + sender=owner, + guardian=guardian_and_relayer_data.guardian, + relayer=guardian_and_relayer_data.relayer, + guardian_service_url=guardian_and_relayer_data.guardian_service_url, + guardian_2fa_code=guardian_and_relayer_data.guardian_2fa_code, + ) + + return tx + + def prepare_transaction_for_delegating( + self, + owner: IAccount, + delegation_contract: Address, + gas_limit: int, + gas_price: int, + value: int, + nonce: int, + version: int, + options: int, + guardian_and_relayer_data: GuardianRelayerData, + ) -> Transaction: + tx = self._factory.create_transaction_for_delegating( + sender=owner.address, + delegation_contract=delegation_contract, + amount=value, + ) + tx.gas_price = gas_price + tx.nonce = nonce + tx.version = version + tx.options = options + tx.guardian = guardian_and_relayer_data.guardian_address + tx.relayer = guardian_and_relayer_data.relayer_address + + self.add_extra_gas_limit_if_required(tx) + + if gas_limit: + tx.gas_limit = gas_limit + + self.sign_transaction( + transaction=tx, + sender=owner, + guardian=guardian_and_relayer_data.guardian, + relayer=guardian_and_relayer_data.relayer, + guardian_service_url=guardian_and_relayer_data.guardian_service_url, + guardian_2fa_code=guardian_and_relayer_data.guardian_2fa_code, + ) + + return tx + + def prepare_transaction_for_claiming_rewards( + self, + owner: IAccount, + delegation_contract: Address, + gas_limit: int, + gas_price: int, + value: int, + nonce: int, + version: int, + options: int, + guardian_and_relayer_data: GuardianRelayerData, + ) -> Transaction: + tx = self._factory.create_transaction_for_claiming_rewards( + sender=owner.address, delegation_contract=delegation_contract + ) + tx.value = value + tx.gas_price = gas_price + tx.nonce = nonce + tx.version = version + tx.options = options + tx.guardian = guardian_and_relayer_data.guardian_address + tx.relayer = guardian_and_relayer_data.relayer_address + + self.add_extra_gas_limit_if_required(tx) + + if gas_limit: + tx.gas_limit = gas_limit + + self.sign_transaction( + transaction=tx, + sender=owner, + guardian=guardian_and_relayer_data.guardian, + relayer=guardian_and_relayer_data.relayer, + guardian_service_url=guardian_and_relayer_data.guardian_service_url, + guardian_2fa_code=guardian_and_relayer_data.guardian_2fa_code, + ) + + return tx + + def prepare_transaction_for_redelegating_rewards( + self, + owner: IAccount, + delegation_contract: Address, + gas_limit: int, + gas_price: int, + value: int, + nonce: int, + version: int, + options: int, + guardian_and_relayer_data: GuardianRelayerData, + ) -> Transaction: + tx = self._factory.create_transaction_for_redelegating_rewards( + sender=owner.address, delegation_contract=delegation_contract + ) + tx.value = value + tx.gas_price = gas_price + tx.nonce = nonce + tx.version = version + tx.options = options + tx.guardian = guardian_and_relayer_data.guardian_address + tx.relayer = guardian_and_relayer_data.relayer_address + + self.add_extra_gas_limit_if_required(tx) + + if gas_limit: + tx.gas_limit = gas_limit + + self.sign_transaction( + transaction=tx, + sender=owner, + guardian=guardian_and_relayer_data.guardian, + relayer=guardian_and_relayer_data.relayer, + guardian_service_url=guardian_and_relayer_data.guardian_service_url, + guardian_2fa_code=guardian_and_relayer_data.guardian_2fa_code, + ) + + return tx + + def prepare_transaction_for_undelegating( + self, + owner: IAccount, + delegation_contract: Address, + gas_limit: int, + gas_price: int, + value: int, + nonce: int, + version: int, + options: int, + guardian_and_relayer_data: GuardianRelayerData, + ) -> Transaction: + tx = self._factory.create_transaction_for_undelegating( + sender=owner.address, + delegation_contract=delegation_contract, + amount=value, + ) + tx.gas_price = gas_price + tx.nonce = nonce + tx.version = version + tx.options = options + tx.guardian = guardian_and_relayer_data.guardian_address + tx.relayer = guardian_and_relayer_data.relayer_address + + self.add_extra_gas_limit_if_required(tx) + + if gas_limit: + tx.gas_limit = gas_limit + + self.sign_transaction( + transaction=tx, + sender=owner, + guardian=guardian_and_relayer_data.guardian, + relayer=guardian_and_relayer_data.relayer, + guardian_service_url=guardian_and_relayer_data.guardian_service_url, + guardian_2fa_code=guardian_and_relayer_data.guardian_2fa_code, + ) + + return tx + + def prepare_transaction_for_withdrawing( + self, + owner: IAccount, + delegation_contract: Address, + gas_limit: int, + gas_price: int, + value: int, + nonce: int, + version: int, + options: int, + guardian_and_relayer_data: GuardianRelayerData, + ) -> Transaction: + tx = self._factory.create_transaction_for_withdrawing( + sender=owner.address, delegation_contract=delegation_contract + ) + tx.value = value + tx.gas_price = gas_price + tx.nonce = nonce + tx.version = version + tx.options = options + tx.guardian = guardian_and_relayer_data.guardian_address + tx.relayer = guardian_and_relayer_data.relayer_address + + self.add_extra_gas_limit_if_required(tx) + + if gas_limit: + tx.gas_limit = gas_limit + + self.sign_transaction( + transaction=tx, + sender=owner, + guardian=guardian_and_relayer_data.guardian, + relayer=guardian_and_relayer_data.relayer, + guardian_service_url=guardian_and_relayer_data.guardian_service_url, + guardian_2fa_code=guardian_and_relayer_data.guardian_2fa_code, + ) + + return tx + + def prepare_transaction_for_changing_service_fee( + self, + owner: IAccount, + delegation_contract: Address, + service_fee: int, + gas_limit: int, + gas_price: int, + value: int, + nonce: int, + version: int, + options: int, + guardian_and_relayer_data: GuardianRelayerData, + ) -> Transaction: + tx = self._factory.create_transaction_for_changing_service_fee( + sender=owner.address, + delegation_contract=delegation_contract, + service_fee=service_fee, + ) + tx.value = value + tx.gas_price = gas_price + tx.nonce = nonce + tx.version = version + tx.options = options + tx.guardian = guardian_and_relayer_data.guardian_address + tx.relayer = guardian_and_relayer_data.relayer_address + + self.add_extra_gas_limit_if_required(tx) + + if gas_limit: + tx.gas_limit = gas_limit + + self.sign_transaction( + transaction=tx, + sender=owner, + guardian=guardian_and_relayer_data.guardian, + relayer=guardian_and_relayer_data.relayer, + guardian_service_url=guardian_and_relayer_data.guardian_service_url, + guardian_2fa_code=guardian_and_relayer_data.guardian_2fa_code, + ) + + return tx + + def prepare_transaction_for_modifying_delegation_cap( + self, + owner: IAccount, + delegation_contract: Address, + delegation_cap: int, + gas_limit: int, + gas_price: int, + value: int, + nonce: int, + version: int, + options: int, + guardian_and_relayer_data: GuardianRelayerData, + ) -> Transaction: + tx = self._factory.create_transaction_for_modifying_delegation_cap( + sender=owner.address, + delegation_contract=delegation_contract, + delegation_cap=delegation_cap, + ) + tx.value = value + tx.gas_price = gas_price + tx.nonce = nonce + tx.version = version + tx.options = options + tx.guardian = guardian_and_relayer_data.guardian_address + tx.relayer = guardian_and_relayer_data.relayer_address + + self.add_extra_gas_limit_if_required(tx) + + if gas_limit: + tx.gas_limit = gas_limit + + self.sign_transaction( + transaction=tx, + sender=owner, + guardian=guardian_and_relayer_data.guardian, + relayer=guardian_and_relayer_data.relayer, + guardian_service_url=guardian_and_relayer_data.guardian_service_url, + guardian_2fa_code=guardian_and_relayer_data.guardian_2fa_code, + ) + + return tx + + def prepare_transaction_for_automatic_activation( + self, + owner: IAccount, + delegation_contract: Address, + set: bool, + unset: bool, + gas_limit: int, + gas_price: int, + value: int, + nonce: int, + version: int, + options: int, + guardian_and_relayer_data: GuardianRelayerData, + ) -> Transaction: + if set and unset: + raise BadUsage("Cannot set and unset at the same time") + + if set: + tx = self._factory.create_transaction_for_setting_automatic_activation( + sender=owner.address, delegation_contract=delegation_contract + ) + elif unset: + tx = self._factory.create_transaction_for_unsetting_automatic_activation( + sender=owner.address, delegation_contract=delegation_contract + ) + else: + raise BadUsage("Both set and unset automatic activation are False") + + tx.value = value + tx.gas_price = gas_price + tx.nonce = nonce + tx.version = version + tx.options = options + tx.guardian = guardian_and_relayer_data.guardian_address + tx.relayer = guardian_and_relayer_data.relayer_address + + self.add_extra_gas_limit_if_required(tx) + + if gas_limit: + tx.gas_limit = gas_limit + + self.sign_transaction( + transaction=tx, + sender=owner, + guardian=guardian_and_relayer_data.guardian, + relayer=guardian_and_relayer_data.relayer, + guardian_service_url=guardian_and_relayer_data.guardian_service_url, + guardian_2fa_code=guardian_and_relayer_data.guardian_2fa_code, + ) + + return tx + + def prepare_transaction_for_redelegate_cap( + self, + owner: IAccount, + delegation_contract: Address, + set: bool, + unset: bool, + gas_limit: int, + gas_price: int, + value: int, + nonce: int, + version: int, + options: int, + guardian_and_relayer_data: GuardianRelayerData, + ) -> Transaction: + if set and unset: + raise BadUsage("Cannot set and unset at the same time") + + if set: + tx = self._factory.create_transaction_for_setting_cap_check_on_redelegate_rewards( + sender=owner.address, delegation_contract=delegation_contract + ) + elif unset: + tx = self._factory.create_transaction_for_unsetting_cap_check_on_redelegate_rewards( + sender=owner.address, delegation_contract=delegation_contract + ) + else: + raise BadUsage("Either set or unset should be True") + + tx.value = value + tx.gas_price = gas_price + tx.nonce = nonce + tx.version = version + tx.options = options + tx.guardian = guardian_and_relayer_data.guardian_address + tx.relayer = guardian_and_relayer_data.relayer_address + + self.add_extra_gas_limit_if_required(tx) + + if gas_limit: + tx.gas_limit = gas_limit + + self.sign_transaction( + transaction=tx, + sender=owner, + guardian=guardian_and_relayer_data.guardian, + relayer=guardian_and_relayer_data.relayer, + guardian_service_url=guardian_and_relayer_data.guardian_service_url, + guardian_2fa_code=guardian_and_relayer_data.guardian_2fa_code, + ) + + return tx + + def prepare_transaction_for_setting_metadata( + self, + owner: IAccount, + delegation_contract: Address, + name: str, + website: str, + identifier: str, + gas_limit: int, + gas_price: int, + value: int, + nonce: int, + version: int, + options: int, + guardian_and_relayer_data: GuardianRelayerData, + ) -> Transaction: + tx = self._factory.create_transaction_for_setting_metadata( + sender=owner.address, + delegation_contract=delegation_contract, + name=name, + website=website, + identifier=identifier, + ) + tx.value = value + tx.gas_price = gas_price + tx.nonce = nonce + tx.version = version + tx.options = options + tx.guardian = guardian_and_relayer_data.guardian_address + tx.relayer = guardian_and_relayer_data.relayer_address + + self.add_extra_gas_limit_if_required(tx) + + if gas_limit: + tx.gas_limit = gas_limit + + self.sign_transaction( + transaction=tx, + sender=owner, + guardian=guardian_and_relayer_data.guardian, + relayer=guardian_and_relayer_data.relayer, + guardian_service_url=guardian_and_relayer_data.guardian_service_url, + guardian_2fa_code=guardian_and_relayer_data.guardian_2fa_code, + ) + + return tx + + # will be replaced in the future once it's implemented in sdk-py + def prepare_transaction_for_creating_delegation_contract_from_validator( + self, + owner: IAccount, + max_cap: int, + service_fee: int, + gas_limit: int, + gas_price: int, + value: int, + nonce: int, + version: int, + options: int, + guardian_and_relayer_data: GuardianRelayerData, + ) -> Transaction: + receiver = Address.new_from_hex(DELEGATION_MANAGER_SC_ADDRESS_HEX, get_address_hrp()) + + serializer = Serializer() + data = "makeNewContractFromValidatorData@" + serializer.serialize( + [BigUIntValue(max_cap), BigUIntValue(service_fee)] + ) + + tx = Transaction( + sender=owner.address, + receiver=receiver, + gas_limit=510000000, + chain_id=self._factory.config.chain_id, + data=data.encode(), + nonce=nonce, + version=version, + options=options, + guardian=guardian_and_relayer_data.guardian_address, + relayer=guardian_and_relayer_data.relayer_address, + gas_price=gas_price, + value=value, + ) + + if gas_limit: + tx.gas_limit = gas_limit + + self.sign_transaction( + transaction=tx, + sender=owner, + guardian=guardian_and_relayer_data.guardian, + relayer=guardian_and_relayer_data.relayer, + guardian_service_url=guardian_and_relayer_data.guardian_service_url, + guardian_2fa_code=guardian_and_relayer_data.guardian_2fa_code, + ) + + return tx diff --git a/multiversx_sdk_cli/delegation/__init__.py b/multiversx_sdk_cli/delegation/__init__.py deleted file mode 100644 index ecdc1533..00000000 --- a/multiversx_sdk_cli/delegation/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from multiversx_sdk_cli.delegation.staking_provider import DelegationOperations - -__all__ = ["DelegationOperations"] diff --git a/multiversx_sdk_cli/delegation/staking_provider.py b/multiversx_sdk_cli/delegation/staking_provider.py deleted file mode 100644 index 977e7424..00000000 --- a/multiversx_sdk_cli/delegation/staking_provider.py +++ /dev/null @@ -1,468 +0,0 @@ -from pathlib import Path -from typing import Any, List, Protocol, Tuple - -from multiversx_sdk import (Address, DelegationTransactionsFactory, - Transaction, ValidatorPublicKey) -from multiversx_sdk.core.serializer import args_to_string - -from multiversx_sdk_cli.config import get_address_hrp -from multiversx_sdk_cli.errors import BadUsage -from multiversx_sdk_cli.interfaces import IAddress, ITransaction -from multiversx_sdk_cli.validators.validators_file import ValidatorsFile - - -class IConfig(Protocol): - chain_id: str - min_gas_limit: int - gas_limit_per_byte: int - gas_limit_stake: int - gas_limit_unstake: int - gas_limit_unbond: int - gas_limit_create_delegation_contract: int - gas_limit_delegation_operations: int - additional_gas_limit_per_validator_node: int - additional_gas_for_delegation_operations: int - - -class IAccount(Protocol): - @property - def address(self) -> IAddress: - ... - - nonce: int - - def sign_transaction(self, transaction: ITransaction) -> str: - ... - - -class DelegationOperations: - def __init__(self, config: IConfig) -> None: - self._factory = DelegationTransactionsFactory(config) - - def prepare_transaction_for_new_delegation_contract(self, owner: IAccount, args: Any) -> ITransaction: - tx = self._factory.create_transaction_for_new_delegation_contract( - sender=owner.address, - total_delegation_cap=int(args.total_delegation_cap), - service_fee=int(args.service_fee), - amount=int(args.value) - ) - tx.nonce = int(args.nonce) - tx.version = int(args.version) - tx.options = int(args.options) - tx.guardian = args.guardian - - if args.gas_limit: - tx.gas_limit = int(args.gas_limit) - - tx.signature = bytes.fromhex(owner.sign_transaction(tx)) - - return tx - - def prepare_transaction_for_adding_nodes(self, owner: IAccount, args: Any) -> ITransaction: - delegation_contract = Address.new_from_bech32(args.delegation_contract) - public_keys, signed_messages = self._get_public_keys_and_signed_messages(args) - - tx = self._factory.create_transaction_for_adding_nodes( - sender=owner.address, - delegation_contract=delegation_contract, - public_keys=public_keys, - signed_messages=signed_messages - ) - tx.nonce = int(args.nonce) - tx.version = int(args.version) - tx.options = int(args.options) - tx.guardian = args.guardian - - if args.gas_limit: - tx.gas_limit = int(args.gas_limit) - - tx.signature = bytes.fromhex(owner.sign_transaction(tx)) - - return tx - - def prepare_transaction_for_removing_nodes(self, owner: IAccount, args: Any) -> ITransaction: - delegation_contract = Address.new_from_bech32(args.delegation_contract) - - public_keys = self._load_validators_public_keys(args) - - tx = self._factory.create_transaction_for_removing_nodes( - sender=owner.address, - delegation_contract=delegation_contract, - public_keys=public_keys - ) - tx.nonce = int(args.nonce) - tx.version = int(args.version) - tx.options = int(args.options) - tx.guardian = args.guardian - - if args.gas_limit: - tx.gas_limit = int(args.gas_limit) - - tx.signature = bytes.fromhex(owner.sign_transaction(tx)) - - return tx - - def prepare_transaction_for_staking_nodes(self, owner: IAccount, args: Any) -> ITransaction: - delegation_contract = Address.new_from_bech32(args.delegation_contract) - - public_keys = self._load_validators_public_keys(args) - - tx = self._factory.create_transaction_for_staking_nodes( - sender=owner.address, - delegation_contract=delegation_contract, - public_keys=public_keys - ) - tx.nonce = int(args.nonce) - tx.version = int(args.version) - tx.options = int(args.options) - tx.guardian = args.guardian - - if args.gas_limit: - tx.gas_limit = int(args.gas_limit) - - tx.signature = bytes.fromhex(owner.sign_transaction(tx)) - - return tx - - def prepare_transaction_for_unbonding_nodes(self, owner: IAccount, args: Any) -> ITransaction: - delegation_contract = Address.new_from_bech32(args.delegation_contract) - - public_keys = self._load_validators_public_keys(args) - - tx = self._factory.create_transaction_for_unbonding_nodes( - sender=owner.address, - delegation_contract=delegation_contract, - public_keys=public_keys - ) - tx.nonce = int(args.nonce) - tx.version = int(args.version) - tx.options = int(args.options) - tx.guardian = args.guardian - - if args.gas_limit: - tx.gas_limit = int(args.gas_limit) - - tx.signature = bytes.fromhex(owner.sign_transaction(tx)) - - return tx - - def prepare_transaction_for_unstaking_nodes(self, owner: IAccount, args: Any) -> ITransaction: - delegation_contract = Address.new_from_bech32(args.delegation_contract) - - public_keys = self._load_validators_public_keys(args) - - tx = self._factory.create_transaction_for_unstaking_nodes( - sender=owner.address, - delegation_contract=delegation_contract, - public_keys=public_keys - ) - tx.nonce = int(args.nonce) - tx.version = int(args.version) - tx.options = int(args.options) - tx.guardian = args.guardian - - if args.gas_limit: - tx.gas_limit = int(args.gas_limit) - - tx.signature = bytes.fromhex(owner.sign_transaction(tx)) - - return tx - - def prepare_transaction_for_unjailing_nodes(self, owner: IAccount, args: Any) -> ITransaction: - delegation_contract = Address.new_from_bech32(args.delegation_contract) - - public_keys = self._load_validators_public_keys(args) - amount = int(args.value) - - tx = self._factory.create_transaction_for_unjailing_nodes( - sender=owner.address, - delegation_contract=delegation_contract, - public_keys=public_keys, - amount=amount - ) - tx.nonce = int(args.nonce) - tx.version = int(args.version) - tx.options = int(args.options) - tx.guardian = args.guardian - tx.value = args.value - - if args.gas_limit: - tx.gas_limit = int(args.gas_limit) - - tx.signature = bytes.fromhex(owner.sign_transaction(tx)) - - return tx - - def prepare_transaction_for_delegating(self, owner: IAccount, args: Any) -> ITransaction: - delegation_contract = Address.new_from_bech32(args.delegation_contract) - - tx = self._factory.create_transaction_for_delegating( - sender=owner.address, - delegation_contract=delegation_contract, - amount=int(args.value) - ) - tx.nonce = int(args.nonce) - tx.version = int(args.version) - tx.options = int(args.options) - tx.guardian = args.guardian - - if args.gas_limit: - tx.gas_limit = int(args.gas_limit) - - tx.signature = bytes.fromhex(owner.sign_transaction(tx)) - - return tx - - def prepare_transaction_for_claiming_rewards(self, owner: IAccount, args: Any) -> ITransaction: - delegation_contract = Address.new_from_bech32(args.delegation_contract) - - tx = self._factory.create_transaction_for_claiming_rewards( - sender=owner.address, - delegation_contract=delegation_contract - ) - tx.nonce = int(args.nonce) - tx.version = int(args.version) - tx.options = int(args.options) - tx.guardian = args.guardian - - if args.gas_limit: - tx.gas_limit = int(args.gas_limit) - - tx.signature = bytes.fromhex(owner.sign_transaction(tx)) - - return tx - - def prepare_transaction_for_redelegating_rewards(self, owner: IAccount, args: Any) -> ITransaction: - delegation_contract = Address.new_from_bech32(args.delegation_contract) - - tx = self._factory.create_transaction_for_redelegating_rewards( - sender=owner.address, - delegation_contract=delegation_contract - ) - tx.nonce = int(args.nonce) - tx.version = int(args.version) - tx.options = int(args.options) - tx.guardian = args.guardian - - if args.gas_limit: - tx.gas_limit = int(args.gas_limit) - - tx.signature = bytes.fromhex(owner.sign_transaction(tx)) - - return tx - - def prepare_transaction_for_undelegating(self, owner: IAccount, args: Any) -> ITransaction: - delegation_contract = Address.new_from_bech32(args.delegation_contract) - - tx = self._factory.create_transaction_for_undelegating( - sender=owner.address, - delegation_contract=delegation_contract, - amount=int(args.value) - ) - tx.nonce = int(args.nonce) - tx.version = int(args.version) - tx.options = int(args.options) - tx.guardian = args.guardian - - if args.gas_limit: - tx.gas_limit = int(args.gas_limit) - - tx.signature = bytes.fromhex(owner.sign_transaction(tx)) - - return tx - - def prepare_transaction_for_withdrawing(self, owner: IAccount, args: Any) -> ITransaction: - delegation_contract = Address.new_from_bech32(args.delegation_contract) - - tx = self._factory.create_transaction_for_withdrawing( - sender=owner.address, - delegation_contract=delegation_contract - ) - tx.nonce = int(args.nonce) - tx.version = int(args.version) - tx.options = int(args.options) - tx.guardian = args.guardian - - if args.gas_limit: - tx.gas_limit = int(args.gas_limit) - - tx.signature = bytes.fromhex(owner.sign_transaction(tx)) - - return tx - - def prepare_transaction_for_changing_service_fee(self, owner: IAccount, args: Any) -> ITransaction: - delegation_contract = Address.new_from_bech32(args.delegation_contract) - - tx = self._factory.create_transaction_for_changing_service_fee( - sender=owner.address, - delegation_contract=delegation_contract, - service_fee=int(args.service_fee) - ) - tx.nonce = int(args.nonce) - tx.version = int(args.version) - tx.options = int(args.options) - tx.guardian = args.guardian - - if args.gas_limit: - tx.gas_limit = int(args.gas_limit) - - tx.signature = bytes.fromhex(owner.sign_transaction(tx)) - - return tx - - def prepare_transaction_for_modifying_delegation_cap(self, owner: IAccount, args: Any) -> ITransaction: - delegation_contract = Address.new_from_bech32(args.delegation_contract) - - tx = self._factory.create_transaction_for_modifying_delegation_cap( - sender=owner.address, - delegation_contract=delegation_contract, - delegation_cap=int(args.delegation_cap) - ) - tx.nonce = int(args.nonce) - tx.version = int(args.version) - tx.options = int(args.options) - tx.guardian = args.guardian - - if args.gas_limit: - tx.gas_limit = int(args.gas_limit) - - tx.signature = bytes.fromhex(owner.sign_transaction(tx)) - - return tx - - def prepare_transaction_for_automatic_activation(self, owner: IAccount, args: Any) -> ITransaction: - delegation_contract = Address.new_from_bech32(args.delegation_contract) - - if args.set: - tx = self._factory.create_transaction_for_setting_automatic_activation( - sender=owner.address, - delegation_contract=delegation_contract - ) - elif args.unset: - tx = self._factory.create_transaction_for_unsetting_automatic_activation( - sender=owner.address, - delegation_contract=delegation_contract - ) - else: - raise BadUsage("Either `--set` or `--unset` should be provided") - - tx.nonce = int(args.nonce) - tx.version = int(args.version) - tx.options = int(args.options) - tx.guardian = args.guardian - - if args.gas_limit: - tx.gas_limit = int(args.gas_limit) - - tx.signature = bytes.fromhex(owner.sign_transaction(tx)) - - return tx - - def prepare_transaction_for_redelegate_cap(self, owner: IAccount, args: Any) -> ITransaction: - delegation_contract = Address.new_from_bech32(args.delegation_contract) - - if args.set: - tx = self._factory.create_transaction_for_setting_cap_check_on_redelegate_rewards( - sender=owner.address, - delegation_contract=delegation_contract - ) - elif args.unset: - tx = self._factory.create_transaction_for_unsetting_cap_check_on_redelegate_rewards( - sender=owner.address, - delegation_contract=delegation_contract - ) - else: - raise BadUsage("Either `--set` or `--unset` should be provided") - - tx.nonce = int(args.nonce) - tx.version = int(args.version) - tx.options = int(args.options) - tx.guardian = args.guardian - - if args.gas_limit: - tx.gas_limit = int(args.gas_limit) - - tx.signature = bytes.fromhex(owner.sign_transaction(tx)) - - return tx - - def prepare_transaction_for_setting_metadata(self, owner: IAccount, args: Any) -> ITransaction: - delegation_contract = Address.new_from_bech32(args.delegation_contract) - - tx = self._factory.create_transaction_for_setting_metadata( - sender=owner.address, - delegation_contract=delegation_contract, - name=args.name, - website=args.website, - identifier=args.identifier - ) - tx.nonce = int(args.nonce) - tx.version = int(args.version) - tx.options = int(args.options) - tx.guardian = args.guardian - - if args.gas_limit: - tx.gas_limit = int(args.gas_limit) - - tx.signature = bytes.fromhex(owner.sign_transaction(tx)) - - return tx - - def prepare_transaction_for_creating_delegation_contract_from_validator(self, owner: IAccount, args: Any) -> ITransaction: - receiver = Address.new_from_hex("000000000000000000010000000000000000000000000000000000000004ffff", get_address_hrp()).to_bech32() - max_cap = int(args.max_cap) - fee = int(args.fee) - data = "makeNewContractFromValidatorData@" + args_to_string([max_cap, fee]) - - tx = Transaction( - sender=owner.address.to_bech32(), - receiver=receiver, - gas_limit=510000000, - chain_id=self._factory.config.chain_id, - data=data.encode(), - nonce=int(args.nonce), - version=int(args.version), - options=int(args.options), - guardian=args.guardian - ) - - if args.gas_limit: - tx.gas_limit = int(args.gas_limit) - - tx.signature = bytes.fromhex(owner.sign_transaction(tx)) - - return tx - - def _load_validators_public_keys(self, args: Any) -> List[ValidatorPublicKey]: - if args.bls_keys: - return self._parse_public_bls_keys(args.bls_keys) - - validators_file_path = Path(args.validators_file).expanduser() - validators_file = ValidatorsFile(validators_file_path) - return validators_file.load_public_keys() - - def _parse_public_bls_keys(self, public_bls_keys: str) -> List[ValidatorPublicKey]: - keys = public_bls_keys.split(",") - validator_public_keys: List[ValidatorPublicKey] = [] - - for key in keys: - validator_public_keys.append(ValidatorPublicKey(bytes.fromhex(key))) - - return validator_public_keys - - def _get_public_keys_and_signed_messages(self, args: Any) -> Tuple[List[ValidatorPublicKey], List[bytes]]: - validators_file_path = Path(args.validators_file).expanduser() - validators_file = ValidatorsFile(validators_file_path) - signers = validators_file.load_signers() - - pubkey = Address.new_from_bech32(args.delegation_contract).get_public_key() - - public_keys: List[ValidatorPublicKey] = [] - signed_messages: List[bytes] = [] - for signer in signers: - signed_message = signer.sign(pubkey) - - public_keys.append(signer.secret_key.generate_public_key()) - signed_messages.append(signed_message) - - return public_keys, signed_messages diff --git a/multiversx_sdk_cli/dependencies/__init__.py b/multiversx_sdk_cli/dependencies/__init__.py index c21f5006..5ecc47b2 100644 --- a/multiversx_sdk_cli/dependencies/__init__.py +++ b/multiversx_sdk_cli/dependencies/__init__.py @@ -1,6 +1,15 @@ -from multiversx_sdk_cli.dependencies.install import (get_all_deps, get_golang, - get_module_by_key, - get_module_directory, - install_module) +from multiversx_sdk_cli.dependencies.install import ( + get_all_deps, + get_golang, + get_module_by_key, + get_module_directory, + install_module, +) -__all__ = ["install_module", "get_module_directory", "get_module_by_key", "get_golang", "get_all_deps"] +__all__ = [ + "install_module", + "get_module_directory", + "get_module_by_key", + "get_golang", + "get_all_deps", +] diff --git a/multiversx_sdk_cli/dependencies/install.py b/multiversx_sdk_cli/dependencies/install.py index 61bf0fe4..c9fb8d29 100644 --- a/multiversx_sdk_cli/dependencies/install.py +++ b/multiversx_sdk_cli/dependencies/install.py @@ -3,15 +3,17 @@ from typing import Dict, List from multiversx_sdk_cli import config, errors -from multiversx_sdk_cli.dependencies.modules import (DependencyModule, - GolangModule, Rust, - TestWalletsModule) +from multiversx_sdk_cli.dependencies.modules import ( + DependencyModule, + GolangModule, + TestWalletsModule, +) logger = logging.getLogger("install") def install_module(key: str, overwrite: bool = False): - if key == 'all': + if key == "all": modules = get_all_deps() else: modules = [get_module_by_key(key)] @@ -48,13 +50,12 @@ def get_deps_dict() -> Dict[str, DependencyModule]: def get_all_deps() -> List[DependencyModule]: return [ - Rust(key="rust"), GolangModule(key="golang"), - TestWalletsModule(key="testwallets") + TestWalletsModule(key="testwallets"), ] def get_golang() -> GolangModule: - golang = get_module_by_key('golang') + golang = get_module_by_key("golang") assert isinstance(golang, GolangModule) return golang diff --git a/multiversx_sdk_cli/dependencies/modules.py b/multiversx_sdk_cli/dependencies/modules.py index 3455b24d..3d2c4df9 100644 --- a/multiversx_sdk_cli/dependencies/modules.py +++ b/multiversx_sdk_cli/dependencies/modules.py @@ -1,22 +1,21 @@ import logging import os -import platform import shutil from os import path from pathlib import Path -from typing import Dict, List, Optional +from typing import Optional -from multiversx_sdk_cli import (config, dependencies, downloader, errors, - myprocess, utils, workstation) +from multiversx_sdk_cli import config, downloader, errors, utils, workstation from multiversx_sdk_cli.dependencies.resolution import ( - DependencyResolution, get_dependency_resolution) -from multiversx_sdk_cli.ux import show_message, show_warning + DependencyResolution, + get_dependency_resolution, +) logger = logging.getLogger("modules") class DependencyModule: - def __init__(self, key: str, aliases: List[str] = []): + def __init__(self, key: str, aliases: list[str] = []): self.key = key self.aliases = aliases @@ -55,7 +54,7 @@ def uninstall(self, tag: str) -> None: def is_installed(self, tag: str) -> bool: raise NotImplementedError() - def get_env(self) -> Dict[str, str]: + def get_env(self) -> dict[str, str]: raise NotImplementedError() def get_resolution(self) -> DependencyResolution: @@ -63,11 +62,13 @@ def get_resolution(self) -> DependencyResolution: class StandaloneModule(DependencyModule): - def __init__(self, - key: str, - aliases: List[str] = [], - repo_name: Optional[str] = None, - organisation: Optional[str] = None): + def __init__( + self, + key: str, + aliases: list[str] = [], + repo_name: Optional[str] = None, + organisation: Optional[str] = None, + ): super().__init__(key, aliases) self.archive_type = "tar.gz" self.repo_name = repo_name @@ -113,8 +114,8 @@ def get_source_directory(self, tag: str) -> Path: tag_no_v = tag_no_v[1:] assert isinstance(self.repo_name, str) - source_folder_option_1 = self.get_directory(tag) / f'{self.repo_name}-{tag_no_v}' - source_folder_option_2 = self.get_directory(tag) / f'{self.repo_name}-{tag}' + source_folder_option_1 = self.get_directory(tag) / f"{self.repo_name}-{tag_no_v}" + source_folder_option_2 = self.get_directory(tag) / f"{self.repo_name}-{tag}" return source_folder_option_1 if source_folder_option_1.exists() else source_folder_option_2 def get_parent_directory(self) -> Path: @@ -155,7 +156,7 @@ def is_installed(self, tag: str) -> bool: raise errors.BadDependencyResolution(self.key, resolution) - def get_env(self) -> Dict[str, str]: + def get_env(self) -> dict[str, str]: resolution = self.get_resolution() directory = self.get_directory(config.get_dependency_tag(self.key)) parent_directory = self.get_parent_directory() @@ -165,7 +166,7 @@ def get_env(self) -> Dict[str, str]: "PATH": os.environ.get("PATH", ""), "GOPATH": os.environ.get("GOPATH", ""), "GOCACHE": os.environ.get("GOCACHE", ""), - "GOROOT": os.environ.get("GOROOT", "") + "GOROOT": os.environ.get("GOROOT", ""), } if resolution == DependencyResolution.SDK: current_path = os.environ.get("PATH", "") @@ -178,7 +179,7 @@ def get_env(self) -> Dict[str, str]: "PATH": f"{(directory / 'go' / 'bin')}:{current_path_without_go}", "GOPATH": str(self.get_gopath()), "GOCACHE": str(parent_directory / "GOCACHE"), - "GOROOT": str(directory / "go") + "GOROOT": str(directory / "go"), } raise errors.BadDependencyResolution(self.key, resolution) @@ -187,173 +188,6 @@ def get_gopath(self) -> Path: return self.get_parent_directory() / "GOPATH" -class Rust(DependencyModule): - def is_installed(self, tag: str) -> bool: - which_rustc = shutil.which("rustc") - which_cargo = shutil.which("cargo") - which_sc_meta = shutil.which("sc-meta") - which_mx_scenario_go = shutil.which("mx-scenario-go") - which_wasm_opt = shutil.which("wasm-opt") - which_twiggy = shutil.which("twiggy") - logger.info(f"which rustc: {which_rustc}") - logger.info(f"which cargo: {which_cargo}") - logger.info(f"which sc-meta: {which_sc_meta}") - logger.info(f"which mx-scenario-go: {which_mx_scenario_go}") - logger.info(f"which wasm-opt: {which_wasm_opt}") - logger.info(f"which twiggy: {which_twiggy}") - - dependencies = [which_rustc, which_cargo, which_sc_meta, which_wasm_opt, which_twiggy] - installed = all(dependency is not None for dependency in dependencies) - - if installed: - actual_version_installed = self._get_actual_installed_version() - - if tag in actual_version_installed: - logger.info(f"[{self.key} {tag}] is installed.") - elif "not found" in actual_version_installed: - show_warning("You have installed Rust without using `rustup`.") - else: - show_warning(f"The Rust version you have installed does not match the recommended version.\nInstalled [{actual_version_installed}], expected [{tag}].") - - return installed - - def _get_actual_installed_version(self) -> str: - args = ["rustup", "default"] - output = myprocess.run_process(args, dump_to_stdout=False) - return output.strip() - - def install(self, overwrite: bool) -> None: - self._check_install_env(apply_correction=overwrite) - - module = dependencies.get_module_by_key("rust") - tag: str = config.get_dependency_tag(module.key) - - if not overwrite: - show_warning(f"We recommend using rust {tag}. If you'd like to overwrite your current version please run `mxpy deps install rust --overwrite`.") - logger.info(f"install: key={self.key}, tag={tag}, overwrite={overwrite}") - - if overwrite: - logger.info("Overwriting the current rust version...") - elif self.is_installed(""): - return - - self._install_rust(tag) - self._install_sc_meta() - self._install_wasm_opt() - self._install_twiggy() - show_message("To ensure sc-meta functions correctly, please install all the required dependencies by executing the following command: `sc-meta install all`.") - - def _check_install_env(self, apply_correction: bool = True): - """ - See https://rust-lang.github.io/rustup/installation/index.html#choosing-where-to-install. - """ - - current_cargo_home = os.environ.get("CARGO_HOME", None) - current_rustup_home = os.environ.get("RUSTUP_HOME", None) - if current_cargo_home: - show_warning(f"""CARGO_HOME variable is set to: {current_cargo_home}. -This may cause problems with the installation.""") - - if apply_correction: - show_warning("CARGO_HOME will be temporarily unset.") - os.environ["CARGO_HOME"] = "" - - if current_rustup_home: - show_warning(f"""RUSTUP_HOME variable is set to: {current_rustup_home}. -This may cause problems with the installation of rust.""") - - if apply_correction: - show_warning("RUSTUP_HOME will be temporarily unset.") - os.environ["RUSTUP_HOME"] = "" - - def _install_rust(self, tag: str) -> None: - installer_url = self._get_installer_url() - installer_path = self._get_installer_path() - - downloader.download(installer_url, str(installer_path)) - utils.mark_executable(str(installer_path)) - - if tag: - toolchain = tag - else: - toolchain = "stable" - - args = [str(installer_path), "--verbose", "--default-toolchain", toolchain, "--profile", - "minimal", "--target", "wasm32-unknown-unknown", "-y"] - - logger.info("Installing rust.") - myprocess.run_process(args) - - def _install_sc_meta(self): - logger.info("Installing multiversx-sc-meta.") - tag = config.get_dependency_tag("sc-meta") - args = ["cargo", "install", "multiversx-sc-meta", "--locked"] - - if tag: - args.extend(["--version", tag]) - - myprocess.run_process(args, env=self.get_cargo_env()) - - def _install_wasm_opt(self): - logger.info("Installing wasm-opt. This may take a while.") - tag = config.get_dependency_tag("wasm-opt") - args = ["cargo", "install", "wasm-opt", "--version", tag] - myprocess.run_process(args, env=self.get_cargo_env()) - - def _install_twiggy(self): - logger.info("Installing twiggy.") - tag = config.get_dependency_tag("twiggy") - args = ["cargo", "install", "twiggy"] - - if tag: - args.extend(["--version", tag]) - - myprocess.run_process(args, env=self.get_cargo_env()) - - def _install_sc_meta_deps(self): - # this is needed for sc-meta to run the tests - # if os is Windows skip installation - os = platform.system().lower() - if os == "windows": - logger.warning("`sc-meta install all` command is not supported on windows") - return - - logger.info("Installing sc-meta dependencies.") - args = ["sc-meta", "install", "all"] - myprocess.run_process(args) - - def _get_installer_url(self) -> str: - if workstation.is_windows(): - return "https://win.rustup.rs" - return "https://sh.rustup.rs" - - def _get_installer_path(self) -> Path: - tools_folder = workstation.get_tools_folder() - - if workstation.is_windows(): - return tools_folder / "rustup-init.exe" - return tools_folder / "rustup.sh" - - def uninstall(self, tag: str): - directory = self.get_directory("") - if os.path.isdir(directory): - shutil.rmtree(directory) - - def get_directory(self, tag: str) -> Path: - tools_folder = workstation.get_tools_folder() - return tools_folder / "vendor-rust" - - def get_env(self) -> Dict[str, str]: - return dict(os.environ) - - def get_cargo_env(self) -> Dict[str, str]: - env = self.get_env() - cargo = Path("~/.cargo/bin").expanduser() - path = env["PATH"] - env["PATH"] = f"{str(cargo)}:{path}" - return env - - class TestWalletsModule(StandaloneModule): def __init__(self, key: str): super().__init__(key, []) diff --git a/multiversx_sdk_cli/dependency_checker.py b/multiversx_sdk_cli/dependency_checker.py deleted file mode 100644 index ffe0d180..00000000 --- a/multiversx_sdk_cli/dependency_checker.py +++ /dev/null @@ -1,11 +0,0 @@ -from multiversx_sdk_cli import config, errors, ux -from multiversx_sdk_cli.dependencies.modules import Rust - - -def check_if_rust_is_installed(): - RUST_MODULE_KEY = "rust" - rust_module = Rust(RUST_MODULE_KEY) - if not rust_module.is_installed(""): - tag = config.get_dependency_tag(RUST_MODULE_KEY) - ux.show_critical_error("Rust is not installed on your machine. Run `mxpy deps install rust --overwrite` and try again.") - raise errors.DependencyMissing(RUST_MODULE_KEY, tag) diff --git a/multiversx_sdk_cli/dns.py b/multiversx_sdk_cli/dns.py index 61e387c4..95ab0a85 100644 --- a/multiversx_sdk_cli/dns.py +++ b/multiversx_sdk_cli/dns.py @@ -1,91 +1,103 @@ -from typing import Any, List, Protocol +from typing import Any, Protocol from Cryptodome.Hash import keccak -from multiversx_sdk import Address, AddressComputer, TransactionsFactoryConfig -from multiversx_sdk.network_providers.network_config import NetworkConfig - -from multiversx_sdk_cli import cli_shared, utils -from multiversx_sdk_cli.accounts import Account +from multiversx_sdk import ( + Address, + AddressComputer, + SmartContractQuery, + SmartContractQueryResponse, +) + +from multiversx_sdk_cli import cli_shared +from multiversx_sdk_cli.args_validation import ( + ensure_wallet_args_are_provided, + validate_broadcast_args, + validate_chain_id_args, + validate_transaction_args, +) from multiversx_sdk_cli.config import get_address_hrp from multiversx_sdk_cli.constants import ADDRESS_ZERO_HEX -from multiversx_sdk_cli.contracts import SmartContract -from multiversx_sdk_cli.transactions import (compute_relayed_v1_data, - do_prepare_transaction) +from multiversx_sdk_cli.transactions import TransactionsController MaxNumShards = 256 ShardIdentiferLen = 2 InitialDNSAddress = bytes([1] * 32) +# fmt: off class INetworkProvider(Protocol): - def query_contract(self, query: Any) -> Any: - ... - - def get_network_config(self) -> NetworkConfig: + def query_contract(self, query: SmartContractQuery) -> SmartContractQueryResponse: ... +# fmt: on def resolve(name: str, proxy: INetworkProvider) -> Address: - name_arg = "0x{}".format(str.encode(name).hex()) dns_address = dns_address_for_name(name) - response = _query_contract( - contract_address=dns_address, - proxy=proxy, - function="resolve", - args=[name_arg] - ) + response = _query_contract(contract_address=dns_address, proxy=proxy, function="resolve", args=[name.encode()]) - if len(response) == 0: + if len(response.return_data_parts) == 0: return Address.new_from_hex(ADDRESS_ZERO_HEX, get_address_hrp()) - result = response[0].get("returnDataParts")[0] - return Address.new_from_hex(result, get_address_hrp()) + result = response.return_data_parts[0] + return Address(result, get_address_hrp()) def validate_name(name: str, shard_id: int, proxy: INetworkProvider): - name_arg = "0x{}".format(str.encode(name).hex()) dns_address = compute_dns_address_for_shard_id(shard_id) response = _query_contract( contract_address=dns_address, proxy=proxy, function="validateName", - args=[name_arg] + args=[name.encode()], ) - response = response[0] - - return_code = response["returnCode"] + return_code: str = response.return_code if return_code == "ok": print(f"name [{name}] is valid") else: print(f"name [{name}] is invalid") - print(response) - def register(args: Any): - args = utils.as_object(args) - - cli_shared.check_guardian_and_options_args(args) - cli_shared.check_broadcast_args(args) - cli_shared.prepare_nonce_in_args(args) - cli_shared.prepare_chain_id_in_args(args) - args.receiver = dns_address_for_name(args.name).bech32() - args.data = dns_register_data(args.name) - - tx = do_prepare_transaction(args) + validate_transaction_args(args) + ensure_wallet_args_are_provided(args) + validate_broadcast_args(args) + validate_chain_id_args(args) + + sender = cli_shared.prepare_sender(args) + guardian_and_relayer_data = cli_shared.get_guardian_and_relayer_data( + sender=sender.address.to_bech32(), + args=args, + ) - if hasattr(args, "relay") and args.relay: - args.outfile.write(compute_relayed_v1_data(tx)) - return + native_amount = int(args.value) + + receiver = dns_address_for_name(args.name) + data = dns_register_data(args.name) + + chain_id = cli_shared.get_chain_id(args.chain, args.proxy) + controller = TransactionsController(chain_id) + + tx = controller.create_transaction( + sender=sender, + receiver=receiver, + native_amount=native_amount, + gas_limit=args.gas_limit, + gas_price=args.gas_price, + nonce=sender.nonce, + version=args.version, + options=args.options, + data=data, + guardian_and_relayer_data=guardian_and_relayer_data, + ) cli_shared.send_or_simulate(tx, args) -def compute_all_dns_addresses() -> List[Address]: - addresses: List[Address] = [] +def compute_all_dns_addresses() -> list[Address]: + addresses: list[Address] = [] for i in range(0, 256): addresses.append(compute_dns_address_for_shard_id(i)) return addresses @@ -102,30 +114,21 @@ def registration_cost(shard_id: int, proxy: INetworkProvider) -> int: contract_address=dns_address, proxy=proxy, function="getRegistrationCost", - args=[] + args=[], ) - response = response[0] - - data = response["returnDataParts"][0] + data = response.return_data_parts[0] if not data: return 0 else: - return int("0x{}".format(data)) + return int.from_bytes(data, byteorder="big", signed=False) def version(shard_id: int, proxy: INetworkProvider) -> str: dns_address = compute_dns_address_for_shard_id(shard_id) - response = _query_contract( - contract_address=dns_address, - proxy=proxy, - function="version", - args=[] - ) - - response = response[0] - return bytearray.fromhex(response["returnDataParts"][0]).decode() + response = _query_contract(contract_address=dns_address, proxy=proxy, function="version", args=[]) + return response.return_data_parts[0].decode() def dns_address_for_name(name: str) -> Address: @@ -135,30 +138,26 @@ def dns_address_for_name(name: str) -> Address: def compute_dns_address_for_shard_id(shard_id: int) -> Address: - deployer_pubkey_prefix = InitialDNSAddress[:len(InitialDNSAddress) - ShardIdentiferLen] + deployer_pubkey_prefix = InitialDNSAddress[: len(InitialDNSAddress) - ShardIdentiferLen] deployer_pubkey = deployer_pubkey_prefix + bytes([0, shard_id]) - deployer = Account(address=Address(deployer_pubkey, get_address_hrp())) - deployer.nonce = 0 + deployer = Address(deployer_pubkey, get_address_hrp()) + nonce = 0 address_computer = AddressComputer(number_of_shards=3) - contract_address = address_computer.compute_contract_address(deployer.address, deployer.nonce) + contract_address = address_computer.compute_contract_address(deployer, nonce) return contract_address def dns_register_data(name: str) -> str: - name_enc: bytes = str.encode(name) - return "register@{}".format(name_enc.hex()) - - -def _query_contract(contract_address: Address, proxy: INetworkProvider, function: str, args: List[Any]) -> List[Any]: - chain_id = proxy.get_network_config().chain_id - config = TransactionsFactoryConfig(chain_id) - contract = SmartContract(config) - - return contract.query_contract( - contract_address=contract_address, - proxy=proxy, - function=function, - arguments=args, - should_prepare_args=False - ) + name_as_hex = str.encode(name).hex() + return f"register@{name_as_hex}" + + +def _query_contract( + contract_address: Address, + proxy: INetworkProvider, + function: str, + args: list[bytes], +) -> SmartContractQueryResponse: + query = SmartContractQuery(contract=contract_address, function=function, arguments=args) + return proxy.query_contract(query) diff --git a/multiversx_sdk_cli/docker.py b/multiversx_sdk_cli/docker.py index 02deb8bb..79767326 100644 --- a/multiversx_sdk_cli/docker.py +++ b/multiversx_sdk_cli/docker.py @@ -2,7 +2,6 @@ import os import subprocess from pathlib import Path -from typing import List from multiversx_sdk_cli.errors import KnownError @@ -23,16 +22,16 @@ def is_docker_installed(): def run_docker( - image: str, - project_path: Path, - contract: str, - output_path: Path, - no_wasm_opt: bool, - docker_interactive: bool, - docker_tty: bool, - no_default_platform: bool + image: str, + project_path: Path, + contract: str, + output_path: Path, + no_wasm_opt: bool, + docker_interactive: bool, + docker_tty: bool, + no_default_platform: bool, ): - docker_mount_args: List[str] = ["--volume", f"{output_path}:/output"] + docker_mount_args: list[str] = ["--volume", f"{output_path}:/output"] if project_path: docker_mount_args.extend(["--volume", f"{project_path}:/project"]) @@ -51,7 +50,7 @@ def run_docker( docker_args += ["--user", f"{str(os.getuid())}:{str(os.getgid())}"] docker_args += ["--rm", image] - entrypoint_args: List[str] = [] + entrypoint_args: list[str] = [] if project_path: entrypoint_args.extend(["--project", "project"]) diff --git a/multiversx_sdk_cli/downloader.py b/multiversx_sdk_cli/downloader.py index 93dab7c1..7b49d0db 100644 --- a/multiversx_sdk_cli/downloader.py +++ b/multiversx_sdk_cli/downloader.py @@ -1,4 +1,3 @@ - import logging import sys @@ -9,9 +8,9 @@ logger = logging.getLogger("downloader") CHUNK_SIZE = 1024 * 16 -LINECLEAR = '\r' + ' ' * 20 + '\r' +LINECLEAR = "\r" + " " * 20 + "\r" -PROGRESS_RULER = 'Downloading...\n|_,_,_,_,_,,_,_,_,_,_|' +PROGRESS_RULER = "Downloading...\n|_,_,_,_,_,,_,_,_,_,_|" def download(url: str, filename: str) -> None: @@ -25,22 +24,21 @@ def download(url: str, filename: str) -> None: response = requests.get(url, stream=True) response.raise_for_status() print(PROGRESS_RULER, file=sys.stderr) - print(' ', end='', file=sys.stderr) + print(" ", end="", file=sys.stderr) sys.stderr.flush() total_size = int(response.headers.get("content-length", 0)) chunk_number = 0 progress = 0 - with open(filename, 'wb') as file: + with open(filename, "wb") as file: for chunk in response.iter_content(chunk_size=CHUNK_SIZE): file.write(chunk) progress = _report_download_progress(progress, chunk_number, total_size) chunk_number += 1 file.flush() - print('', file=sys.stderr) + print("", file=sys.stderr) sys.stderr.flush() except requests.HTTPError as err: - raise errors.DownloadError( - f"Could not download [{url}] to [{filename}]") from err + raise errors.DownloadError(f"Could not download [{url}] to [{filename}]") from err logger.info("Download done.") @@ -50,8 +48,8 @@ def _report_download_progress(progress: int, chunk_number: int, total_size: int) num_chunks = int(total_size / CHUNK_SIZE) new_progress = int((chunk_number / num_chunks) * 20) if new_progress > progress: - progress_markers = '·' * (new_progress - progress) - print(progress_markers, end='', file=sys.stderr) + progress_markers = "·" * (new_progress - progress) + print(progress_markers, end="", file=sys.stderr) sys.stderr.flush() return new_progress except ZeroDivisionError: diff --git a/multiversx_sdk_cli/errors.py b/multiversx_sdk_cli/errors.py index 2164ad6d..be13e41b 100644 --- a/multiversx_sdk_cli/errors.py +++ b/multiversx_sdk_cli/errors.py @@ -1,4 +1,4 @@ -from typing import Any, List, Tuple, Union +from typing import Any, Union class KnownError(Exception): @@ -38,7 +38,7 @@ def __init__(self, name: str, tag: str): class DependenciesMissing(KnownError): - def __init__(self, dependencies: List[Tuple[str, str]]): + def __init__(self, dependencies: list[tuple[str, str]]): message = "Dependencies missing: \n" for dependency in dependencies: @@ -94,9 +94,11 @@ def __init__(self, input: str, message: str): class ExternalProcessError(KnownError): def __init__(self, command_line: str, message: str): - super().__init__(f"""External process error: + super().__init__( + f"""External process error: Command line: {command_line} -Output: {message}""") +Output: {message}""" + ) class UnknownConfigurationError(KnownError): @@ -161,11 +163,7 @@ def __init__(self, message: str): class ProxyError(KnownError): def __init__(self, message: str, url: str, data: str, code: str): - inner = { - "url": url, - "data": data, - "code": code - } + inner = {"url": url, "data": data, "code": code} super().__init__(message, inner) @@ -179,11 +177,11 @@ def __init__(self, message: str, inner: Any = None): super().__init__(message, str(inner)) -class NativeAuthClientError(KnownError): +class IncorrectWalletError(KnownError): def __init__(self, message: str): super().__init__(message) -class IncorrectWalletError(KnownError): +class InvalidArgumentsError(KnownError): def __init__(self, message: str): super().__init__(message) diff --git a/multiversx_sdk_cli/guardian_relayer_data.py b/multiversx_sdk_cli/guardian_relayer_data.py new file mode 100644 index 00000000..a47268f6 --- /dev/null +++ b/multiversx_sdk_cli/guardian_relayer_data.py @@ -0,0 +1,16 @@ +from dataclasses import dataclass +from typing import Optional + +from multiversx_sdk import Address + +from multiversx_sdk_cli.interfaces import IAccount + + +@dataclass +class GuardianRelayerData: + guardian: Optional[IAccount] = None + guardian_address: Optional[Address] = None + guardian_service_url: Optional[str] = None + guardian_2fa_code: Optional[str] = None + relayer: Optional[IAccount] = None + relayer_address: Optional[Address] = None diff --git a/multiversx_sdk_cli/interfaces.py b/multiversx_sdk_cli/interfaces.py index 469047a0..8a533e7c 100644 --- a/multiversx_sdk_cli/interfaces.py +++ b/multiversx_sdk_cli/interfaces.py @@ -1,39 +1,16 @@ -from typing import Any, Dict, Protocol +from typing import Any, Protocol - -class IAddress(Protocol): - def to_hex(self) -> str: - ... - - def to_bech32(self) -> str: - ... - - -class ITransaction(Protocol): - sender: str - receiver: str - gas_limit: int - chain_id: str - nonce: int - value: int - sender_username: str - receiver_username: str - gas_price: int - data: bytes - version: int - options: int - guardian: str - signature: bytes - guardian_signature: bytes - relayer: str - relayer_signature: bytes +from multiversx_sdk import Address, Transaction +# fmt: off class IAccount(Protocol): - def sign_transaction(self, transaction: ITransaction) -> str: + address: Address + + def sign_transaction(self, transaction: Transaction) -> bytes: ... class ISimulateResponse(Protocol): - def to_dictionary(self) -> Dict[str, Any]: + def to_dictionary(self) -> dict[str, Any]: ... diff --git a/multiversx_sdk_cli/ledger/__init__.py b/multiversx_sdk_cli/ledger/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/multiversx_sdk_cli/ledger/config.py b/multiversx_sdk_cli/ledger/config.py deleted file mode 100644 index f9584006..00000000 --- a/multiversx_sdk_cli/ledger/config.py +++ /dev/null @@ -1,38 +0,0 @@ -class LedgerAppConfiguration: - data_activated: bool - account_index: int - address_index: int - version: str - - -def load_ledger_config_from_response(response: bytes) -> LedgerAppConfiguration: - config = LedgerAppConfiguration() - - config.data_activated = False - if response[0] == 0x01: - config.data_activated = True - - config.account_index = response[1] - config.address_index = response[2] - - version = str(response[3]) + "." + str(response[4]) + "." + str(response[5]) - config.version = version - - return config - - -def compare_versions(version1: str, version2: str) -> int: - version1_tuple = version_tuple(version1) - version2_tuple = version_tuple(version2) - if version1_tuple == version2_tuple: - return 0 - if version1_tuple < version2_tuple: - return -1 - return 1 - - -def version_tuple(v): - filled = [] - for point in v.split("."): - filled.append(point.zfill(8)) - return tuple(filled) diff --git a/multiversx_sdk_cli/ledger/ledger_app_handler.py b/multiversx_sdk_cli/ledger/ledger_app_handler.py deleted file mode 100644 index 2ec9e955..00000000 --- a/multiversx_sdk_cli/ledger/ledger_app_handler.py +++ /dev/null @@ -1,167 +0,0 @@ -from typing import List - -from ledgercomm import Transport - -from multiversx_sdk_cli.errors import LedgerError -from multiversx_sdk_cli.ledger.config import (LedgerAppConfiguration, - load_ledger_config_from_response) - -SIGN_USING_HASH_VERSION = "1.0.11" -CONNECTION_ERROR_MSG = "check if device is plugged in, unlocked and on MultiversX app" - -# Also see: https://github.com/multiversx/mx-sdk-js-hw-provider/blob/main/src/ledgerApp.ts -CLA = 0xed -SIGN_RAW_TX_INS = 0x04 -SIGN_HASH_TX_INS = 0x07 -SIGN_MESSAGE_INS = 0x06 -PROVIDE_ESDT_INFO_INS = 0x08 -GET_ADDRESS_AUTH_TOKEN_INS = 0x09 - - -class Apdu: - cla: int - ins: int - p1: int - p2: int - data: bytes - - -class LedgerApp: - def __init__(self): - try: - self.transport = Transport(interface="hid", debug=False) # Nano S/X using HID interface - except Exception: - raise LedgerError(CONNECTION_ERROR_MSG) - - def close(self): - self.transport.close() - - def set_address(self, account_index: int = 0, address_index: int = 0): - data = account_index.to_bytes(4, byteorder='big') + address_index.to_bytes(4, byteorder='big') - self.transport.send(cla=0xed, ins=0x05, p1=0x00, p2=0x00, cdata=data) - sw, _ = self.transport.recv() - err = get_error(sw) - if err != '': - raise LedgerError(err) - - def get_address(self, account_index: int = 0, address_index: int = 0) -> str: - data = account_index.to_bytes(4, byteorder='big') + address_index.to_bytes(4, byteorder='big') - - self.transport.send(cla=0xed, ins=0x03, p1=0x00, p2=0x00, cdata=data) - sw, response = self.transport.recv() - assert isinstance(response, bytes) - - err = get_error(sw) - if err != '': - raise LedgerError(CONNECTION_ERROR_MSG + " (" + err + ")") - - response_body = response[1:] - address = response_body.decode("utf-8") - return address - - def get_app_configuration(self) -> LedgerAppConfiguration: - self.transport.send(cla=0xed, ins=0x02, p1=0x00, p2=0x00, cdata=b"") - sw, response = self.transport.recv() - err = get_error(sw) - if err != '': - raise LedgerError(CONNECTION_ERROR_MSG + " (" + err + ")") - return load_ledger_config_from_response(response) - - def get_version(self) -> str: - config = self.get_app_configuration() - return config.version - - def sign_transaction(self, tx_bytes: bytes, should_use_hash_signing: bool) -> str: - ins_signing_method = SIGN_RAW_TX_INS - if should_use_hash_signing: - ins_signing_method = SIGN_HASH_TX_INS - - return self._do_sign(tx_bytes, ins_signing_method) - - def sign_message(self, message_bytes: bytes) -> str: - return self._do_sign(message_bytes, SIGN_MESSAGE_INS) - - def _do_sign(self, data: bytes, ins_signing_method: int) -> str: - total_size = len(data) - max_chunk_size = 150 - - apdus: List[Apdu] = [] - - offset = 0 - while offset != total_size: - is_first = offset == 0 - - apdu = Apdu() - - if is_first: - apdu.p1 = 0x00 - else: - apdu.p1 = 0x80 - - has_more = offset + max_chunk_size < total_size - chunk_size = total_size - offset - if has_more: - chunk_size = max_chunk_size - - apdu.ins = ins_signing_method - apdu.p2 = 0x00 - apdu.cla = CLA - apdu.data = data[offset:offset + chunk_size] - - apdus.append(apdu) - - offset += chunk_size - - return self.get_signature_from_apdus(apdus) - - def get_signature_from_apdus(self, apdus: List[Apdu]) -> str: - sw: int - response: bytes - for apdu in apdus: - self.transport.send( - cla=apdu.cla, - ins=apdu.ins, - p1=apdu.p1, - p2=apdu.p2, - cdata=apdu.data) - sw, response = self.transport.recv() - - assert isinstance(response, bytes) - if len(response) != 65 or response[0] != 64 or get_error(sw) != '': - err_message = "signature failed" - err = get_error(sw) - if err != '': - err_message += ': ' + err - raise LedgerError(err_message) - - response_body = response[1:] - signature = response_body.hex() - return signature - - -def get_error(code: int): - switcher = { - 0x9000: '', - 0x6985: 'user denied', - 0x6D00: 'unknown instruction', - 0x6E00: 'wrong cla', - 0x6E10: 'signature failed', - 0x6E01: 'invalid arguments', - 0x6E02: 'invalid message', - 0x6E03: 'invalid p1', - 0x6E04: 'message too long', - 0x6E05: 'receiver too long', - 0x6E06: 'amount too long', - 0x6E07: 'contract data disabled', - 0x6E08: 'message incomplete', - 0x6E09: 'wrong tx version', - 0x6E0A: 'nonce too long', - 0x6E0B: 'invalid amount', - 0x6E0C: 'invalid fee', - 0x6E0D: 'pretty failed', - 0x6E0E: 'data too long', - 0x6E0F: 'wrong tx options', - 0x6E11: 'regular signing is deprecated', - } - - return switcher.get(code, "unknown error code: " + hex(code)) diff --git a/multiversx_sdk_cli/ledger/ledger_functions.py b/multiversx_sdk_cli/ledger/ledger_functions.py deleted file mode 100644 index 233b8eaa..00000000 --- a/multiversx_sdk_cli/ledger/ledger_functions.py +++ /dev/null @@ -1,55 +0,0 @@ -import logging - -from multiversx_sdk_cli.ledger.ledger_app_handler import LedgerApp - -TX_HASH_SIGN_VERSION = 2 -TX_HASH_SIGN_OPTIONS = 1 - -logger = logging.getLogger("ledger") - - -def do_sign_transaction_with_ledger( - tx_payload: bytes, - account_index: int, - address_index: int, - sign_using_hash: bool -) -> str: - ledger_handler = LedgerApp() - ledger_handler.set_address(account_index=account_index, address_index=address_index) - - logger.info("Ledger: please confirm the transaction on the device") - signature = ledger_handler.sign_transaction(tx_payload, sign_using_hash) - ledger_handler.close() - - return signature - - -def do_sign_message_with_ledger( - message_payload: bytes, - account_index: int, - address_index: int -) -> str: - ledger_handler = LedgerApp() - ledger_handler.set_address(account_index=account_index, address_index=address_index) - - logger.info("Ledger: please confirm the message on the device") - signature = ledger_handler.sign_message(message_payload) - ledger_handler.close() - - return signature - - -def do_get_ledger_address(account_index: int, address_index: int) -> str: - ledger_handler = LedgerApp() - ledger_address = ledger_handler.get_address(account_index=account_index, address_index=address_index) - ledger_handler.close() - - return ledger_address - - -def do_get_ledger_version() -> str: - ledger_handler = LedgerApp() - ledger_version = ledger_handler.get_version() - ledger_handler.close() - - return ledger_version diff --git a/multiversx_sdk_cli/localnet/config_default.py b/multiversx_sdk_cli/localnet/config_default.py index 752362aa..49a1ca42 100644 --- a/multiversx_sdk_cli/localnet/config_default.py +++ b/multiversx_sdk_cli/localnet/config_default.py @@ -2,18 +2,19 @@ from multiversx_sdk_cli.localnet.config_general import General from multiversx_sdk_cli.localnet.config_networking import Networking -from multiversx_sdk_cli.localnet.config_sharding import (Metashard, - RegularShards) -from multiversx_sdk_cli.localnet.config_software import (Software, - SoftwareChainGo, - SoftwareChainProxyGo, - SoftwareResolution) +from multiversx_sdk_cli.localnet.config_sharding import Metashard, RegularShards +from multiversx_sdk_cli.localnet.config_software import ( + Software, + SoftwareChainGo, + SoftwareChainProxyGo, + SoftwareResolution, +) general = General( log_level="*:DEBUG", genesis_delay_seconds=10, rounds_per_epoch=100, - round_duration_milliseconds=6000 + round_duration_milliseconds=6000, ) software = Software( @@ -22,15 +23,21 @@ archive_url="https://github.com/multiversx/mx-chain-go/archive/refs/heads/master.zip", archive_download_folder=Path("~/multiversx-sdk") / "localnet_software_remote" / "downloaded" / "mx-chain-go", archive_extraction_folder=Path("~/multiversx-sdk") / "localnet_software_remote" / "extracted" / "mx-chain-go", - local_path=Path("~/multiversx-sdk") / "localnet_software_local" / "mx-chain-go" + local_path=Path("~/multiversx-sdk") / "localnet_software_local" / "mx-chain-go", ), mx_chain_proxy_go=SoftwareChainProxyGo( resolution=SoftwareResolution.Remote, archive_url="https://github.com/multiversx/mx-chain-proxy-go/archive/refs/heads/master.zip", - archive_download_folder=Path("~/multiversx-sdk") / "localnet_software_remote" / "downloaded" / "mx-chain-proxy-go", - archive_extraction_folder=Path("~/multiversx-sdk") / "localnet_software_remote" / "extracted" / "mx-chain-proxy-go", - local_path=Path("~/multiversx-sdk") / "localnet_software_local" / "mx-chain-proxy-go" - ) + archive_download_folder=Path("~/multiversx-sdk") + / "localnet_software_remote" + / "downloaded" + / "mx-chain-proxy-go", + archive_extraction_folder=Path("~/multiversx-sdk") + / "localnet_software_remote" + / "extracted" + / "mx-chain-proxy-go", + local_path=Path("~/multiversx-sdk") / "localnet_software_local" / "mx-chain-proxy-go", + ), ) metashard = Metashard( @@ -55,5 +62,5 @@ port_first_observer=21100, port_first_observer_rest_api=10100, port_first_validator=21500, - port_first_validator_rest_api=10200 + port_first_validator_rest_api=10200, ) diff --git a/multiversx_sdk_cli/localnet/config_general.py b/multiversx_sdk_cli/localnet/config_general.py index a4579924..03b1c943 100644 --- a/multiversx_sdk_cli/localnet/config_general.py +++ b/multiversx_sdk_cli/localnet/config_general.py @@ -4,11 +4,13 @@ class General(ConfigPart): - def __init__(self, - log_level: str, - genesis_delay_seconds: int, - rounds_per_epoch: int, - round_duration_milliseconds: int): + def __init__( + self, + log_level: str, + genesis_delay_seconds: int, + rounds_per_epoch: int, + round_duration_milliseconds: int, + ): self.log_level: str = log_level self.genesis_delay_seconds: int = genesis_delay_seconds self.rounds_per_epoch: int = rounds_per_epoch diff --git a/multiversx_sdk_cli/localnet/config_networking.py b/multiversx_sdk_cli/localnet/config_networking.py index f13f877a..171b29cb 100644 --- a/multiversx_sdk_cli/localnet/config_networking.py +++ b/multiversx_sdk_cli/localnet/config_networking.py @@ -4,16 +4,18 @@ class Networking(ConfigPart): - def __init__(self, - host: str, - port_seednode: int, - port_seednode_rest_api: int, - p2p_id_seednode: str, - port_proxy: int, - port_first_observer: int, - port_first_observer_rest_api: int, - port_first_validator: int, - port_first_validator_rest_api: int): + def __init__( + self, + host: str, + port_seednode: int, + port_seednode_rest_api: int, + p2p_id_seednode: str, + port_proxy: int, + port_first_observer: int, + port_first_observer_rest_api: int, + port_first_validator: int, + port_first_validator_rest_api: int, + ): self.host: str = host self.port_seednode: int = port_seednode self.port_seednode_rest_api: int = port_seednode_rest_api @@ -36,7 +38,9 @@ def _do_override(self, other: Dict[str, Any]): self.port_first_observer = other.get("port_first_observer", self.port_first_observer) self.port_first_observer_rest_api = other.get("port_first_observer_rest_api", self.port_first_observer_rest_api) self.port_first_validator = other.get("port_first_validator", self.port_first_validator) - self.port_first_validator_rest_api = other.get("port_first_validator_rest_api", self.port_first_validator_rest_api) + self.port_first_validator_rest_api = other.get( + "port_first_validator_rest_api", self.port_first_validator_rest_api + ) def get_proxy_url(self) -> str: return f"http://{self.host}:{self.port_proxy}" diff --git a/multiversx_sdk_cli/localnet/config_part.py b/multiversx_sdk_cli/localnet/config_part.py index cccdd17f..9035cfae 100644 --- a/multiversx_sdk_cli/localnet/config_part.py +++ b/multiversx_sdk_cli/localnet/config_part.py @@ -23,10 +23,12 @@ def _validate_overriding_entries(self, overriding: Dict[str, Any]) -> None: unknown_entries = overriding_entries - allowed_entries if unknown_entries: - logger.error(f"""\ + logger.error( + f"""\ Unknown localnet configuration entries: {unknown_entries}. Please check the configuration of the localnet. -For "{self.get_name()}", the allowed entries are: {allowed_entries}.""") +For "{self.get_name()}", the allowed entries are: {allowed_entries}.""" + ) raise UnknownConfigurationError(f"Unknown localnet configuration entries: {unknown_entries}") def _do_override(self, other: Dict[str, Any]) -> None: diff --git a/multiversx_sdk_cli/localnet/config_root.py b/multiversx_sdk_cli/localnet/config_root.py index 575a44ad..a40f90a1 100644 --- a/multiversx_sdk_cli/localnet/config_root.py +++ b/multiversx_sdk_cli/localnet/config_root.py @@ -7,6 +7,7 @@ from multiversx_sdk_cli.localnet import config_default from multiversx_sdk_cli.localnet.config_part import ConfigPart +from multiversx_sdk_cli.localnet.config_sharding import Metashard, RegularShards from multiversx_sdk_cli.localnet.constants import METACHAIN_ID from multiversx_sdk_cli.localnet.node import Node @@ -14,11 +15,11 @@ class ConfigRoot(ConfigPart): - def __init__(self): + def __init__(self) -> None: self.general = config_default.general self.software = config_default.software - self.metashard = config_default.metashard - self.shards = config_default.shards + self.metashard: Metashard = config_default.metashard + self.shards: RegularShards = config_default.shards self.networking = config_default.networking def get_name(self) -> str: @@ -146,18 +147,22 @@ def api_addresses_sharded_for_proxy_config(self) -> List[Dict[str, Any]]: nodes: List[Dict[str, Any]] = [] for node in self.observers(): - nodes.append({ - "ShardId": int(node.shard), - "Address": node.api_address(), - "Type": "Observer" - }) + nodes.append( + { + "ShardId": int(node.shard), + "Address": node.api_address(), + "Type": "Observer", + } + ) for node in self.validators(): - nodes.append({ - "ShardId": int(node.shard), - "Address": node.api_address(), - "Type": "Validator" - }) + nodes.append( + { + "ShardId": int(node.shard), + "Address": node.api_address(), + "Type": "Validator", + } + ) return nodes diff --git a/multiversx_sdk_cli/localnet/config_sharding.py b/multiversx_sdk_cli/localnet/config_sharding.py index d509a1db..a2e802ba 100644 --- a/multiversx_sdk_cli/localnet/config_sharding.py +++ b/multiversx_sdk_cli/localnet/config_sharding.py @@ -4,10 +4,7 @@ class Metashard(ConfigPart): - def __init__(self, - consensus_size: int, - num_observers: int, - num_validators: int): + def __init__(self, consensus_size: int, num_observers: int, num_validators: int): self.consensus_size: int = consensus_size self.num_observers: int = num_observers self.num_validators: int = num_validators @@ -22,11 +19,13 @@ def _do_override(self, other: Dict[str, Any]): class RegularShards(ConfigPart): - def __init__(self, - num_shards: int, - consensus_size: int, - num_observers_per_shard: int, - num_validators_per_shard: int): + def __init__( + self, + num_shards: int, + consensus_size: int, + num_observers_per_shard: int, + num_validators_per_shard: int, + ): self.num_shards: int = num_shards self.consensus_size: int = consensus_size self.num_observers_per_shard: int = num_observers_per_shard diff --git a/multiversx_sdk_cli/localnet/config_software.py b/multiversx_sdk_cli/localnet/config_software.py index ca8cdf2b..060069d5 100644 --- a/multiversx_sdk_cli/localnet/config_software.py +++ b/multiversx_sdk_cli/localnet/config_software.py @@ -12,10 +12,7 @@ class SoftwareResolution(Enum): class Software(ConfigPart): - def __init__( - self, - mx_chain_go: 'SoftwareChainGo', - mx_chain_proxy_go: 'SoftwareChainProxyGo'): + def __init__(self, mx_chain_go: "SoftwareChainGo", mx_chain_proxy_go: "SoftwareChainProxyGo"): self.mx_chain_go = mx_chain_go self.mx_chain_proxy_go = mx_chain_proxy_go @@ -34,12 +31,14 @@ def to_dictionary(self) -> Dict[str, Any]: class SoftwareComponent(ConfigPart): - def __init__(self, - resolution: SoftwareResolution, - archive_url: str, - archive_download_folder: Path, - archive_extraction_folder: Path, - local_path: Path): + def __init__( + self, + resolution: SoftwareResolution, + archive_url: str, + archive_download_folder: Path, + archive_extraction_folder: Path, + local_path: Path, + ): self.resolution: SoftwareResolution = resolution self.archive_url: str = archive_url self.archive_download_folder: Path = archive_download_folder @@ -58,15 +57,19 @@ def _do_override(self, other: Dict[str, Any]) -> None: def _verify(self): if self.resolution == SoftwareResolution.Remote: if not self.archive_url: - raise KnownError(f"In configuration section '{self.get_name()}', resolution is '{self.resolution.value}', but 'archive_url' is bad (empty)") + raise KnownError( + f"In configuration section '{self.get_name()}', resolution is '{self.resolution.value}', but 'archive_url' is bad (empty)" + ) if self.resolution == SoftwareResolution.Local: if not self.get_local_path().is_dir(): - raise KnownError(f"In configuration section '{self.get_name()}', resolution is '{self.resolution.value}', but 'local_path' is not a directory: {self.local_path}") + raise KnownError( + f"In configuration section '{self.get_name()}', resolution is '{self.resolution.value}', but 'local_path' is not a directory: {self.local_path}" + ) - def get_archive_download_folder(self): + def get_archive_download_folder(self) -> Path: return self.archive_download_folder.expanduser().resolve() - def get_archive_extraction_folder(self): + def get_archive_extraction_folder(self) -> Path: return self.archive_extraction_folder.expanduser().resolve() def get_local_path(self): diff --git a/multiversx_sdk_cli/localnet/constants.py b/multiversx_sdk_cli/localnet/constants.py index 26ef194c..5e5bec4f 100644 --- a/multiversx_sdk_cli/localnet/constants.py +++ b/multiversx_sdk_cli/localnet/constants.py @@ -3,4 +3,6 @@ METACHAIN_ID = 4294967295 NETWORK_MONITORING_INTERVAL_IN_SECONDS = 1 # Read, write and execute by owner, read and execute by group and others -FILE_MODE_EXECUTABLE = stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR | stat.S_IRGRP | stat.S_IXGRP | stat.S_IROTH | stat.S_IXOTH +FILE_MODE_EXECUTABLE = ( + stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR | stat.S_IRGRP | stat.S_IXGRP | stat.S_IROTH | stat.S_IXOTH +) diff --git a/multiversx_sdk_cli/localnet/genesis_json.py b/multiversx_sdk_cli/localnet/genesis_json.py index bfd61d63..f7fd3d61 100644 --- a/multiversx_sdk_cli/localnet/genesis_json.py +++ b/multiversx_sdk_cli/localnet/genesis_json.py @@ -1,6 +1,7 @@ from typing import Any, Dict, List -from multiversx_sdk_cli.accounts import Account +from multiversx_sdk import Account + from multiversx_sdk_cli.localnet import wallets from multiversx_sdk_cli.localnet.config_root import ConfigRoot from multiversx_sdk_cli.localnet.genesis import is_last_user @@ -38,10 +39,7 @@ def _build_validator_entry(nickname: str, account: Account, value: int) -> Dict[ "supply": str(value), "balance": "0", "stakingvalue": str(value), - "delegation": { - "address": "", - "value": "0" - } + "delegation": {"address": "", "value": "0"}, } @@ -52,8 +50,5 @@ def _build_user_entry(nickname: str, account: Account, value: int) -> Dict[str, "supply": str(value), "balance": str(value), "stakingvalue": "0", - "delegation": { - "address": "", - "value": "0" - } + "delegation": {"address": "", "value": "0"}, } diff --git a/multiversx_sdk_cli/localnet/node_config_toml.py b/multiversx_sdk_cli/localnet/node_config_toml.py index 847f876e..bfd59d4a 100644 --- a/multiversx_sdk_cli/localnet/node_config_toml.py +++ b/multiversx_sdk_cli/localnet/node_config_toml.py @@ -7,31 +7,31 @@ def patch_config(data: ConfigDict, config: ConfigRoot): - data['GeneralSettings']['ChainID'] = CHAIN_ID + data["GeneralSettings"]["ChainID"] = CHAIN_ID # "--operation-mode=historical-balances" is not available for nodes, # since << validator cannot be a full archive node >>, # but we attempt to set the "deep-history" mode as follows: - data['DbLookupExtensions']['Enabled'] = True - data['GeneralSettings']['StartInEpochEnabled'] = False - data['StateTriesConfig']['AccountsStatePruningEnabled'] = False - data['StoragePruning']['ObserverCleanOldEpochsData'] = False - data['StoragePruning']['AccountsTrieCleanOldEpochsData'] = False + data["DbLookupExtensions"]["Enabled"] = True + data["GeneralSettings"]["StartInEpochEnabled"] = False + data["StateTriesConfig"]["AccountsStatePruningEnabled"] = False + data["StoragePruning"]["ObserverCleanOldEpochsData"] = False + data["StoragePruning"]["AccountsTrieCleanOldEpochsData"] = False # Make epochs shorter epoch_start_config: ConfigDict = dict() - epoch_start_config['RoundsPerEpoch'] = config.general.rounds_per_epoch - epoch_start_config['MinRoundsBetweenEpochs'] = int(config.general.rounds_per_epoch / 4) + epoch_start_config["RoundsPerEpoch"] = config.general.rounds_per_epoch + epoch_start_config["MinRoundsBetweenEpochs"] = int(config.general.rounds_per_epoch / 4) - data['EpochStartConfig'].update(epoch_start_config) - data['WebServerAntiflood']['VmQueryDelayAfterStartInSec'] = 30 + data["EpochStartConfig"].update(epoch_start_config) + data["WebServerAntiflood"]["VmQueryDelayAfterStartInSec"] = 30 # Always use the latest VM - data['VirtualMachine']['Execution']['WasmVMVersions'] = [{'StartEpoch': 0, 'Version': '*'}] - data['VirtualMachine']['Querying']['WasmVMVersions'] = [{'StartEpoch': 0, 'Version': '*'}] + data["VirtualMachine"]["Execution"]["WasmVMVersions"] = [{"StartEpoch": 0, "Version": "*"}] + data["VirtualMachine"]["Querying"]["WasmVMVersions"] = [{"StartEpoch": 0, "Version": "*"}] # Adjust "ChainParametersByEpoch" (for Andromeda) - chain_parameters_by_epoch = data['GeneralSettings'].get('ChainParametersByEpoch', []) + chain_parameters_by_epoch = data["GeneralSettings"].get("ChainParametersByEpoch", []) if chain_parameters_by_epoch: # For convenience, keep a single entry ... @@ -48,34 +48,36 @@ def patch_config(data: ConfigDict, config: ConfigRoot): def patch_api(data: ConfigDict, config: ConfigRoot): - routes = data['APIPackages']['transaction']['Routes'] + routes = data["APIPackages"]["transaction"]["Routes"] for route in routes: - route['Open'] = True + route["Open"] = True def patch_enable_epochs(data: ConfigDict, config: ConfigRoot): enable_epochs = data["EnableEpochs"] - enable_epochs['SCDeployEnableEpoch'] = 0 - enable_epochs['BuiltInFunctionsEnableEpoch'] = 0 - enable_epochs['RelayedTransactionsEnableEpoch'] = 0 - enable_epochs['PenalizedTooMuchGasEnableEpoch'] = 0 - enable_epochs['AheadOfTimeGasUsageEnableEpoch'] = 0 - enable_epochs['GasPriceModifierEnableEpoch'] = 0 - enable_epochs['RepairCallbackEnableEpoch'] = 0 - enable_epochs['ReturnDataToLastTransferEnableEpoch'] = 0 - enable_epochs['SenderInOutTransferEnableEpoch'] = 0 - enable_epochs['ESDTEnableEpoch'] = 0 - enable_epochs['IncrementSCRNonceInMultiTransferEnableEpoch'] = 0 - enable_epochs['ESDTMultiTransferEnableEpoch'] = 0 - enable_epochs['GlobalMintBurnDisableEpoch'] = 0 - enable_epochs['ESDTTransferRoleEnableEpoch'] = 0 - enable_epochs['BuiltInFunctionOnMetaEnableEpoch'] = 0 - enable_epochs['MultiESDTTransferFixOnCallBackOnEnableEpoch'] = 0 - enable_epochs['ESDTNFTCreateOnMultiShard'] = 0 - enable_epochs['MetaESDTSetEnableEpoch'] = 0 - enable_epochs['DelegationManagerEnableEpoch'] = 0 - - max_nodes_change_enable_epoch = enable_epochs['MaxNodesChangeEnableEpoch'] + enable_epochs["SCDeployEnableEpoch"] = 0 + enable_epochs["BuiltInFunctionsEnableEpoch"] = 0 + enable_epochs["RelayedTransactionsEnableEpoch"] = 0 + enable_epochs["PenalizedTooMuchGasEnableEpoch"] = 0 + enable_epochs["AheadOfTimeGasUsageEnableEpoch"] = 0 + enable_epochs["GasPriceModifierEnableEpoch"] = 0 + enable_epochs["RepairCallbackEnableEpoch"] = 0 + enable_epochs["ReturnDataToLastTransferEnableEpoch"] = 0 + enable_epochs["SenderInOutTransferEnableEpoch"] = 0 + enable_epochs["ESDTEnableEpoch"] = 0 + enable_epochs["IncrementSCRNonceInMultiTransferEnableEpoch"] = 0 + enable_epochs["ESDTMultiTransferEnableEpoch"] = 0 + enable_epochs["GlobalMintBurnDisableEpoch"] = 0 + enable_epochs["ESDTTransferRoleEnableEpoch"] = 0 + enable_epochs["BuiltInFunctionOnMetaEnableEpoch"] = 0 + enable_epochs["MultiESDTTransferFixOnCallBackOnEnableEpoch"] = 0 + enable_epochs["ESDTNFTCreateOnMultiShard"] = 0 + enable_epochs["MetaESDTSetEnableEpoch"] = 0 + enable_epochs["DelegationManagerEnableEpoch"] = 0 + + max_nodes_change_enable_epoch = enable_epochs["MaxNodesChangeEnableEpoch"] last_entry = max_nodes_change_enable_epoch[-1] penultimate_entry = max_nodes_change_enable_epoch[-2] - last_entry['MaxNumNodes'] = penultimate_entry['MaxNumNodes'] - (config.shards.num_shards + 1) * penultimate_entry['NodesToShufflePerShard'] + last_entry["MaxNumNodes"] = ( + penultimate_entry["MaxNumNodes"] - (config.shards.num_shards + 1) * penultimate_entry["NodesToShufflePerShard"] + ) diff --git a/multiversx_sdk_cli/localnet/nodes_setup_json.py b/multiversx_sdk_cli/localnet/nodes_setup_json.py index f1570fb6..b99a4c2a 100644 --- a/multiversx_sdk_cli/localnet/nodes_setup_json.py +++ b/multiversx_sdk_cli/localnet/nodes_setup_json.py @@ -22,7 +22,7 @@ def build(config: ConfigRoot) -> Any: # Then, patch the list of initial nodes, so that higher indexes will become metachain nodes. num_metachain_nodes = config.metashard.num_validators num_nodes = len(initial_nodes) - initial_nodes = initial_nodes[num_nodes - num_metachain_nodes:] + initial_nodes[:num_nodes - num_metachain_nodes] + initial_nodes = initial_nodes[num_nodes - num_metachain_nodes :] + initial_nodes[: num_nodes - num_metachain_nodes] return { "startTime": config.genesis_time(), @@ -35,5 +35,5 @@ def build(config: ConfigRoot) -> Any: "adaptivity": False, "chainID": CHAIN_ID, "minTransactionVersion": 1, - "initialNodes": initial_nodes + "initialNodes": initial_nodes, } diff --git a/multiversx_sdk_cli/localnet/p2p_toml.py b/multiversx_sdk_cli/localnet/p2p_toml.py index b4e5a4f4..830d4ca6 100644 --- a/multiversx_sdk_cli/localnet/p2p_toml.py +++ b/multiversx_sdk_cli/localnet/p2p_toml.py @@ -2,23 +2,21 @@ from multiversx_sdk_cli.localnet.config_root import ConfigRoot -PROTOCOL_ID = '/erd/kad/sandbox' +PROTOCOL_ID = "/erd/kad/sandbox" def patch(data: Any, config: ConfigRoot, node_index: int, port_first: int) -> Any: - data['Node']['Port'] = str(port_first + node_index) - data['Node']['ThresholdMinConnectedPeers'] = 1 - data['KadDhtPeerDiscovery']['InitialPeerList'] = [ - config.seednode_address() - ] - data['KadDhtPeerDiscovery']['ProtocolID'] = PROTOCOL_ID - data['Sharding']['Type'] = "NilListSharder" + data["Node"]["Port"] = str(port_first + node_index) + data["Node"]["ThresholdMinConnectedPeers"] = 1 + data["KadDhtPeerDiscovery"]["InitialPeerList"] = [config.seednode_address()] + data["KadDhtPeerDiscovery"]["ProtocolID"] = PROTOCOL_ID + data["Sharding"]["Type"] = "NilListSharder" def patch_for_seednode(data: Any, config: ConfigRoot): port_seednode = config.networking.port_seednode - data['Node']['Port'] = str(port_seednode) - data['Node']['MaximumExpectedPeerCount'] = 16 - data['KadDhtPeerDiscovery']['ProtocolID'] = PROTOCOL_ID - data['Sharding']['Type'] = "NilListSharder" + data["Node"]["Port"] = str(port_seednode) + data["Node"]["MaximumExpectedPeerCount"] = 16 + data["KadDhtPeerDiscovery"]["ProtocolID"] = PROTOCOL_ID + data["Sharding"]["Type"] = "NilListSharder" diff --git a/multiversx_sdk_cli/localnet/step_build_software.py b/multiversx_sdk_cli/localnet/step_build_software.py index 533b0066..7f9b38ca 100644 --- a/multiversx_sdk_cli/localnet/step_build_software.py +++ b/multiversx_sdk_cli/localnet/step_build_software.py @@ -79,12 +79,7 @@ def _set_rpath(cmd_path: Path): return try: - subprocess.check_call([ - "install_name_tool", - "-add_rpath", - "@loader_path", - cmd_path - ]) + subprocess.check_call(["install_name_tool", "-add_rpath", "@loader_path", cmd_path]) except Exception as e: # In most cases, this isn't critical (libraries might be found among the downloaded Go packages). logger.warning(f"Failed to set rpath of {cmd_path}: {e}") diff --git a/multiversx_sdk_cli/localnet/step_config.py b/multiversx_sdk_cli/localnet/step_config.py index 48053a63..d864d5cb 100644 --- a/multiversx_sdk_cli/localnet/step_config.py +++ b/multiversx_sdk_cli/localnet/step_config.py @@ -6,10 +6,15 @@ import multiversx_sdk_cli.utils as utils from multiversx_sdk_cli.errors import KnownError -from multiversx_sdk_cli.localnet import (genesis_json, - genesis_smart_contracts_json, - libraries, node_config_toml, - nodes_setup_json, p2p_toml, wallets) +from multiversx_sdk_cli.localnet import ( + genesis_json, + genesis_smart_contracts_json, + libraries, + node_config_toml, + nodes_setup_json, + p2p_toml, + wallets, +) from multiversx_sdk_cli.localnet.config_root import ConfigRoot logger = logging.getLogger("localnet") @@ -52,14 +57,8 @@ def configure(configfile: Path): config, config.observer_config_folders(), ) - overwrite_genesis_file( - config, - config.validator_config_folders() - ) - overwrite_genesis_file( - config, - config.observer_config_folders() - ) + overwrite_genesis_file(config, config.validator_config_folders()) + overwrite_genesis_file(config, config.observer_config_folders()) # Seed node copy_config_to_seednode(config) @@ -78,7 +77,7 @@ def create_folders(config: ConfigRoot): folder = config.proxy_folder() makefolder(folder) - makefolder(folder / 'config') + makefolder(folder / "config") for folder in config.all_nodes_folders(): makefolder(folder) @@ -101,22 +100,22 @@ def copy_validator_keys(config: ConfigRoot): def patch_node_config(config: ConfigRoot): for node_config in config.all_nodes_config_folders(): - node_config_file = node_config / 'config.toml' + node_config_file = node_config / "config.toml" data = utils.read_toml_file(node_config_file) node_config_toml.patch_config(data, config) utils.write_toml_file(node_config_file, data) - api_config_file = node_config / 'api.toml' + api_config_file = node_config / "api.toml" data = utils.read_toml_file(api_config_file) node_config_toml.patch_api(data, config) utils.write_toml_file(api_config_file, data) - enable_epochs_config_file = node_config / 'enableEpochs.toml' + enable_epochs_config_file = node_config / "enableEpochs.toml" data = utils.read_toml_file(enable_epochs_config_file) node_config_toml.patch_enable_epochs(data, config) utils.write_toml_file(enable_epochs_config_file, data) - genesis_smart_contracts_file = node_config / 'genesisSmartContracts.json' + genesis_smart_contracts_file = node_config / "genesisSmartContracts.json" data = utils.read_json_file(genesis_smart_contracts_file) genesis_smart_contracts_json.patch(data, config) utils.write_json_file(genesis_smart_contracts_file, data) @@ -126,13 +125,13 @@ def copy_config_to_seednode(config: ConfigRoot): config_source = config.software.mx_chain_go.get_seednode_config_folder() seednode_config = config.seednode_config_folder() makefolder(seednode_config) - shutil.copy(config_source / 'p2p.toml', seednode_config / 'p2p.toml') - shutil.copy(config_source / 'config.toml', seednode_config / 'config.toml') + shutil.copy(config_source / "p2p.toml", seednode_config / "p2p.toml") + shutil.copy(config_source / "config.toml", seednode_config / "config.toml") def patch_seednode_p2p_config(config: ConfigRoot): seednode_config = config.seednode_config_folder() - seednode_config_file = seednode_config / 'p2p.toml' + seednode_config_file = seednode_config / "p2p.toml" data = utils.read_toml_file(seednode_config_file) p2p_toml.patch_for_seednode(data, config) @@ -146,7 +145,7 @@ def copy_seednode_p2p_key(config: ConfigRoot): def patch_nodes_p2p_config(config: ConfigRoot, nodes_config_folders: List[Path], port_first: int): for index, config_folder in enumerate(nodes_config_folders): - config_file = config_folder / 'p2p.toml' + config_file = config_folder / "p2p.toml" data = utils.read_toml_file(config_file) p2p_toml.patch(data, config, index, port_first) utils.write_toml_file(config_file, data) @@ -156,14 +155,14 @@ def overwrite_nodes_setup(config: ConfigRoot, nodes_config_folders: List[Path]): nodes_setup = nodes_setup_json.build(config) for _, config_folder in enumerate(nodes_config_folders): - utils.write_json_file(config_folder / 'nodesSetup.json', nodes_setup) + utils.write_json_file(config_folder / "nodesSetup.json", nodes_setup) def overwrite_genesis_file(config: ConfigRoot, nodes_config_folders: List[Path]): genesis = genesis_json.build(config) for _, config_folder in enumerate(nodes_config_folders): - utils.write_json_file(config_folder / 'genesis.json', genesis) + utils.write_json_file(config_folder / "genesis.json", genesis) def copy_config_to_proxy(config: ConfigRoot): @@ -171,27 +170,27 @@ def copy_config_to_proxy(config: ConfigRoot): proxy_config = config.proxy_config_folder() makefolder(proxy_config) - shutil.copy(config_prototype / 'config.toml', proxy_config) - shutil.copytree(config_prototype / 'apiConfig', proxy_config / 'apiConfig') + shutil.copy(config_prototype / "config.toml", proxy_config) + shutil.copytree(config_prototype / "apiConfig", proxy_config / "apiConfig") # Removed in newer versions: # https://github.com/multiversx/mx-chain-proxy-go/pull/454 - if (config_prototype / 'external.toml').exists(): - shutil.copy(config_prototype / 'external.toml', proxy_config) + if (config_prototype / "external.toml").exists(): + shutil.copy(config_prototype / "external.toml", proxy_config) def patch_proxy_config(config: ConfigRoot): - proxy_config_file = config.proxy_config_folder() / 'config.toml' + proxy_config_file = config.proxy_config_folder() / "config.toml" nodes = config.api_addresses_sharded_for_proxy_config() data = utils.read_toml_file(proxy_config_file) - data['Observers'] = nodes - data['FullHistoryNodes'] = nodes - data['GeneralSettings']['ServerPort'] = config.networking.port_proxy + data["Observers"] = nodes + data["FullHistoryNodes"] = nodes + data["GeneralSettings"]["ServerPort"] = config.networking.port_proxy utils.write_toml_file(proxy_config_file, data) - api_config_file = path.join(config.proxy_config_folder(), 'apiConfig', 'v1_0.toml') + api_config_file = path.join(config.proxy_config_folder(), "apiConfig", "v1_0.toml") data = utils.read_toml_file(api_config_file) - routes = data['APIPackages']['transaction']['Routes'] + routes = data["APIPackages"]["transaction"]["Routes"] for route in routes: route["Open"] = True utils.write_toml_file(api_config_file, data) diff --git a/multiversx_sdk_cli/localnet/step_prerequisites.py b/multiversx_sdk_cli/localnet/step_prerequisites.py index bff27822..2ce69773 100644 --- a/multiversx_sdk_cli/localnet/step_prerequisites.py +++ b/multiversx_sdk_cli/localnet/step_prerequisites.py @@ -5,8 +5,10 @@ from multiversx_sdk_cli import dependencies from multiversx_sdk_cli.localnet.config_root import ConfigRoot -from multiversx_sdk_cli.localnet.config_software import (SoftwareComponent, - SoftwareResolution) +from multiversx_sdk_cli.localnet.config_software import ( + SoftwareComponent, + SoftwareResolution, +) logger = logging.getLogger("localnet") diff --git a/multiversx_sdk_cli/localnet/step_start.py b/multiversx_sdk_cli/localnet/step_start.py index 7c6ee04c..dc926ae6 100644 --- a/multiversx_sdk_cli/localnet/step_start.py +++ b/multiversx_sdk_cli/localnet/step_start.py @@ -11,10 +11,10 @@ from multiversx_sdk_cli import workstation from multiversx_sdk_cli.localnet.config_root import ConfigRoot -from multiversx_sdk_cli.localnet.constants import \ - NETWORK_MONITORING_INTERVAL_IN_SECONDS -from multiversx_sdk_cli.localnet.step_config import \ - copy_binaries_into_localnet_workspace +from multiversx_sdk_cli.localnet.constants import NETWORK_MONITORING_INTERVAL_IN_SECONDS +from multiversx_sdk_cli.localnet.step_config import ( + copy_binaries_into_localnet_workspace, +) logger = logging.getLogger("localnet") @@ -53,53 +53,72 @@ async def do_start(configfile: Path, stop_after_seconds: int): display_api_table(config) - logger.info('Localnet folder is %s', config.root()) + logger.info("Localnet folder is %s", config.root()) to_run: List[Coroutine[Any, Any, None]] = [] # Seed node - to_run.append(run([ - "./seednode", - "--log-save", - f"--rest-api-interface={config.seednode_api_interface()}", - ], cwd=config.seednode_folder())) + to_run.append( + run( + [ + "./seednode", + "--log-save", + f"--rest-api-interface={config.seednode_api_interface()}", + ], + cwd=config.seednode_folder(), + ) + ) loglevel = _patch_loglevel(config.general.log_level) logger.info(f"loglevel: {loglevel}") # Observers for observer in config.observers(): - to_run.append(run([ - "./node", - f"--display-name=observer-{observer.shard}-{observer.index}", - "--use-log-view", - "--log-save", - f"--log-level={loglevel}", - "--log-logger-name", - "--log-correlation", - f"--destination-shard-as-observer={observer.shard}", - f"--rest-api-interface={observer.api_interface()}", - "--operation-mode=historical-balances" - ], cwd=observer.folder, delay=NODES_START_DELAY)) + to_run.append( + run( + [ + "./node", + f"--display-name=observer-{observer.shard}-{observer.index}", + "--use-log-view", + "--log-save", + f"--log-level={loglevel}", + "--log-logger-name", + f"--destination-shard-as-observer={observer.shard}", + f"--rest-api-interface={observer.api_interface()}", + "--operation-mode=historical-balances", + ], + cwd=observer.folder, + delay=NODES_START_DELAY, + ) + ) # Validators for validator in config.validators(): - to_run.append(run([ - "./node", - f"--display-name=validator-{validator.index}", - "--use-log-view", - "--log-save", - f"--log-level={loglevel}", - "--log-logger-name", - "--log-correlation", - f"--rest-api-interface={validator.api_interface()}" - ], cwd=validator.folder, delay=NODES_START_DELAY)) + to_run.append( + run( + [ + "./node", + f"--display-name=validator-{validator.index}", + "--use-log-view", + "--log-save", + f"--log-level={loglevel}", + "--log-logger-name", + "--log-correlation", + f"--rest-api-interface={validator.api_interface()}", + ], + cwd=validator.folder, + delay=NODES_START_DELAY, + ) + ) # Proxy - to_run.append(run([ - "./proxy", - "--log-save" - ], cwd=config.proxy_folder(), delay=PROXY_START_DELAY)) + to_run.append( + run( + ["./proxy", "--log-save"], + cwd=config.proxy_folder(), + delay=PROXY_START_DELAY, + ) + ) # Monitor network to_run.append(monitor_network(stop_after_seconds)) @@ -156,16 +175,24 @@ async def run(args: List[str], cwd: Path, delay: int = 0): # For MacOS, dylibs are directly found near the binary (no workaround needed) pass - process = await asyncio.create_subprocess_exec(*args, stdout=asyncio.subprocess.PIPE, - stderr=asyncio.subprocess.PIPE, cwd=cwd, limit=1024 * 512, env=env) + process = await asyncio.create_subprocess_exec( + *args, + stdout=asyncio.subprocess.PIPE, + stderr=asyncio.subprocess.PIPE, + cwd=cwd, + limit=1024 * 512, + env=env, + ) pid = process.pid print(f"Started process [{pid}]", args) - await asyncio.wait([ - asyncio.create_task(_read_stream(process.stdout, pid)), - asyncio.create_task(_read_stream(process.stderr, pid)) - ]) + await asyncio.wait( + [ + asyncio.create_task(_read_stream(process.stdout, pid)), + asyncio.create_task(_read_stream(process.stderr, pid)), + ] + ) return_code = await process.wait() print(f"Proces [{pid}] stopped. Return code: {return_code}.") @@ -197,7 +224,13 @@ def _patch_loglevel(loglevel: str) -> str: LOGLINE_GENESIS_THRESHOLD_MARKER = "started committing block" -LOGLINE_AFTER_GENESIS_INTERESTING_MARKERS = ["started committing block", "ERROR", "WARN", "vm", "smartcontract"] +LOGLINE_AFTER_GENESIS_INTERESTING_MARKERS = [ + "started committing block", + "ERROR", + "WARN", + "vm", + "smartcontract", +] # We ignore SC calls on genesis. LOGLINE_ON_GENESIS_INTERESTING_MARKERS = ["started committing block", "ERROR", "WARN"] diff --git a/multiversx_sdk_cli/localnet/wallets.py b/multiversx_sdk_cli/localnet/wallets.py index f4e3e5e2..a68c2a44 100644 --- a/multiversx_sdk_cli/localnet/wallets.py +++ b/multiversx_sdk_cli/localnet/wallets.py @@ -3,10 +3,9 @@ from pathlib import Path from typing import Dict, Tuple -from multiversx_sdk import ValidatorPEM +from multiversx_sdk import Account, ValidatorPEM from multiversx_sdk_cli import errors, utils -from multiversx_sdk_cli.accounts import Account from multiversx_sdk_cli.workstation import get_tools_folder MAX_NUM_NODES = 12 @@ -36,7 +35,7 @@ def get_validator_wallets(num_validators: int) -> Dict[str, Account]: for i in range(0, num_validators): pem_file = get_validator_wallet_file(i) nickname = "validator{:02}".format(i) - account = Account(pem_file=str(pem_file)) + account = Account.new_from_pem(file_path=pem_file) result[nickname] = account return result @@ -52,7 +51,7 @@ def get_validators(num_validators: int) -> Dict[str, Tuple[str, Account]]: pem_file = get_validator_wallet_file(i) nickname = "validator{:02}".format(i) - account = Account(pem_file=str(pem_file)) + account = Account.new_from_pem(file_path=pem_file) result[nickname] = (pubkey, account) return result @@ -81,7 +80,7 @@ def get_users() -> Dict[str, Account]: for pem_file in sorted(utils.list_files(_get_users_folder(), ".pem")): nickname = Path(pem_file).stem - account = Account(pem_file=str(pem_file)) + account = Account.new_from_pem(file_path=pem_file) result[nickname] = account return result diff --git a/multiversx_sdk_cli/myprocess.py b/multiversx_sdk_cli/myprocess.py index d2bcb587..810db319 100644 --- a/multiversx_sdk_cli/myprocess.py +++ b/multiversx_sdk_cli/myprocess.py @@ -1,18 +1,30 @@ import logging import subprocess from pathlib import Path -from typing import Any, List, Optional, Union +from typing import Any, Optional, Union from multiversx_sdk_cli import errors logger = logging.getLogger("myprocess") -def run_process(args: List[str], env: Any = None, dump_to_stdout: bool = True, cwd: Optional[Union[str, Path]] = None) -> str: +def run_process( + args: list[str], + env: Any = None, + dump_to_stdout: bool = True, + cwd: Optional[Union[str, Path]] = None, +) -> str: logger.info(f"run_process: {args}, in folder: {cwd}") try: - output = subprocess.check_output(args, shell=False, universal_newlines=True, stderr=subprocess.STDOUT, env=env, cwd=cwd) + output = subprocess.check_output( + args, + shell=False, + universal_newlines=True, + stderr=subprocess.STDOUT, + env=env, + cwd=cwd, + ) logger.info("Successful run. Output:") if dump_to_stdout: print(output or "[No output]") diff --git a/multiversx_sdk_cli/native_auth_client.py b/multiversx_sdk_cli/native_auth_client.py deleted file mode 100644 index f7174b8e..00000000 --- a/multiversx_sdk_cli/native_auth_client.py +++ /dev/null @@ -1,104 +0,0 @@ -import base64 -import json -from typing import Any, Dict, Optional - -import requests - -from multiversx_sdk_cli.errors import NativeAuthClientError - -DEFAULT_EXPIRY_TIME_IN_SECONDS = 60 * 60 * 2 -DEFAULT_API_URL = "https://api.multiversx.com" - - -class NativeAuthClientConfig: - def __init__( - self, - origin: str = '', - api_url: str = DEFAULT_API_URL, - expiry_seconds: int = DEFAULT_EXPIRY_TIME_IN_SECONDS, - block_hash_shard: Optional[int] = None, - gateway_url: Optional[str] = None, - extra_request_headers: Optional[Dict[str, str]] = None - ) -> None: - self.origin = origin - self.api_url = api_url - self.expiry_seconds = expiry_seconds - self.block_hash_shard = block_hash_shard - self.gateway_url = gateway_url - self.extra_request_headers = extra_request_headers - - -class NativeAuthClient: - def __init__(self, config: Optional[NativeAuthClientConfig] = None) -> None: - self.config = config or NativeAuthClientConfig() - - def initialize(self, extra_info: Dict[Any, Any] = {}) -> str: - block_hash = self.get_current_block_hash() - encoded_extra_info = self._encode_value(json.dumps(extra_info)) - encoded_origin = self._encode_value(self.config.origin) - - return f"{encoded_origin}.{block_hash}.{self.config.expiry_seconds}.{encoded_extra_info}" - - def get_token(self, address: str, token: str, signature: str) -> str: - encoded_address = self._encode_value(address) - encoded_token = self._encode_value(token) - - return f"{encoded_address}.{encoded_token}.{signature}" - - def get_current_block_hash(self) -> str: - if self.config.gateway_url: - return self._get_current_block_hash_using_gateway() - return self._get_current_block_hash_using_api() - - def _get_current_block_hash_using_gateway(self) -> str: - round = self._get_current_round() - url = f"{self.config.gateway_url}/blocks/by-round/{round}" - response = self._execute_request(url) - blocks = response["data"]["blocks"] - block = [b for b in blocks if b["shard"] == self.config.block_hash_shard][0] - return block["hash"] - - def _get_current_round(self) -> int: - if self.config.gateway_url is None: - raise NativeAuthClientError("Gateway URL not set") - - if self.config.block_hash_shard is None: - raise NativeAuthClientError("Blockhash shard not set") - - url = f"{self.config.gateway_url}/network/status/{self.config.block_hash_shard}" - response = self._execute_request(url) - status = response["data"]["status"] - - return status["erd_current_round"] - - def _get_current_block_hash_using_api(self) -> str: - try: - url = f"{self.config.api_url}/blocks/latest?ttl={self.config.expiry_seconds}&fields=hash" - response = self._execute_request(url) - if response["hash"]: - return response["hash"] - except Exception: - pass - - return self._get_current_block_hash_using_api_fallback() - - def _get_current_block_hash_using_api_fallback(self) -> str: - url = f"{self.config.api_url}/blocks?size=1&fields=hash" - - if self.config.block_hash_shard: - url += f"&shard={self.config.block_hash_shard}" - - response = self._execute_request(url) - return response[0]["hash"] - - def _encode_value(self, string: str) -> str: - encoded = base64.b64encode(string.encode('utf-8')).decode('utf-8') - return self._escape(encoded) - - def _escape(self, string: str) -> str: - return string.replace("+", "-").replace("/", "_").replace("=", "") - - def _execute_request(self, url: str) -> Any: - response = requests.get(url=url, headers=self.config.extra_request_headers) - response.raise_for_status() - return response.json() diff --git a/multiversx_sdk_cli/projects/__init__.py b/multiversx_sdk_cli/projects/__init__.py deleted file mode 100644 index db23619f..00000000 --- a/multiversx_sdk_cli/projects/__init__.py +++ /dev/null @@ -1,8 +0,0 @@ -from multiversx_sdk_cli.projects.core import (build_project, clean_project, - load_project, run_tests) -from multiversx_sdk_cli.projects.project_base import Project -from multiversx_sdk_cli.projects.project_rust import ProjectRust -from multiversx_sdk_cli.projects.report.do_report import do_report -from multiversx_sdk_cli.projects.templates import Contract - -__all__ = ["build_project", "clean_project", "do_report", "run_tests", "load_project", "Project", "ProjectRust", "Contract"] diff --git a/multiversx_sdk_cli/projects/constants.py b/multiversx_sdk_cli/projects/constants.py deleted file mode 100644 index 0ee36a27..00000000 --- a/multiversx_sdk_cli/projects/constants.py +++ /dev/null @@ -1,2 +0,0 @@ -PROJECT_CONFIG_FILENAME = "multiversx.json" -OLD_PROJECT_CONFIG_FILENAME = "elrond.json" diff --git a/multiversx_sdk_cli/projects/core.py b/multiversx_sdk_cli/projects/core.py deleted file mode 100644 index 1a663731..00000000 --- a/multiversx_sdk_cli/projects/core.py +++ /dev/null @@ -1,61 +0,0 @@ -import logging -from pathlib import Path -from typing import Any, List - -from multiversx_sdk_cli import errors, guards -from multiversx_sdk_cli.projects import shared -from multiversx_sdk_cli.projects.constants import (OLD_PROJECT_CONFIG_FILENAME, - PROJECT_CONFIG_FILENAME) -from multiversx_sdk_cli.projects.project_base import Project -from multiversx_sdk_cli.projects.project_rust import ProjectRust - -logger = logging.getLogger("projects.core") - - -def load_project(directory: Path) -> Project: - guards.is_directory(directory) - - if shared.is_source_rust(directory): - return ProjectRust(directory) - else: - raise errors.NotSupportedProject(str(directory)) - - -def build_project(directory: Path, args: List[str]): - directory = directory.expanduser() - - logger.info("build_project.directory: %s", directory) - - project = load_project(directory) - outputs = project.build(args) - logger.info("Build ran.") - for output_wasm_file in outputs: - logger.info(f"WASM file generated: {output_wasm_file}") - - -def clean_project(directory: Path): - logger.info("clean_project.directory: %s", directory) - directory = directory.expanduser() - guards.is_directory(directory) - project = load_project(directory) - project.clean() - logger.info("Project cleaned.") - - -def run_tests(args: Any): - directory = Path(args.path) - - logger.info("run_tests.project: %s", directory) - - guards.is_directory(directory) - project = load_project(directory) - project.run_tests(args) - - -def get_project_paths_recursively(base_path: Path) -> List[Path]: - guards.is_directory(base_path) - old_markers = list(base_path.glob(f"**/{OLD_PROJECT_CONFIG_FILENAME}")) - new_markers = list(base_path.glob(f"**/{PROJECT_CONFIG_FILENAME}")) - project_marker_files = old_markers + new_markers - path_list = [marker_file.parent for marker_file in project_marker_files] - return sorted(path_list) diff --git a/multiversx_sdk_cli/projects/interfaces.py b/multiversx_sdk_cli/projects/interfaces.py deleted file mode 100644 index 980b7b48..00000000 --- a/multiversx_sdk_cli/projects/interfaces.py +++ /dev/null @@ -1,10 +0,0 @@ -from pathlib import Path -from typing import Any - - -class IProject(): - def get_option(self, option_name: str) -> Any: - return None - - def get_file_wasm(self) -> Path: - return Path("") diff --git a/multiversx_sdk_cli/projects/migrations.py b/multiversx_sdk_cli/projects/migrations.py deleted file mode 100644 index 6f861011..00000000 --- a/multiversx_sdk_cli/projects/migrations.py +++ /dev/null @@ -1,21 +0,0 @@ -from pathlib import Path - -from multiversx_sdk_cli.projects.constants import (OLD_PROJECT_CONFIG_FILENAME, - PROJECT_CONFIG_FILENAME) - - -def migrate_project_config_file(project_path: Path): - new_config_file = project_path / PROJECT_CONFIG_FILENAME - old_config_file = project_path / OLD_PROJECT_CONFIG_FILENAME - - if old_config_file.exists(): - if new_config_file.exists(): - old_config_file.unlink() - else: - old_config_file.rename(new_config_file) - - -def migrate_project_templates(parent_folder: Path): - for project_folder in parent_folder.iterdir(): - if project_folder.is_dir(): - migrate_project_config_file(project_folder) diff --git a/multiversx_sdk_cli/projects/project_base.py b/multiversx_sdk_cli/projects/project_base.py deleted file mode 100644 index 75d7db8b..00000000 --- a/multiversx_sdk_cli/projects/project_base.py +++ /dev/null @@ -1,195 +0,0 @@ -import logging -import shutil -from abc import abstractmethod -from os import path -from pathlib import Path -from typing import Any, Dict, List, Union, final - -from multiversx_sdk_cli import dependencies, errors, myprocess, utils -from multiversx_sdk_cli.projects.constants import PROJECT_CONFIG_FILENAME -from multiversx_sdk_cli.projects.interfaces import IProject -from multiversx_sdk_cli.projects.migrations import migrate_project_config_file - -logger = logging.getLogger("Project") - - -class Project(IProject): - - def __init__(self, directory: Path): - self.path = directory.expanduser().resolve() - self.directory = str(self.path) - - def build(self, args: List[str], options: Union[Dict[str, Any], None] = None) -> List[Path]: - migrate_project_config_file(self.path) - self.forwarded_args = args - self.options = options or dict() - self.debug = self.options.get("debug", False) - self._ensure_dependencies_installed() - self.perform_build() - contract_paths = self._do_after_build_custom() - self._do_after_build_core() - return contract_paths - - def clean(self): - utils.remove_folder(self.get_output_folder()) - - def _ensure_dependencies_installed(self): - module_keys = self.get_dependencies() - for module_key in module_keys: - if module_key == "": - continue - dependencies.install_module(module_key) - - def get_dependencies(self) -> List[str]: - raise NotImplementedError() - - def perform_build(self) -> None: - raise NotImplementedError() - - def get_file_wasm(self) -> Path: - return self.find_file_in_output("*.wasm") - - def find_file_globally(self, pattern: str) -> Path: - return self.find_file_in_folder(self.path, pattern) - - def find_file_in_output(self, pattern: str) -> Path: - folder = self.get_output_folder() - return self.find_file_in_folder(Path(folder), pattern) - - def find_file_in_folder(self, folder: Path, pattern: str) -> Path: - files = list(folder.rglob(pattern)) - - if len(files) == 0: - raise errors.KnownError(f"No file matches pattern [{pattern}].") - if len(files) > 1: - logger.warning(f"More files match pattern [{pattern}]. Will pick first:\n{files}") - - file = folder / files[0] - return Path(file).resolve() - - def find_wasm_files(self): - output_folder = Path(self.get_output_folder()) - wasm_files = output_folder.rglob("*.wasm") - main_wasm_files = list(filter(lambda wasm_path: not wasm_path.name.endswith("-dbg.wasm"), wasm_files)) - return main_wasm_files - - @abstractmethod - def _do_after_build_custom(self) -> List[Path]: - raise NotImplementedError() - - @final - def _do_after_build_core(self): - pass - - def _copy_to_output(self, source: Path, destination: Union[str, None] = None) -> Path: - output_folder = self.get_output_folder() - utils.ensure_folder(output_folder) - destination = path.join(output_folder, destination) if destination else output_folder - output_wasm_file = shutil.copy(str(source), destination) - return Path(output_wasm_file) - - def get_output_folder(self): - return path.join(self.directory, "output") - - def get_wasm_default_name(self, suffix: str = "") -> str: - return f"{self.path.name}{suffix}.wasm" - - def get_wasm_path(self, wasm_name: str) -> Path: - return Path(self.get_output_folder(), wasm_name).resolve() - - def get_wasm_default_path(self) -> Path: - return self.get_wasm_path(self.get_wasm_default_name()) - - def get_wasm_view_default_path(self) -> Path: - return self.get_wasm_path(self.get_wasm_default_name("-view")) - - def get_bytecode(self): - bytecode: bytes = self.get_file_wasm().read_bytes() - bytecode_hex = bytecode.hex() - return bytecode_hex - - def load_config(self): - self.ensure_config_file() - config_file = self.get_config_file() - config = utils.read_json_file(str(config_file)) - return config - - def get_config_file(self) -> Path: - return self.path / PROJECT_CONFIG_FILENAME - - def ensure_config_file(self) -> None: - config_file = self.get_config_file() - - if not config_file.exists(): - utils.write_json_file(str(config_file), self.default_config()) - logger.info("created default project configuration") - - def default_config(self) -> Dict[str, Any]: - return dict() - - def run_tests(self, args: Any): - command = ["sc-meta", "test"] - - if args.path: - command.extend(["--path", str(Path(args.path).expanduser())]) - - if args.go: - command.append("--go") - - if args.scen: - command.append("--scen") - - if args.nocapture: - command.append("--nocapture") - - myprocess.run_process(command) - - -def glob_files(folder: Path, pattern: str) -> List[Path]: - files = folder.rglob(pattern) - return [Path(folder / file).resolve() for file in files] - - -def exclude_files(files: List[Path], to_exclude: List[Path]) -> List[Path]: - return list(set(files).difference(to_exclude)) - - -def rename_wasm_files(paths: List[Path], name: Union[str, None]) -> List[Path]: - if name is None: - return paths - new_paths = [adjust_wasm_filename(path, name) for path in paths] - for old_path, new_path in zip(paths, new_paths): - old_path.rename(new_path) - return new_paths - - -def get_contract_suffix(name: str) -> str: - for suffix in ["-view.wasm", ".wasm"]: - if name.endswith(suffix): - return suffix - return "" - - -def remove_suffix(name: str, suffix: str) -> str: - if not name.endswith(suffix) or len(suffix) == 0: - return name - return name[:-len(suffix)] - - -def adjust_wasm_filename(path: Path, name_hint: str) -> Path: - """ - Adjusts the wasm's filename by using a name hint - - >>> adjust_wasm_filename(Path("test/my-contract.wasm"), "hello.wasm") - PosixPath('test/hello.wasm') - >>> adjust_wasm_filename(Path("test/my-contract-view.wasm"), "hello.wasm") - PosixPath('test/hello-view.wasm') - >>> adjust_wasm_filename(Path("test/my-contract-view.wasm"), "hello") - PosixPath('test/hello-view.wasm') - >>> adjust_wasm_filename(Path("test/my-contract.wasm"), "world-view.wasm") - PosixPath('test/world-view.wasm') - >>> adjust_wasm_filename(Path("test/my-contract-view.wasm"), "world-view.wasm") - PosixPath('test/world-view-view.wasm') - """ - new_name = remove_suffix(name_hint, ".wasm") + get_contract_suffix(path.name) - return path.with_name(new_name) diff --git a/multiversx_sdk_cli/projects/project_rust.py b/multiversx_sdk_cli/projects/project_rust.py deleted file mode 100644 index 7825d5d6..00000000 --- a/multiversx_sdk_cli/projects/project_rust.py +++ /dev/null @@ -1,123 +0,0 @@ -import logging -import subprocess -from pathlib import Path -from typing import Any, Dict, List - -from multiversx_sdk_cli import dependencies, errors, utils, workstation -from multiversx_sdk_cli.constants import DEFAULT_CARGO_TARGET_DIR_NAME -from multiversx_sdk_cli.dependencies.modules import Rust -from multiversx_sdk_cli.projects.project_base import Project - -logger = logging.getLogger("ProjectRust") - - -class ProjectRust(Project): - def __init__(self, directory: Path): - super().__init__(directory) - - def clean(self): - env = self.get_env() - - args = [ - "sc-meta", - "all", - "clean", - "--path", - self.directory - ] - - subprocess.check_call(args, env=env) - - def get_meta_folder(self): - return self.path / 'meta' - - def get_wasm_view_folder(self): - return self.path / 'wasm-view' - - def perform_build(self): - meta = self.has_meta() - if not meta: - raise errors.NotSupportedProject("The project does not have a meta crate") - - self.run_meta() - - def prepare_build_wasm_args(self, args: List[str]): - args.extend([ - "--target=wasm32-unknown-unknown", - "--release", - "--out-dir", - self.get_output_folder() - ]) - - def run_meta(self): - env = self.get_env() - - args = [ - "sc-meta", - "all", - "build" - ] - - args.extend(self.forwarded_args) - - try: - subprocess.check_call(args, env=env) - except subprocess.CalledProcessError as err: - raise errors.BuildError(f"error code = {err.returncode}, see output") - - def _ensure_cargo_target_dir(self, target_dir: str): - default_target_dir = str(workstation.get_tools_folder() / DEFAULT_CARGO_TARGET_DIR_NAME) - target_dir = target_dir or default_target_dir - utils.ensure_folder(target_dir) - return target_dir - - def has_meta(self): - return (self.get_meta_folder() / "Cargo.toml").exists() - - def has_wasm_view(self): - return (self.get_wasm_view_folder() / "Cargo.toml").exists() - - def has_abi(self): - return (self.get_abi_folder() / "Cargo.toml").exists() - - def get_abi_filepath(self): - return self.get_abi_folder() / "abi.json" - - def get_abi_folder(self): - return Path(self.directory, "abi") - - def _do_after_build_custom(self) -> List[Path]: - outputs = [self.get_wasm_default_path()] - if self.has_wasm_view(): - outputs.append(self.get_wasm_view_default_path()) - return outputs - - def get_dependencies(self): - return ["rust"] - - def get_env(self): - return dependencies.get_module_by_key("rust").get_env() - - def build_wasm_with_debug_symbols(self, build_options: Dict[str, Any]): - rust_module = Rust("rust") - rust_module.install(overwrite=False) - - cwd = self.path - env = self.get_env() - target_dir: str = build_options.get("target-dir", "") - target_dir = self._ensure_cargo_target_dir(target_dir) - - args = [ - "sc-meta", - "all", - "build", - "--wasm-symbols", - "--wasm-suffix", "dbg", - "--no-wasm-opt", - "--target-dir", target_dir - ] - - try: - subprocess.check_call(args, env=env, cwd=cwd) - except subprocess.CalledProcessError as err: - raise errors.BuildError(f"error code = {err.returncode}, see output") diff --git a/multiversx_sdk_cli/projects/report/__init__.py b/multiversx_sdk_cli/projects/report/__init__.py deleted file mode 100644 index 54ebbb27..00000000 --- a/multiversx_sdk_cli/projects/report/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from .do_report import do_report - -__all__ = ["do_report"] diff --git a/multiversx_sdk_cli/projects/report/data/__init__.py b/multiversx_sdk_cli/projects/report/data/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/multiversx_sdk_cli/projects/report/data/common.py b/multiversx_sdk_cli/projects/report/data/common.py deleted file mode 100644 index e2983c6e..00000000 --- a/multiversx_sdk_cli/projects/report/data/common.py +++ /dev/null @@ -1,56 +0,0 @@ -from collections import OrderedDict -import itertools -from typing import Callable, List, Optional, TypeVar - - -def flatten_list_of_rows(list_of_rows: List[List[List[str]]]) -> List[List[str]]: - return list(itertools.chain(*list_of_rows)) - - -def merge_values(first: List[str], second: List[str]) -> List[str]: - return list(OrderedDict.fromkeys(first + second)) - - -T = TypeVar('T') -K = TypeVar('K') - - -def first_not_none(first: Optional[T], second: Optional[T]) -> T: - return next(item for item in [first, second] if item is not None) - - -def get_keys(items: List[T], key_getter: Callable[[T], K]) -> List[K]: - return [key_getter(item) for item in items] - - -def list_as_key_value_dict(items: List[T], key_getter: Callable[[T], K]) -> 'OrderedDict[K, T]': - return OrderedDict(zip(get_keys(items, key_getter), items)) - - -def merge_values_by_key(first: List[T], second: List[T], key_getter: Callable[[T], K], merge: Callable[[Optional[T], Optional[T]], T]) -> List[T]: - """ - Merge the values of two lists when the key matches. - Used in order to de-duplicate report entries depending on certain criteria, such as paths or feature names. - ->>> def merge_func(a, b): -... if a == None: -... return (b[0], b[1] + 100) -... if b == None: -... return (a[0], a[1] + 200) -... return (a[0], a[1] + b[1]) ->>> first = [('one', 1), ('two', 2)] ->>> second = [('two', 3), ('three', 4)] ->>> key_getter = lambda item: item[0] ->>> merge_values_by_key(first, second, key_getter, merge_func) -[('one', 201), ('two', 5), ('three', 104)] - """ - first_as_dict = list_as_key_value_dict(first, key_getter) - second_as_dict = list_as_key_value_dict(second, key_getter) - union = OrderedDict.fromkeys(list(first_as_dict.keys()) + list(second_as_dict.keys())) - all_keys = union.keys() - result = [] - for key in all_keys: - first_value = first_as_dict.get(key) - second_value = second_as_dict.get(key) - result.append(merge(first_value, second_value)) - return result diff --git a/multiversx_sdk_cli/projects/report/data/extracted_feature.py b/multiversx_sdk_cli/projects/report/data/extracted_feature.py deleted file mode 100644 index 02ed3d4f..00000000 --- a/multiversx_sdk_cli/projects/report/data/extracted_feature.py +++ /dev/null @@ -1,104 +0,0 @@ -from typing import Any, List, Optional - -from multiversx_sdk_cli.projects.report.data.common import first_not_none, merge_values_by_key -from multiversx_sdk_cli.projects.report.format.change_type import ChangeType -from multiversx_sdk_cli.projects.report.format.format_options import FormatOptions - - -class ExtractedFeature: - def __init__(self, feature_name: str, results: List[str]) -> None: - self.feature_name = feature_name - self.results = results - - def to_json(self) -> Any: - return { - 'feature_name': self.feature_name, - 'results': self.results - } - - @staticmethod - def from_json(json: Any) -> 'ExtractedFeature': - return ExtractedFeature(json['feature_name'], json['results']) - - def results_to_markdown(self, format_options: FormatOptions) -> str: - separator = ' :arrow_right: ' if format_options.github_flavor else ' -> ' - change_type = self._classify_changes() - display_results = _prepare_results_for_markdown(self.results) - if change_type == ChangeType.NONE: - return display_results[0] - else: - return separator.join(display_results) + ' ' + change_type.to_markdown(format_options) - - def _classify_changes(self) -> ChangeType: - """ - Used to classify if a change in a feature's results is good, bad etc. - Required because changes in values are not always intuitive: - - a higher value may be worse than a small one (eg. for file sizes) - - a 'Yes' may be worse than a 'No' - This information is used to draw small icons at the end of a cell containing a change. - """ - - all_results_are_identical = all(result == self.results[0] for result in self.results) - if all_results_are_identical: - return ChangeType.NONE - any_is_not_available = any(result == 'N/A' for result in self.results) - if any_is_not_available: - return ChangeType.UNKNOWN - if self.feature_name == 'size': - sizes = list(map(int, self.results)) - return _classify_list(sizes, reverse=True) - if self.feature_name == 'has-allocator' or self.feature_name == 'has-format': - presence_checks = list(map(lambda value: False if value == 'False' else True, self.results)) - return _classify_list(presence_checks, reverse=True) - return ChangeType.UNKNOWN - - -def _prepare_results_for_markdown(results: List[str]) -> List[str]: - return list(map(_replace_bool_with_yes_no, results)) - - -def _replace_bool_with_yes_no(item: str) -> str: - if item == 'True': - return 'Yes' - if item == 'False': - return 'No' - return item - - -def _classify_list(items: List[Any], reverse: bool = False) -> ChangeType: - if reverse: - items.reverse() - if _is_strictly_better(items): - return ChangeType.GOOD - if _is_strictly_worse(items): - return ChangeType.BAD - return ChangeType.MIXED - - -def _is_strictly_better(items: List[Any]) -> bool: - return sorted(items) == items - - -def _is_strictly_worse(items: List[Any]) -> bool: - return sorted(items, reverse=True) == items - - -def merge_lists_of_extracted_features(first: List[ExtractedFeature], second: List[ExtractedFeature]) -> List[ExtractedFeature]: - return merge_values_by_key(first, second, _get_extracted_feature_key, _merge_two_extracted_features) - - -def _get_extracted_feature_key(extracted_feature: ExtractedFeature) -> str: - return extracted_feature.feature_name - - -def _merge_two_extracted_features(first: Optional[ExtractedFeature], second: Optional[ExtractedFeature]) -> ExtractedFeature: - any = first_not_none(first, second) - merged_results = _results_or_NA(first) + _results_or_NA(second) - return ExtractedFeature(any.feature_name, merged_results) - - -def _results_or_NA(extracted_feature: Optional[ExtractedFeature]) -> List[str]: - if extracted_feature is None: - return ['N/A'] - else: - return extracted_feature.results diff --git a/multiversx_sdk_cli/projects/report/data/folder_report.py b/multiversx_sdk_cli/projects/report/data/folder_report.py deleted file mode 100644 index d0559379..00000000 --- a/multiversx_sdk_cli/projects/report/data/folder_report.py +++ /dev/null @@ -1,53 +0,0 @@ -from pathlib import Path -from typing import Any, List, Optional - -from multiversx_sdk_cli.projects.report.data.common import ( - first_not_none, flatten_list_of_rows, merge_values_by_key) -from multiversx_sdk_cli.projects.report.data.project_report import ( - ProjectReport, merge_list_of_projects) -from multiversx_sdk_cli.projects.report.format.format_options import \ - FormatOptions - - -class FolderReport: - def __init__(self, root_path: Path, projects: List[ProjectReport]) -> None: - self.root_path = root_path - self.projects = projects - - def to_json(self) -> Any: - return { - 'root_path': str(self.root_path), - 'projects': self.projects - } - - @staticmethod - def from_json(json: Any) -> 'FolderReport': - projects = [ProjectReport.from_json(project) for project in json['projects']] - return FolderReport(Path(json['root_path']), projects) - - def get_markdown_rows(self, format_options: FormatOptions) -> List[List[str]]: - folder_row = [str(self.root_path)] - project_rows = flatten_list_of_rows([project.get_rows_markdown(format_options) for project in self.projects]) - return [folder_row] + project_rows - - -def merge_list_of_folder_reports(first: List[FolderReport], second: List[FolderReport]) -> List[FolderReport]: - return merge_values_by_key(first, second, _get_folder_report_root_path, _merge_two_folder_reports) - - -def _get_folder_report_root_path(item: FolderReport) -> Path: - return item.root_path - - -def _projects_or_default(folder_report: Optional[FolderReport]) -> List[ProjectReport]: - if folder_report is None: - return [] - return folder_report.projects - - -def _merge_two_folder_reports(first: Optional[FolderReport], second: Optional[FolderReport]) -> FolderReport: - any = first_not_none(first, second) - first_projects = _projects_or_default(first) - second_projects = _projects_or_default(second) - merged_projects = merge_list_of_projects(first_projects, second_projects) - return FolderReport(any.root_path, merged_projects) diff --git a/multiversx_sdk_cli/projects/report/data/project_report.py b/multiversx_sdk_cli/projects/report/data/project_report.py deleted file mode 100644 index db137b6a..00000000 --- a/multiversx_sdk_cli/projects/report/data/project_report.py +++ /dev/null @@ -1,56 +0,0 @@ -from pathlib import Path -from typing import Any, List, Optional -from multiversx_sdk_cli.projects.report.data.common import first_not_none, merge_values_by_key - -from multiversx_sdk_cli.projects.report.data.wasm_report import WasmReport, merge_list_of_wasm_reports -from multiversx_sdk_cli.projects.report.format.format_options import FormatOptions - - -class ProjectReport: - def __init__(self, project_path: Path, wasm_reports: List[WasmReport]) -> None: - self.project_path = project_path - self.wasm_reports = wasm_reports - - def to_json(self) -> Any: - return { - 'project_path': str(self.project_path), - 'wasm_reports': self.wasm_reports - } - - @staticmethod - def from_json(json: Any) -> 'ProjectReport': - wasm_reports = [WasmReport.from_json(wasm_report) for wasm_report in json['wasm_reports']] - return ProjectReport(Path(json['project_path']), wasm_reports) - - def get_rows_markdown(self, format_options: FormatOptions) -> List[List[str]]: - wasm_count = len(self.wasm_reports) - if wasm_count == 0: - return [[f" - {str(self.project_path)} "]] - elif wasm_count == 1: - return [[f" - {str(self.project_path / self.wasm_reports[0].wasm_name)}"] + self.wasm_reports[0].get_extracted_features_markdown(format_options)] - else: - project_path_row = [f" - {str(self.project_path)}"] - wasm_rows = [[f" - - {wasm.wasm_name}"] + wasm.get_extracted_features_markdown(format_options) for wasm in self.wasm_reports] - return [project_path_row] + wasm_rows - - -def merge_list_of_projects(first: List[ProjectReport], second: List[ProjectReport]) -> List[ProjectReport]: - return merge_values_by_key(first, second, _get_project_report_path, _merge_two_project_reports) - - -def _get_project_report_path(project_report: ProjectReport) -> Path: - return project_report.project_path - - -def _wasm_reports_or_default(project_report: Optional[ProjectReport]) -> List[WasmReport]: - if project_report is None: - return [] - return project_report.wasm_reports - - -def _merge_two_project_reports(first: Optional[ProjectReport], second: Optional[ProjectReport]) -> ProjectReport: - any = first_not_none(first, second) - first_wasm_reports = _wasm_reports_or_default(first) - second_wasm_reports = _wasm_reports_or_default(second) - merged_wasm_reports = merge_list_of_wasm_reports(first_wasm_reports, second_wasm_reports) - return ProjectReport(any.project_path, merged_wasm_reports) diff --git a/multiversx_sdk_cli/projects/report/data/report.py b/multiversx_sdk_cli/projects/report/data/report.py deleted file mode 100644 index cd9440a6..00000000 --- a/multiversx_sdk_cli/projects/report/data/report.py +++ /dev/null @@ -1,107 +0,0 @@ -import functools -from io import StringIO -import json -from pathlib import Path -from typing import Any, List -from multiversx_sdk_cli.projects.report.data.folder_report import FolderReport, merge_list_of_folder_reports - -from multiversx_sdk_cli.projects.report.data.common import flatten_list_of_rows, merge_values -from multiversx_sdk_cli.projects.report.format.change_type import ChangeType -from multiversx_sdk_cli.projects.report.format.format_options import FormatOptions - - -class Report: - def __init__(self, feature_names: List[str], folders: List[FolderReport]) -> None: - self.feature_names = feature_names - self.folders = folders - - def to_json(self) -> Any: - return { - 'features': self.feature_names, - 'folders': self.folders - } - - @staticmethod - def from_json(json: Any) -> 'Report': - folders = [FolderReport.from_json(folder_report) for folder_report in json['folders']] - return Report(json['features'], folders) - - @staticmethod - def load_from_file(report_json_path: Path) -> 'Report': - with open(report_json_path, 'r') as report_file: - report_json = json.load(report_file) - return Report.from_json(report_json) - - def get_markdown_rows(self, format_options: FormatOptions) -> List[List[str]]: - rows = [folder_report.get_markdown_rows(format_options) for folder_report in self.folders] - return flatten_list_of_rows(rows) - - def to_markdown(self, format_options: FormatOptions) -> str: - text = StringIO() - - table_headers = ["Path"] + self.feature_names - _adjust_table_headers(table_headers, format_options) - _write_markdown_row(text, table_headers, format_options) - - ALIGN_LEFT = ":--" - ALIGN_RIGHT = "--:" - row_alignments = [ALIGN_LEFT] + len(self.feature_names) * [ALIGN_RIGHT] - _write_markdown_row(text, row_alignments, format_options) - - for row in self.get_markdown_rows(format_options): - _write_markdown_row(text, row, format_options) - - return text.getvalue() - - def to_json_string(self) -> str: - return json.dumps(self, indent=4, default=lambda obj: obj.to_json()) - - -# Adjusts the column widths in github tables - see: -# https://github.com/markedjs/marked/issues/266#issuecomment-616347986 -def _adjust_table_headers(table_headers: List[str], format_options: FormatOptions) -> None: - if not format_options.github_flavor: - return - NBSP = '\u00A0' - table_headers[0] = table_headers[0].ljust(60, NBSP) - table_headers[1] = table_headers[1].rjust(40, NBSP) - table_headers[2] = table_headers[2].rjust(30, NBSP) - table_headers[3] = table_headers[3].rjust(30, NBSP) - - -def merge_list_of_reports(reports: List[Report]) -> Report: - return functools.reduce(_merge_two_reports, reports) - - -def _merge_two_reports(first: Report, other: Report) -> Report: - feature_names = merge_values(first.feature_names, other.feature_names) - folders = merge_list_of_folder_reports(first.folders, other.folders) - return Report(feature_names, folders) - - -# Hack in order to keep the column alignment in a terminal -# as unicode characters are sometimes wider or narrower -def _justify_text_string(string: str, width: int) -> str: - if ChangeType.UNKNOWN._to_text_markdown() in string: - width += 1 - if ChangeType.GOOD._to_text_markdown() in string: - width -= 1 - if ChangeType.BAD._to_text_markdown() in string: - width -= 1 - return string.rjust(width) - - -def _format_row_markdown(row: List[str], format_options: FormatOptions) -> str: - row += [''] * (4 - len(row)) - if not format_options.github_flavor: - row[0] = row[0].ljust(100) - row[1] = _justify_text_string(row[1], 20) - row[2] = _justify_text_string(row[2], 20) - row[3] = _justify_text_string(row[3], 20) - merged_cells = " | ".join(row) - return f"| {merged_cells} |" - - -def _write_markdown_row(string: StringIO, row: List[str], format_options: FormatOptions): - string.write(_format_row_markdown(row, format_options)) - string.write('\n') diff --git a/multiversx_sdk_cli/projects/report/data/wasm_report.py b/multiversx_sdk_cli/projects/report/data/wasm_report.py deleted file mode 100644 index 14dcd731..00000000 --- a/multiversx_sdk_cli/projects/report/data/wasm_report.py +++ /dev/null @@ -1,46 +0,0 @@ -from typing import Any, List, Optional -from multiversx_sdk_cli.projects.report.data.common import first_not_none, merge_values_by_key -from multiversx_sdk_cli.projects.report.data.extracted_feature import ExtractedFeature, merge_lists_of_extracted_features -from multiversx_sdk_cli.projects.report.format.format_options import FormatOptions - - -class WasmReport: - def __init__(self, wasm_name: str, extracted_features: List[ExtractedFeature]) -> None: - self.wasm_name = wasm_name - self.extracted_features = extracted_features - - def to_json(self) -> Any: - return { - 'wasm_name': self.wasm_name, - 'extracted_features': self.extracted_features - } - - @staticmethod - def from_json(json: Any) -> 'WasmReport': - extracted_features = [ExtractedFeature.from_json(extracted_feature) for extracted_feature in json['extracted_features']] - return WasmReport(json['wasm_name'], extracted_features) - - def get_extracted_features_markdown(self, format_options: FormatOptions) -> List[str]: - return [extracted_feature.results_to_markdown(format_options) for extracted_feature in self.extracted_features] - - -def merge_list_of_wasm_reports(first: List[WasmReport], second: List[WasmReport]) -> List[WasmReport]: - return merge_values_by_key(first, second, _get_wasm_report_key, merge_two_wasm_reports) - - -def _get_wasm_report_key(wasm_report: WasmReport) -> str: - return wasm_report.wasm_name - - -def _get_extracted_features_or_default(wasm: Optional[WasmReport]) -> List[ExtractedFeature]: - if wasm is None: - return [] - return wasm.extracted_features - - -def merge_two_wasm_reports(first: Optional[WasmReport], second: Optional[WasmReport]) -> WasmReport: - any = first_not_none(first, second) - first_extracted_features = _get_extracted_features_or_default(first) - second_extracted_features = _get_extracted_features_or_default(second) - merged_extracted_features = merge_lists_of_extracted_features(first_extracted_features, second_extracted_features) - return WasmReport(any.wasm_name, merged_extracted_features) diff --git a/multiversx_sdk_cli/projects/report/do_report.py b/multiversx_sdk_cli/projects/report/do_report.py deleted file mode 100644 index 8d5b2fc0..00000000 --- a/multiversx_sdk_cli/projects/report/do_report.py +++ /dev/null @@ -1,77 +0,0 @@ -import copy -import logging -from pathlib import Path -from typing import Any, Dict, List - -from multiversx_sdk_cli import cli_shared, utils -from multiversx_sdk_cli.projects.core import get_project_paths_recursively -from multiversx_sdk_cli.projects.report.data.report import ( - Report, merge_list_of_reports) -from multiversx_sdk_cli.projects.report.features.features import \ - get_default_report_features -from multiversx_sdk_cli.projects.report.format.format_options import \ - FormatOptions -from multiversx_sdk_cli.projects.report.report_creator import ReportCreator - -logger = logging.getLogger("report") - - -def do_report(args: Any, build_options: Any) -> None: - compare_report_paths = args.compare - if compare_report_paths is None: - _build_report(args, build_options) - else: - _compare_reports(args, compare_report_paths) - - -def _build_report(args: Any, build_options: Dict[str, Any]) -> None: - base_path = Path(args.path) - project_paths = get_project_paths_recursively(base_path) - options = get_default_report_features() - - args_copy = _prepare_args_for_build(args) - build_args = cli_shared.convert_args_object_to_args_list(args_copy) - - report_creator = ReportCreator(options, skip_build=args.skip_build, skip_twiggy=args.skip_twiggy, build_options=build_options, build_args=build_args) - report = report_creator.create_report(base_path, project_paths) - _finalize_report(report, args) - - -def _prepare_args_for_build(args: Any): - args_copy = copy.deepcopy(args) - - arguments: Dict[str, Any] = args_copy.__dict__ - arguments.pop("output_format", None) - arguments.pop("output_file", None) - - return args_copy - - -def _compare_reports(args: Any, merge_report_paths: List[Path]) -> None: - reports = [Report.load_from_file(report_path) for report_path in merge_report_paths] - final_report = merge_list_of_reports(reports) - _finalize_report(final_report, args) - - -def _finalize_report(report: Report, args: Any) -> None: - output = _get_report_output_string(report, args) - _store_output(output, args) - - -def _get_report_output_string(report: Report, args: Any) -> str: - output_format = args.output_format - if output_format == 'github-markdown': - return report.to_markdown(FormatOptions(github_markdown=True)) - if output_format == 'text-markdown': - return report.to_markdown(FormatOptions(github_markdown=False)) - elif output_format == 'json': - return report.to_json_string() - raise Exception('Invalid output format') - - -def _store_output(output: str, args: Any) -> None: - output_file_path = args.output_file - if output_file_path is None: - print(output) - else: - utils.write_file(Path(output_file_path), output) diff --git a/multiversx_sdk_cli/projects/report/features/__init__.py b/multiversx_sdk_cli/projects/report/features/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/multiversx_sdk_cli/projects/report/features/features.py b/multiversx_sdk_cli/projects/report/features/features.py deleted file mode 100644 index 4ea081db..00000000 --- a/multiversx_sdk_cli/projects/report/features/features.py +++ /dev/null @@ -1,13 +0,0 @@ -from typing import List - -from multiversx_sdk_cli.projects.report.features.report_option import ReportFeature -from multiversx_sdk_cli.projects.report.features.size import Size -from multiversx_sdk_cli.projects.report.features.twiggy_paths_check import TwiggyPathsCheck - - -def get_default_report_features() -> List[ReportFeature]: - return [ - Size("size"), - TwiggyPathsCheck("has-allocator", pattern="wee_alloc::"), - TwiggyPathsCheck("has-format", pattern="core::fmt"), - ] diff --git a/multiversx_sdk_cli/projects/report/features/report_option.py b/multiversx_sdk_cli/projects/report/features/report_option.py deleted file mode 100644 index 67f87cbe..00000000 --- a/multiversx_sdk_cli/projects/report/features/report_option.py +++ /dev/null @@ -1,29 +0,0 @@ -from abc import abstractmethod -from pathlib import Path -from typing import Any, Optional - - -class ReportFeature: - """ - Base class for any feature in a report. - - A feature represents a column in a report. - The name argument will appear as the column header in a report. - The implementation of the extract method will determine the contents of each cell in said column. - """ - - def __init__(self, name: str) -> None: - self.name = name - - @abstractmethod - def extract(self, wasm_path: Path) -> str: - pass - - def requires_twiggy_paths(self) -> bool: - return False - - -def str_or_default(field: Optional[Any], default: str = '-') -> str: - if field is None: - return default - return str(field) diff --git a/multiversx_sdk_cli/projects/report/features/size.py b/multiversx_sdk_cli/projects/report/features/size.py deleted file mode 100644 index 2b162d22..00000000 --- a/multiversx_sdk_cli/projects/report/features/size.py +++ /dev/null @@ -1,17 +0,0 @@ -from pathlib import Path -from typing import Optional - -from .report_option import ReportFeature, str_or_default - - -class Size(ReportFeature): - def extract(self, wasm_path: Path): - size = _get_file_size(wasm_path) - return str_or_default(size) - - -def _get_file_size(file_path: Path) -> Optional[int]: - try: - return int(file_path.stat().st_size) - except FileNotFoundError: - return None diff --git a/multiversx_sdk_cli/projects/report/features/twiggy_paths_check.py b/multiversx_sdk_cli/projects/report/features/twiggy_paths_check.py deleted file mode 100644 index 31fa51ff..00000000 --- a/multiversx_sdk_cli/projects/report/features/twiggy_paths_check.py +++ /dev/null @@ -1,67 +0,0 @@ -import logging -from pathlib import Path - -from multiversx_sdk_cli import dependencies, myprocess, utils -from multiversx_sdk_cli.errors import BadFile -from multiversx_sdk_cli.projects.report.features.report_option import \ - ReportFeature - -logger = logging.getLogger("projects.report.options.twiggy_paths_check") - - -class TwiggyPathsCheck(ReportFeature): - def __init__(self, name: str, pattern: str) -> None: - super().__init__(name) - - self.pattern = pattern - - def extract(self, wasm_path: Path) -> str: - twiggy_paths_path = _get_twiggy_paths_path(wasm_path) - try: - text = utils.read_text_file(twiggy_paths_path) - return str(self.pattern in text) - except BadFile: - return 'N/A' - - def requires_twiggy_paths(self): - return True - - -def run_twiggy_paths(wasm_path: Path) -> Path: - rust = dependencies.get_module_by_key("rust") - debug_wasm_path = _get_debug_wasm_path(wasm_path) - - twiggy_paths_args = ["twiggy", "paths", str(debug_wasm_path)] - output = myprocess.run_process(twiggy_paths_args, env=rust.get_env(), cwd=debug_wasm_path.parent, dump_to_stdout=False) - - output_path = _get_twiggy_paths_path(wasm_path) - utils.write_file(output_path, output) - logger.info(f"Twiggy paths output path: {output_path}") - return output_path - - -def _replace_file_suffix(file_path: Path, suffix: str) -> Path: - new_name = file_path.stem + suffix - return file_path.with_name(new_name) - - -def _add_file_prefix(file_path: Path, prefix: str) -> Path: - new_name = prefix + file_path.name - return file_path.with_name(new_name) - - -def _get_debug_wasm_path(wasm_path: Path) -> Path: - """ ->>> _get_debug_wasm_path(Path('test/contract.wasm')) -PosixPath('test/contract-dbg.wasm') - """ - return _replace_file_suffix(wasm_path, '-dbg.wasm') - - -def _get_twiggy_paths_path(wasm_path: Path) -> Path: - """ ->>> _get_twiggy_paths_path(Path('test/contract.wasm')) -PosixPath('test/twiggy-paths-contract-dbg.txt') - """ - txt_file_path = _replace_file_suffix(wasm_path, '-dbg.txt') - return _add_file_prefix(txt_file_path, 'twiggy-paths-') diff --git a/multiversx_sdk_cli/projects/report/format/__init__.py b/multiversx_sdk_cli/projects/report/format/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/multiversx_sdk_cli/projects/report/format/change_type.py b/multiversx_sdk_cli/projects/report/format/change_type.py deleted file mode 100644 index 7685ee82..00000000 --- a/multiversx_sdk_cli/projects/report/format/change_type.py +++ /dev/null @@ -1,37 +0,0 @@ -from enum import Enum, auto - -from multiversx_sdk_cli.projects.report.format.format_options import FormatOptions - - -class ChangeType(Enum): - UNKNOWN = auto() - NONE = auto() - GOOD = auto() - BAD = auto() - MIXED = auto() - - def to_markdown(self, format_options: FormatOptions) -> str: - if format_options.github_flavor: - return self._to_github_markdown() - else: - return self._to_text_markdown() - - def _to_github_markdown(self) -> str: - switch = { - ChangeType.UNKNOWN: ':warning:', - ChangeType.NONE: '', - ChangeType.GOOD: ':green_circle:', - ChangeType.BAD: ':red_circle:', - ChangeType.MIXED: ':yellow_circle:' - } - return switch[self] - - def _to_text_markdown(self) -> str: - switch = { - ChangeType.UNKNOWN: '\u26a0\ufe0f ', - ChangeType.NONE: '', - ChangeType.GOOD: '\U0001F34F', - ChangeType.BAD: '\u274C', - ChangeType.MIXED: '\U0001F536\uFE0F' - } - return switch[self] diff --git a/multiversx_sdk_cli/projects/report/format/format_options.py b/multiversx_sdk_cli/projects/report/format/format_options.py deleted file mode 100644 index ca2a068c..00000000 --- a/multiversx_sdk_cli/projects/report/format/format_options.py +++ /dev/null @@ -1,3 +0,0 @@ -class FormatOptions: - def __init__(self, github_markdown: bool) -> None: - self.github_flavor = github_markdown diff --git a/multiversx_sdk_cli/projects/report/report_creator.py b/multiversx_sdk_cli/projects/report/report_creator.py deleted file mode 100644 index 11c30c87..00000000 --- a/multiversx_sdk_cli/projects/report/report_creator.py +++ /dev/null @@ -1,83 +0,0 @@ - -import itertools -import operator -from pathlib import Path -from typing import Any, Dict, Iterable, List, Tuple - -from multiversx_sdk_cli import guards -from multiversx_sdk_cli.projects.core import load_project -from multiversx_sdk_cli.projects.project_base import remove_suffix -from multiversx_sdk_cli.projects.project_rust import ProjectRust -from multiversx_sdk_cli.projects.report.data.extracted_feature import \ - ExtractedFeature -from multiversx_sdk_cli.projects.report.data.folder_report import FolderReport -from multiversx_sdk_cli.projects.report.data.project_report import \ - ProjectReport -from multiversx_sdk_cli.projects.report.data.report import Report -from multiversx_sdk_cli.projects.report.data.wasm_report import WasmReport -from multiversx_sdk_cli.projects.report.features.report_option import \ - ReportFeature -from multiversx_sdk_cli.projects.report.features.twiggy_paths_check import \ - run_twiggy_paths - - -class ReportCreator: - def __init__(self, options: List[ReportFeature], skip_build: bool, skip_twiggy: bool, build_options: Dict[str, Any], build_args: List[str]) -> None: - self.report_features = options - self.skip_build = skip_build - self.skip_twiggy = skip_twiggy - self.require_twiggy_paths = any(report_feature.requires_twiggy_paths() for report_feature in self.report_features) - self.build_options = build_options - self.build_args = build_args - - def create_report(self, base_path: Path, project_paths: List[Path]) -> Report: - base_path = base_path.resolve() - guards.is_directory(base_path) - - folder_groups = [self._create_folder_report(base_path, parent_folder, iter) - for parent_folder, iter in _group_projects_by_folder(project_paths)] - - feature_names = [report_feature.name for report_feature in self.report_features] - return Report(feature_names, folder_groups) - - def _create_folder_report(self, base_path: Path, parent_folder: Path, iter: Iterable[Tuple[Path, Path]]) -> FolderReport: - parent_folder = parent_folder.resolve() - project_reports = [self._create_project_report(parent_folder, project_path) for _, project_path in iter] - - root_path = parent_folder.relative_to(base_path.parent) - return FolderReport(root_path, project_reports) - - def _create_project_report(self, parent_path: Path, project_path: Path) -> ProjectReport: - project_path = project_path.resolve() - project = load_project(project_path) - - if not self.skip_build: - project.build(self.build_args, self.build_options) - - twiggy_requirements_met = False - should_build_twiggy = self.require_twiggy_paths and not self.skip_twiggy - if should_build_twiggy and isinstance(project, ProjectRust): - project.build_wasm_with_debug_symbols(self.build_options) - twiggy_requirements_met = True - - wasm_reports = [self._create_wasm_report(wasm_path, twiggy_requirements_met) for wasm_path in project.find_wasm_files()] - wasm_reports.sort(key=lambda report: remove_suffix(report.wasm_name, '.wasm')) - - return ProjectReport(project_path.relative_to(parent_path), wasm_reports) - - def _create_wasm_report(self, wasm_path: Path, twiggy_requirements_met: bool) -> WasmReport: - if twiggy_requirements_met: - run_twiggy_paths(wasm_path) - name = wasm_path.name - extracted_features = [_extract_feature(report_feature, wasm_path) for report_feature in self.report_features] - return WasmReport(name, extracted_features) - - -def _extract_feature(feature: ReportFeature, wasm_path: Path) -> ExtractedFeature: - result = feature.extract(wasm_path) - return ExtractedFeature(feature.name, [result]) - - -def _group_projects_by_folder(project_paths: List[Path]) -> Iterable[Tuple[Path, Iterable[Tuple[Path, Path]]]]: - path_pairs = sorted([(path.parent, path) for path in project_paths]) - return itertools.groupby(path_pairs, operator.itemgetter(0)) diff --git a/multiversx_sdk_cli/projects/shared.py b/multiversx_sdk_cli/projects/shared.py deleted file mode 100644 index 25cdbfd4..00000000 --- a/multiversx_sdk_cli/projects/shared.py +++ /dev/null @@ -1,15 +0,0 @@ -import logging -from pathlib import Path - -logger = logging.getLogger("projects.shared") - - -def is_source_rust(directory: Path) -> bool: - return _directory_contains_file(directory, "Cargo.toml") - - -def _directory_contains_file(directory: Path, name_suffix: str) -> bool: - for file in directory.iterdir(): - if str(file).lower().endswith(name_suffix.lower()): - return True - return False diff --git a/multiversx_sdk_cli/projects/templates.py b/multiversx_sdk_cli/projects/templates.py deleted file mode 100644 index 3b5890b9..00000000 --- a/multiversx_sdk_cli/projects/templates.py +++ /dev/null @@ -1,55 +0,0 @@ -import logging -from pathlib import Path -from typing import List, Union - -from multiversx_sdk_cli import myprocess -from multiversx_sdk_cli.dependency_checker import check_if_rust_is_installed - -logger = logging.getLogger("projects.templates") - - -class Contract: - def __init__(self, - tag: Union[str, None] = None, - name: Union[str, None] = None, - template: str = "", - path: Path = Path() - ) -> None: - self.tag = tag - self.name = name - self.template = template - self.path = path - - def get_contract_templates(self) -> str: - self._ensure_dependencies_installed() - args = self._prepare_args_to_list_templates() - templates = myprocess.run_process(args=args, dump_to_stdout=False) - return templates - - def create_from_template(self) -> None: - self._ensure_dependencies_installed() - args = self._prepare_args_to_create_new_contract_from_template() - myprocess.run_process(args) - - def _ensure_dependencies_installed(self): - logger.info("Checking if the necessarry dependencies are installed.") - check_if_rust_is_installed() - - def _prepare_args_to_list_templates(self) -> List[str]: - args = ["sc-meta", "templates"] - - if self.tag: - args.extend(["--tag", self.tag]) - - return args - - def _prepare_args_to_create_new_contract_from_template(self) -> List[str]: - args = ["sc-meta", "new", "--template", self.template, "--path", str(self.path)] - - if self.name: - args.extend(["--name", self.name]) - - if self.tag: - args.extend(["--tag", self.tag]) - - return args diff --git a/multiversx_sdk_cli/sign_verify.py b/multiversx_sdk_cli/sign_verify.py index 77e4c77e..a380fbb9 100644 --- a/multiversx_sdk_cli/sign_verify.py +++ b/multiversx_sdk_cli/sign_verify.py @@ -1,8 +1,22 @@ -from typing import Dict +from typing import Protocol -from multiversx_sdk import Address, Message, MessageComputer, UserVerifier +from multiversx_sdk import ( + Address, + Message, + MessageComputer, + UserVerifier, + ValidatorPublicKey, + ValidatorSigner, +) -from multiversx_sdk_cli.accounts import Account + +# fmt: off +class IAccount(Protocol): + address: Address + + def sign_message(self, message: Message) -> bytes: + ... +# fmt: off class SignedMessage: @@ -18,23 +32,38 @@ def __init__(self, address: str, message: str, signature: str) -> None: self.signature = signature - def verify_signature(self) -> bool: + def verify_user_signature(self) -> bool: verifiable_message = Message(self.message.encode()) verifiable_message.signature = bytes.fromhex(self.signature) message_computer = MessageComputer() verifier = UserVerifier.from_address(Address.new_from_bech32(self.address)) - is_signed = verifier.verify(message_computer.compute_bytes_for_signing(verifiable_message), verifiable_message.signature) + is_signed = verifier.verify( + message_computer.compute_bytes_for_signing(verifiable_message), + verifiable_message.signature, + ) return is_signed - def to_dictionary(self) -> Dict[str, str]: + def verify_validator_signature(self) -> bool: + validator_pubkey = ValidatorPublicKey(bytes.fromhex(self.address)) + return validator_pubkey.verify( + self.message.encode(), + bytes.fromhex(self.signature), + ) + + def to_dictionary(self) -> dict[str, str]: return { "address": self.address, "message": self.message, - "signature": "0x" + self.signature + "signature": "0x" + self.signature, } -def sign_message(message: str, account: Account) -> SignedMessage: - signature = account.sign_message(message.encode()) - return SignedMessage(account.address.to_bech32(), message, signature) +def sign_message(message: str, account: IAccount) -> SignedMessage: + signature = account.sign_message(Message(message.encode())) + return SignedMessage(account.address.to_bech32(), message, signature.hex()) + + +def sign_message_by_validator(message: str, validator: ValidatorSigner) -> SignedMessage: + signature = validator.sign(message.encode()) + return SignedMessage(validator.get_pubkey().hex(), message, signature.hex()) diff --git a/multiversx_sdk_cli/simulation.py b/multiversx_sdk_cli/simulation.py index bd5c41c6..8ba3a35a 100644 --- a/multiversx_sdk_cli/simulation.py +++ b/multiversx_sdk_cli/simulation.py @@ -1,31 +1,34 @@ from collections import OrderedDict -from typing import Any, Dict, Protocol +from typing import Any, Protocol + +from multiversx_sdk import Transaction, TransactionOnNetwork -from multiversx_sdk_cli.interfaces import ISimulateResponse, ITransaction from multiversx_sdk_cli.utils import ISerializable +# fmt: off class INetworkProvider(Protocol): - def simulate_transaction(self, transaction: ITransaction) -> ISimulateResponse: + def simulate_transaction(self, transaction: Transaction) -> TransactionOnNetwork: ... +# fmt: on class Simulation(ISerializable): - def __init__(self, simulate_response: ISimulateResponse) -> None: + def __init__(self, simulate_response: TransactionOnNetwork) -> None: self.simulation_response = simulate_response - def to_dictionary(self) -> Dict[str, Any]: - dictionary: Dict[str, Any] = OrderedDict() - dictionary["execution"] = self.simulation_response.to_dictionary() + def to_dictionary(self) -> dict[str, Any]: + dictionary: dict[str, Any] = OrderedDict() + dictionary["execution"] = self.simulation_response.raw return dictionary -class Simulator(): +class Simulator: def __init__(self, proxy: INetworkProvider) -> None: self.proxy = proxy - def run(self, transaction: ITransaction) -> Simulation: + def run(self, transaction: Transaction) -> Simulation: simulation_response = self.proxy.simulate_transaction(transaction) return Simulation(simulation_response) diff --git a/multiversx_sdk_cli/tests/conftest.py b/multiversx_sdk_cli/tests/conftest.py index abff81f5..4a606102 100644 --- a/multiversx_sdk_cli/tests/conftest.py +++ b/multiversx_sdk_cli/tests/conftest.py @@ -3,7 +3,7 @@ # function executed right after test items collected but before test run def pytest_collection_modifyitems(config, items): - if not config.getoption('-m'): + if not config.getoption("-m"): skip_me = pytest.mark.skip(reason="require_localnet will only run if explicitly set to with -m") for item in items: if "require_localnet" in item.keywords: diff --git a/multiversx_sdk_cli/tests/local_verify_server.py b/multiversx_sdk_cli/tests/local_verify_server.py index dbc989ab..aa9cb1a3 100644 --- a/multiversx_sdk_cli/tests/local_verify_server.py +++ b/multiversx_sdk_cli/tests/local_verify_server.py @@ -1,7 +1,7 @@ import json from http.server import BaseHTTPRequestHandler, HTTPServer -HOST = 'localhost' +HOST = "localhost" PORT = 7777 @@ -12,18 +12,18 @@ def do_POST(self): self.end_headers() if self.path == "/initialise": - response = {'token': 7890} - self.wfile.write(bytes(json.dumps(response), 'utf-8')) + response = {"token": 7890} + self.wfile.write(bytes(json.dumps(response), "utf-8")) if self.path == "/verify": - response = {'status': 'sent to verification'} - self.wfile.write(bytes(json.dumps(response), 'utf-8')) + response = {"status": "sent to verification"} + self.wfile.write(bytes(json.dumps(response), "utf-8")) server = HTTPServer((HOST, PORT), HTTP) -print('Server running...') +print("Server running...") server.serve_forever() server.server_close() -print('Server closed!') +print("Server closed!") diff --git a/multiversx_sdk_cli/tests/test_accounts.py b/multiversx_sdk_cli/tests/test_accounts.py index e89e20ec..8272763f 100644 --- a/multiversx_sdk_cli/tests/test_accounts.py +++ b/multiversx_sdk_cli/tests/test_accounts.py @@ -1,32 +1,31 @@ from pathlib import Path import pytest - -from multiversx_sdk_cli.accounts import Account +from multiversx_sdk import Account def test_load_account_from_keystore_without_kind(): alice_json = Path(__file__).parent / "testdata" / "alice.json" - account = Account(key_file=str(alice_json), password="password") - assert account.address.bech32() == "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th" + account = Account.new_from_keystore(file_path=alice_json, password="password") + assert account.address.to_bech32() == "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th" with pytest.raises(Exception): - _ = Account(key_file=str(alice_json), password="wrong_password") + _ = Account.new_from_keystore(file_path=alice_json, password="wrong_password") def test_load_account_from_keystore_with_kind_secret_key(): keystore_path = Path(__file__).parent / "testdata" / "aliceWithKindSecretKey.json" - account = Account(key_file=str(keystore_path), password="password") - assert account.address.bech32() == "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th" + account = Account.new_from_keystore(file_path=keystore_path, password="password") + assert account.address.to_bech32() == "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th" with pytest.raises(Exception): - _ = Account(key_file=str(keystore_path), password="wrong_password") + _ = Account.new_from_keystore(file_path=keystore_path, password="wrong_password") def test_load_account_from_keystore_with_kind_mnemonic(): keystore_path = Path(__file__).parent / "testdata" / "withDummyMnemonic.json" - account = Account(key_file=str(keystore_path), password="password") - assert account.address.bech32() == "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th" + account = Account.new_from_keystore(file_path=keystore_path, password="password") + assert account.address.to_bech32() == "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th" with pytest.raises(Exception): - _ = Account(key_file=str(keystore_path), password="wrong_password") + _ = Account.new_from_keystore(file_path=keystore_path, password="wrong_password") diff --git a/multiversx_sdk_cli/tests/test_cli_contracts.py b/multiversx_sdk_cli/tests/test_cli_contracts.py index 12e4c523..bb822e70 100644 --- a/multiversx_sdk_cli/tests/test_cli_contracts.py +++ b/multiversx_sdk_cli/tests/test_cli_contracts.py @@ -2,85 +2,15 @@ from pathlib import Path from typing import Any -import pytest - from multiversx_sdk_cli.cli import main parent = Path(__file__).parent +(parent / "testdata-out").mkdir(exist_ok=True) -def test_contract_new(): - main([ - "contract", - "new", - "--template", - "adder", - "--path", - f"{parent}/testdata-out/SANDBOX" - ]) - assert Path.is_dir(parent / "testdata-out" / "SANDBOX" / "adder") - - -def test_contract_new_with_bad_code(): - # we change the contract code so the build would fail so we can catch the error - main([ - "contract", - "new", - "--template", - "adder", - "--path", - f"{parent}/testdata-out/SANDBOX", - "--name", - "adder-bad-src" - ]) - - assert Path.is_dir(parent / "testdata-out" / "SANDBOX" / "adder-bad-src") - replace_variable_with_unknown_variable_for_adder() - - -def replace_variable_with_unknown_variable_for_adder(): - # this is done in order to replace the value added in the adder contract with a unknown variable - with open(parent / "testdata-out" / "SANDBOX" / "adder-bad-src" / "src" / "adder_bad_src.rs", "r") as f: - contract_lines = f.readlines() - - for index, line in reversed(list(enumerate(contract_lines))): - if "value" in line: - contract_lines[index] = line.replace("value", "unknown_variable") - break - - with open(parent / "testdata-out" / "SANDBOX" / "adder-bad-src" / "src" / "adder_bad_src.rs", "w") as f: - f.writelines(contract_lines) - - -@pytest.mark.skip_on_windows def test_contract_build(): - main([ - "contract", - "build", - "--path", - f"{parent}/testdata-out/SANDBOX/adder" - ]) - - assert Path.is_file(parent / "testdata-out" / "SANDBOX" / "adder" / "output" / "adder.wasm") - - -@pytest.mark.skip_on_windows -def test_bad_contract_build(capsys: Any): - ERROR = "Build error: error code = 101, see output." - - main([ - "contract", - "build", - "--path", - f"{parent}/testdata-out/SANDBOX/adder-bad-src" - ]) - - out, _ = capsys.readouterr() - - if ERROR in out: - assert True - else: - assert False + return_code = main(["contract", "build"]) + assert not return_code def test_contract_deploy(): @@ -173,60 +103,107 @@ def test_contract_transfer_and_execute(capsys: Any): first_token = "NFT-123456-02" second_token = "ESDT-987654" - main([ - "contract", "call", contract_address, - "--pem", f"{parent}/testdata/testUser.pem", - "--chain", "D", - "--nonce", "7", - "--gas-limit", "5000000", - "--function", "add", - "--arguments", "5", - "--token-transfers", first_token, "1" - ]) + main( + [ + "contract", + "call", + contract_address, + "--pem", + f"{parent}/testdata/testUser.pem", + "--chain", + "D", + "--nonce", + "7", + "--gas-limit", + "5000000", + "--function", + "add", + "--arguments", + "5", + "--token-transfers", + first_token, + "1", + ] + ) data = get_transaction_data(capsys) - assert data == "ESDTNFTTransfer@4e46542d313233343536@02@01@0000000000000000050067a1fd3a146a1d9a6df532d0f6b004e2b29153ba69e1@616464@05" + assert ( + data + == "ESDTNFTTransfer@4e46542d313233343536@02@01@0000000000000000050067a1fd3a146a1d9a6df532d0f6b004e2b29153ba69e1@616464@05" + ) # Clear the captured content capsys.readouterr() - main([ - "contract", "call", contract_address, - "--pem", f"{parent}/testdata/testUser.pem", - "--chain", "D", - "--nonce", "77", - "--gas-limit", "5000000", - "--function", "add", - "--arguments", "5", - "--token-transfers", first_token, "1", second_token, "100" - ]) + main( + [ + "contract", + "call", + contract_address, + "--pem", + f"{parent}/testdata/testUser.pem", + "--chain", + "D", + "--nonce", + "77", + "--gas-limit", + "5000000", + "--function", + "add", + "--arguments", + "5", + "--token-transfers", + first_token, + "1", + second_token, + "100", + ] + ) data = get_transaction_data(capsys) - assert data == "MultiESDTNFTTransfer@0000000000000000050067a1fd3a146a1d9a6df532d0f6b004e2b29153ba69e1@02@4e46542d313233343536@02@01@455344542d393837363534@@64@616464@05" + assert ( + data + == "MultiESDTNFTTransfer@0000000000000000050067a1fd3a146a1d9a6df532d0f6b004e2b29153ba69e1@02@4e46542d313233343536@02@01@455344542d393837363534@@64@616464@05" + ) def test_contract_flow(capsys: Any): alice = f"{parent}/testdata/alice.pem" adder = f"{parent}/testdata/adder.wasm" - main([ - "contract", "deploy", - "--bytecode", adder, - "--pem", alice, - "--recall-nonce", - "--gas-limit", "5000000", - "--proxy", "https://testnet-api.multiversx.com", - "--arguments", "0", - "--send", "--wait-result" - ]) + main( + [ + "contract", + "deploy", + "--bytecode", + adder, + "--pem", + alice, + "--recall-nonce", + "--gas-limit", + "5000000", + "--proxy", + "https://testnet-api.multiversx.com", + "--arguments", + "0", + "--send", + "--wait-result", + ] + ) contract = get_contract_address(capsys) # Clear the captured content capsys.readouterr() - main([ - "contract", "query", contract, - "--function", "getSum", - "--proxy", "https://testnet-api.multiversx.com" - ]) + main( + [ + "contract", + "query", + contract, + "--function", + "getSum", + "--proxy", + "https://testnet-api.multiversx.com", + ] + ) response = get_query_response(capsys) assert len(response) == 1 assert response == [""] @@ -234,25 +211,41 @@ def test_contract_flow(capsys: Any): # Clear the captured content capsys.readouterr() - main([ - "contract", "call", contract, - "--pem", alice, - "--function", "add", - "--recall-nonce", - "--gas-limit", "5000000", - "--proxy", "https://testnet-api.multiversx.com", - "--arguments", "7", - "--send", "--wait-result" - ]) + main( + [ + "contract", + "call", + contract, + "--pem", + alice, + "--function", + "add", + "--recall-nonce", + "--gas-limit", + "5000000", + "--proxy", + "https://testnet-api.multiversx.com", + "--arguments", + "7", + "--send", + "--wait-result", + ] + ) # Clear the captured content capsys.readouterr() - main([ - "contract", "query", contract, - "--function", "getSum", - "--proxy", "https://testnet-api.multiversx.com" - ]) + main( + [ + "contract", + "query", + contract, + "--function", + "getSum", + "--proxy", + "https://testnet-api.multiversx.com", + ] + ) response = get_query_response(capsys) assert len(response) == 1 assert response == ["07"] @@ -260,31 +253,49 @@ def test_contract_flow(capsys: Any): # Clear the captured content capsys.readouterr() - main([ - "contract", "upgrade", contract, - "--bytecode", adder, - "--pem", alice, - "--recall-nonce", - "--gas-limit", "5000000", - "--proxy", "https://testnet-api.multiversx.com", - "--arguments", "0", - "--send", "--wait-result" - ]) + main( + [ + "contract", + "upgrade", + contract, + "--bytecode", + adder, + "--pem", + alice, + "--recall-nonce", + "--gas-limit", + "5000000", + "--proxy", + "https://testnet-api.multiversx.com", + "--arguments", + "0", + "--send", + "--wait-result", + ] + ) def test_contract_deploy_without_required_arguments(): alice = f"{parent}/testdata/alice.pem" adder = f"{parent}/testdata/adder.wasm" - return_code = main([ - "contract", "deploy", - "--bytecode", adder, - "--pem", alice, - "--recall-nonce", - "--gas-limit", "5000000", - "--arguments", "0", - "--send", "--wait-result" - ]) + return_code = main( + [ + "contract", + "deploy", + "--bytecode", + adder, + "--pem", + alice, + "--recall-nonce", + "--gas-limit", + "5000000", + "--arguments", + "0", + "--send", + "--wait-result", + ] + ) assert return_code @@ -292,26 +303,44 @@ def test_contract_commands_argument_parameter(): alice = f"{parent}/testdata/alice.pem" adder = f"{parent}/testdata/adder.wasm" - return_code = main([ - "contract", "deploy", - "--bytecode", adder, - "--pem", alice, - "--nonce", "7", - "--chain", "D", - "--gas-limit", "5000000", - "--arguments", "foobar", - ]) + return_code = main( + [ + "contract", + "deploy", + "--bytecode", + adder, + "--pem", + alice, + "--nonce", + "7", + "--chain", + "D", + "--gas-limit", + "5000000", + "--arguments", + "foobar", + ] + ) assert return_code - return_code = main([ - "contract", "deploy", - "--bytecode", adder, - "--pem", alice, - "--nonce", "7", - "--chain", "D", - "--gas-limit", "5000000", - "--arguments", "str:foobar", - ]) + return_code = main( + [ + "contract", + "deploy", + "--bytecode", + adder, + "--pem", + alice, + "--nonce", + "7", + "--chain", + "D", + "--gas-limit", + "5000000", + "--arguments", + "str:foobar", + ] + ) assert not return_code @@ -320,77 +349,130 @@ def test_contract_deploy_with_abi(capsys: Any): multisig = f"{parent}/testdata/multisig.wasm" multisig_abi = f"{parent}/testdata/multisig.abi.json" - return_code = main([ - "contract", "deploy", - "--bytecode", multisig, - "--pem", alice, - "--chain", "T", - "--nonce", "7", - "--gas-limit", "5000000", - "--arguments", "2", "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th", "erd1cqqxak4wun7508e0yj9ng843r6hv4mzd0hhpjpsejkpn9wa9yq8sj7u2u5" - ]) + return_code = main( + [ + "contract", + "deploy", + "--bytecode", + multisig, + "--pem", + alice, + "--chain", + "T", + "--nonce", + "7", + "--gas-limit", + "5000000", + "--arguments", + "2", + "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th", + "erd1cqqxak4wun7508e0yj9ng843r6hv4mzd0hhpjpsejkpn9wa9yq8sj7u2u5", + ] + ) assert not return_code deploy_without_abi_data = get_transaction_data(capsys) # Clear the captured content capsys.readouterr() - return_code = main([ - "contract", "deploy", - "--bytecode", multisig, - "--abi", multisig_abi, - "--pem", alice, - "--chain", "T", - "--nonce", "7", - "--gas-limit", "5000000", - "--arguments-file", f"{parent}/testdata/deploy_multisig_args.json" - ]) + return_code = main( + [ + "contract", + "deploy", + "--bytecode", + multisig, + "--abi", + multisig_abi, + "--pem", + alice, + "--chain", + "T", + "--nonce", + "7", + "--gas-limit", + "5000000", + "--arguments-file", + f"{parent}/testdata/deploy_multisig_args.json", + ] + ) assert not return_code deploy_with_abi_data = get_transaction_data(capsys) assert deploy_without_abi_data == deploy_with_abi_data - assert deploy_without_abi_data.endswith("@02@0139472eff6886771a982f3083da5d421f24c29181e63888228dc81ca60d69e1@c0006edaaee4fd479f2f248b341eb11eaecaec4d7dee190619958332bba5200f") - assert deploy_with_abi_data.endswith("@02@0139472eff6886771a982f3083da5d421f24c29181e63888228dc81ca60d69e1@c0006edaaee4fd479f2f248b341eb11eaecaec4d7dee190619958332bba5200f") + assert deploy_without_abi_data.endswith( + "@02@0139472eff6886771a982f3083da5d421f24c29181e63888228dc81ca60d69e1@c0006edaaee4fd479f2f248b341eb11eaecaec4d7dee190619958332bba5200f" + ) + assert deploy_with_abi_data.endswith( + "@02@0139472eff6886771a982f3083da5d421f24c29181e63888228dc81ca60d69e1@c0006edaaee4fd479f2f248b341eb11eaecaec4d7dee190619958332bba5200f" + ) def test_contract_call_with_abi(capsys: Any): alice = f"{parent}/testdata/alice.pem" multisig_abi = f"{parent}/testdata/multisig.abi.json" - return_code = main([ - "contract", "call", "erd1qqqqqqqqqqqqqpgqpg2tnjelx3s3dhkksqhjavprtgmt9xt4d8ss9al8m4", - "--pem", alice, - "--chain", "T", - "--nonce", "7", - "--gas-limit", "5000000", - "--function", "proposeBatch", - "--abi", multisig_abi, - "--arguments-file", f"{parent}/testdata/call_multisig_propose_batch_args.json" - ]) + return_code = main( + [ + "contract", + "call", + "erd1qqqqqqqqqqqqqpgqpg2tnjelx3s3dhkksqhjavprtgmt9xt4d8ss9al8m4", + "--pem", + alice, + "--chain", + "T", + "--nonce", + "7", + "--gas-limit", + "5000000", + "--function", + "proposeBatch", + "--abi", + multisig_abi, + "--arguments-file", + f"{parent}/testdata/call_multisig_propose_batch_args.json", + ] + ) assert not return_code data = get_transaction_data(capsys) - assert data == "proposeBatch@0500000000000000000500ed8e25a94efa837aae0e593112cfbb01b448755069e1000000080de0b6b3a7640000010000000000e4e1c000000003616464000000010000000107" + assert ( + data + == "proposeBatch@0500000000000000000500ed8e25a94efa837aae0e593112cfbb01b448755069e1000000080de0b6b3a7640000010000000000e4e1c000000003616464000000010000000107" + ) def test_contract_upgrade_with_abi(capsys: Any): alice = f"{parent}/testdata/alice.pem" multisig_abi = f"{parent}/testdata/multisig.abi.json" - return_code = main([ - "contract", "call", "erd1qqqqqqqqqqqqqpgqpg2tnjelx3s3dhkksqhjavprtgmt9xt4d8ss9al8m4", - "--pem", alice, - "--chain", "T", - "--nonce", "7", - "--gas-limit", "5000000", - "--function", "proposeSCUpgradeFromSource", - "--abi", multisig_abi, - "--arguments-file", f"{parent}/testdata/upgrade_multisig_args.json" - ]) + return_code = main( + [ + "contract", + "call", + "erd1qqqqqqqqqqqqqpgqpg2tnjelx3s3dhkksqhjavprtgmt9xt4d8ss9al8m4", + "--pem", + alice, + "--chain", + "T", + "--nonce", + "7", + "--gas-limit", + "5000000", + "--function", + "proposeSCUpgradeFromSource", + "--abi", + multisig_abi, + "--arguments-file", + f"{parent}/testdata/upgrade_multisig_args.json", + ] + ) assert not return_code data = get_transaction_data(capsys) - assert data == "proposeSCUpgradeFromSource@000000000000000005000a14b9cb3f346116ded6802f2eb0235a36b2997569e1@@00000000000000000500ed8e25a94efa837aae0e593112cfbb01b448755069e1@0500@" + assert ( + data + == "proposeSCUpgradeFromSource@000000000000000005000a14b9cb3f346116ded6802f2eb0235a36b2997569e1@@00000000000000000500ed8e25a94efa837aae0e593112cfbb01b448755069e1@0500@" + ) def test_contract_query(capsys: Any): @@ -398,44 +480,70 @@ def test_contract_query(capsys: Any): adder = f"{parent}/testdata/adder.wasm" adder_abi = f"{parent}/testdata/adder.abi.json" - return_code = main([ - "contract", "deploy", - "--bytecode", adder, - "--abi", adder_abi, - "--pem", alice, - "--recall-nonce", - "--gas-limit", "5000000", - "--proxy", "https://testnet-api.multiversx.com", - "--arguments", "0", - "--send", "--wait-result" - ]) + return_code = main( + [ + "contract", + "deploy", + "--bytecode", + adder, + "--abi", + adder_abi, + "--pem", + alice, + "--recall-nonce", + "--gas-limit", + "5000000", + "--proxy", + "https://testnet-api.multiversx.com", + "--arguments", + "0", + "--send", + "--wait-result", + ] + ) assert not return_code contract = get_contract_address(capsys) # Clear the captured content capsys.readouterr() - return_code = main([ - "contract", "call", contract, - "--pem", alice, - "--function", "add", - "--recall-nonce", - "--gas-limit", "5000000", - "--proxy", "https://testnet-api.multiversx.com", - "--arguments", "14", - "--send", "--wait-result" - ]) + return_code = main( + [ + "contract", + "call", + contract, + "--pem", + alice, + "--function", + "add", + "--recall-nonce", + "--gas-limit", + "5000000", + "--proxy", + "https://testnet-api.multiversx.com", + "--arguments", + "14", + "--send", + "--wait-result", + ] + ) assert not return_code # Clear the captured content capsys.readouterr() # invalid, without abi - return_code = main([ - "contract", "query", contract, - "--function", "getSummm", - "--proxy", "https://testnet-api.multiversx.com" - ]) + return_code = main( + [ + "contract", + "query", + contract, + "--function", + "getSummm", + "--proxy", + "https://testnet-api.multiversx.com", + ] + ) assert return_code output = _read_stdout(capsys) if "invalid function (not found)" in output: @@ -447,12 +555,19 @@ def test_contract_query(capsys: Any): capsys.readouterr() # invalid, with abi, error is thrown by sdk-py - return_code = main([ - "contract", "query", contract, - "--function", "getSummm", - "--abi", adder_abi, - "--proxy", "https://testnet-api.multiversx.com" - ]) + return_code = main( + [ + "contract", + "query", + contract, + "--function", + "getSummm", + "--abi", + adder_abi, + "--proxy", + "https://testnet-api.multiversx.com", + ] + ) assert return_code output = _read_stdout(capsys) if "endpoint 'getSummm' not found" in output: @@ -464,11 +579,17 @@ def test_contract_query(capsys: Any): capsys.readouterr() # query contract, without abi - return_code = main([ - "contract", "query", contract, - "--function", "getSum", - "--proxy", "https://testnet-api.multiversx.com" - ]) + return_code = main( + [ + "contract", + "query", + contract, + "--function", + "getSum", + "--proxy", + "https://testnet-api.multiversx.com", + ] + ) assert not return_code response = get_query_response(capsys) assert response == ["0e"] @@ -477,19 +598,27 @@ def test_contract_query(capsys: Any): capsys.readouterr() # query contract, without abi - return_code = main([ - "contract", "query", contract, - "--function", "getSum", - "--abi", adder_abi, - "--proxy", "https://testnet-api.multiversx.com" - ]) + return_code = main( + [ + "contract", + "query", + contract, + "--function", + "getSum", + "--abi", + adder_abi, + "--proxy", + "https://testnet-api.multiversx.com", + ] + ) assert not return_code response = get_query_response(capsys) assert response == [14] def _read_stdout(capsys: Any) -> str: - return capsys.readouterr().out.strip() + stdout: str = capsys.readouterr().out.strip() + return stdout def get_contract_address(capsys: Any): @@ -505,5 +634,5 @@ def get_query_response(capsys: Any): def get_transaction_data(capsys: Any) -> str: out = _read_stdout(capsys) - output = json.loads(out) + output: dict[str, str] = json.loads(out) return output["emittedTransactionData"] diff --git a/multiversx_sdk_cli/tests/test_cli_deps.py b/multiversx_sdk_cli/tests/test_cli_deps.py index 63162d71..8c131656 100644 --- a/multiversx_sdk_cli/tests/test_cli_deps.py +++ b/multiversx_sdk_cli/tests/test_cli_deps.py @@ -1,36 +1,8 @@ -import shutil -from pathlib import Path - import pytest from multiversx_sdk_cli.cli import main -def test_deps_install_rust(): - return_code = main(["deps", "install", "rust", "--overwrite"]) - assert return_code == 0 - - -def test_deps_check_rust(): - return_code = main(["deps", "check", "rust"]) - assert True if return_code == 0 else False - - which_rustc = shutil.which("rustc") - assert which_rustc and Path.is_file(Path(which_rustc)) - - which_cargo = shutil.which("cargo") - assert which_cargo and Path.is_file(Path(which_cargo)) - - which_sc_meta = shutil.which("sc-meta") - assert which_sc_meta and Path.is_file(Path(which_sc_meta)) - - which_wasm_opt = shutil.which("wasm-opt") - assert which_wasm_opt and Path.is_file(Path(which_wasm_opt)) - - which_twiggy = shutil.which("twiggy") - assert which_twiggy and Path.is_file(Path(which_twiggy)) - - def test_deps_install_testwallets(): return_code = main(["deps", "install", "testwallets"]) assert return_code == 0 diff --git a/multiversx_sdk_cli/tests/test_cli_dns.py b/multiversx_sdk_cli/tests/test_cli_dns.py index 3d2265a9..9241e393 100644 --- a/multiversx_sdk_cli/tests/test_cli_dns.py +++ b/multiversx_sdk_cli/tests/test_cli_dns.py @@ -1,21 +1,67 @@ +import json from pathlib import Path +from typing import Any from multiversx_sdk_cli.cli import main testdata_path = Path(__file__).parent / "testdata" -def test_prepare_relayed_dns_register_transaction(): +def test_prepare_relayed_dns_register_transaction(capsys: Any): alice = testdata_path / "alice.pem" + user = testdata_path / "testUser.pem" - return_code = main([ - "dns", "register", - "--pem", str(alice), - "--name", "alice.elrond", - "--nonce", "0", - "--gas-limit", "15000000", - "--chain", "T", - "--relay" - ]) - - assert False if return_code else True + return_code = main( + [ + "dns", + "register", + "--pem", + str(alice), + "--name", + "alice.elrond", + "--nonce", + "0", + "--gas-limit", + "15000000", + "--chain", + "T", + "--relayer", + "erd1cqqxak4wun7508e0yj9ng843r6hv4mzd0hhpjpsejkpn9wa9yq8sj7u2u5", + "--relayer-pem", + str(user), + ] + ) + assert not return_code + + output = get_output(capsys) + tx = output["emittedTransaction"] + data = output["emittedTransactionData"] + + assert tx["sender"] == "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th" + assert tx["receiver"] == "erd1qqqqqqqqqqqqqpgqf97pgqdy0tstwauxu09kszz020hp5kgqqzzsscqtww" + assert tx["value"] == "0" + assert tx["nonce"] == 0 + assert tx["gasLimit"] == 15000000 + assert tx["chainID"] == "T" + assert tx["version"] == 2 + assert tx["options"] == 0 + assert tx["relayer"] == "erd1cqqxak4wun7508e0yj9ng843r6hv4mzd0hhpjpsejkpn9wa9yq8sj7u2u5" + assert ( + tx["signature"] + == "25af35896b853ad13bf1adcc3e516f8d7532349b4b00e958e30a6b9c0b9c38cbe0ce712684aba91e3f91bf080f6ae89d8cdfd6d0c69701d3761346aa6c54ac0d" + ) + assert ( + tx["relayerSignature"] + == "e7b22c3f8e3cfa8f15038d3b59beabe3e4b2a0e40fdb40e57c39e762450ebe3cdf327bb66585c27e66480846b7487d5e78366959f6f09f10bb63e9b643c08f03" + ) + assert data == "register@616c6963652e656c726f6e64" + + +def get_output(capsys: Any): + tx = _read_stdout(capsys) + return json.loads(tx) + + +def _read_stdout(capsys: Any) -> str: + stdout: str = capsys.readouterr().out.strip() + return stdout diff --git a/multiversx_sdk_cli/tests/test_cli_shared.py b/multiversx_sdk_cli/tests/test_cli_shared.py deleted file mode 100644 index 4a1b1107..00000000 --- a/multiversx_sdk_cli/tests/test_cli_shared.py +++ /dev/null @@ -1,50 +0,0 @@ -from multiversx_sdk_cli.cli_shared import convert_args_object_to_args_list - - -class ContractBuildArgs: - def __init__(self) -> None: - self.verbose = False - self.path = "testadata-out/SANDBOX/myadder-rs" - self.no_wasm_opt = False - self.wasm_symbols = False - self.wasm_name = None - self.wasm_suffix = None - self.target_dir = None - self.wat = False - self.mir = False - self.llvm_ir = False - self.ignore = None - self.no_imports = False - self.no_abi_git_version = False - self.twiggy_top = False - self.twiggy_paths = False - self.twiggy_monos = False - self.twiggy_dominators = False - self.func = "should be a func object" - - -def test_args_obj_to_list(): - contract_build_args = ContractBuildArgs() - - args_list = convert_args_object_to_args_list(contract_build_args) - - assert len(args_list) == 2 - assert args_list[0] == "--path" - assert args_list[1] == contract_build_args.path - - contract_build_args.no_wasm_opt = True - args_list = convert_args_object_to_args_list(contract_build_args) - - assert len(args_list) == 3 - assert args_list[0] == "--path" - assert args_list[1] == contract_build_args.path - assert args_list[2] == "--no-wasm-opt" - - contract_build_args.ignore = "random_directory" # type: ignore - contract_build_args.no_imports = True - args_list = convert_args_object_to_args_list(contract_build_args) - - assert len(args_list) == 6 - assert args_list[3] == "--ignore" - assert args_list[4] == contract_build_args.ignore - assert args_list[5] == "--no-imports" diff --git a/multiversx_sdk_cli/tests/test_cli_staking_provider.py b/multiversx_sdk_cli/tests/test_cli_staking_provider.py index a657c0d1..7b8319e2 100644 --- a/multiversx_sdk_cli/tests/test_cli_staking_provider.py +++ b/multiversx_sdk_cli/tests/test_cli_staking_provider.py @@ -9,19 +9,28 @@ first_bls_key = "f8910e47cf9464777c912e6390758bb39715fffcb861b184017920e4a807b42553f2f21e7f3914b81bcf58b66a72ab16d97013ae1cff807cefc977ef8cbf116258534b9e46d19528042d16ef8374404a89b184e0a4ee18c77c49e454d04eae8d" second_bls_key = "1b4e60e6d100cdf234d3427494dac55fbac49856cadc86bcb13a01b9bb05a0d9143e86c186c948e7ae9e52427c9523102efe9019a2a9c06db02993f2e3e6756576ae5a3ec7c235d548bc79de1a6990e1120ae435cb48f7fc436c9f9098b92a0d" -validators_file = parent / "testdata" / "validators_file.json" +validators_file = parent / "testdata" / "validators_file.pem" def test_create_new_delegation_contract(capsys: Any): - main([ - "staking-provider", "create-new-delegation-contract", - "--pem", str(alice), - "--nonce", "7", "--estimate-gas", - "--value", "1250000000000000000000", - "--total-delegation-cap", "10000000000000000000000", - "--service-fee", "100", - "--chain", "T" - ]) + main( + [ + "staking-provider", + "create-new-delegation-contract", + "--pem", + str(alice), + "--nonce", + "7", + "--value", + "1250000000000000000000", + "--total-delegation-cap", + "10000000000000000000000", + "--service-fee", + "100", + "--chain", + "T", + ] + ) tx = get_transaction(capsys) data = tx["emittedTransactionData"] transaction = tx["emittedTransaction"] @@ -32,20 +41,33 @@ def test_create_new_delegation_contract(capsys: Any): assert transaction["chainID"] == "T" assert transaction["gasLimit"] == 60126500 assert transaction["value"] == "1250000000000000000000" - assert transaction["signature"] == "0a6d7249c671b1db00f1b8807770bb64eac51e2e2779e426f35439c6cb7b00dadd023392a061ba1b6ee35d235ac2c0ad87283413b1d5558d8526bc5712588702" + assert ( + transaction["signature"] + == "0a6d7249c671b1db00f1b8807770bb64eac51e2e2779e426f35439c6cb7b00dadd023392a061ba1b6ee35d235ac2c0ad87283413b1d5558d8526bc5712588702" + ) def test_create_new_delegation_contract_with_provided_gas_limit(capsys: Any): - main([ - "staking-provider", "create-new-delegation-contract", - "--pem", str(alice), - "--nonce", "7", "--estimate-gas", - "--value", "1250000000000000000000", - "--total-delegation-cap", "10000000000000000000000", - "--service-fee", "100", - "--chain", "T", - "--gas-limit", "60126501" - ]) + main( + [ + "staking-provider", + "create-new-delegation-contract", + "--pem", + str(alice), + "--nonce", + "7", + "--value", + "1250000000000000000000", + "--total-delegation-cap", + "10000000000000000000000", + "--service-fee", + "100", + "--chain", + "T", + "--gas-limit", + "60126501", + ] + ) tx = get_transaction(capsys) data = tx["emittedTransactionData"] transaction = tx["emittedTransaction"] @@ -56,183 +78,293 @@ def test_create_new_delegation_contract_with_provided_gas_limit(capsys: Any): assert transaction["chainID"] == "T" assert transaction["gasLimit"] == 60126501 assert transaction["value"] == "1250000000000000000000" - assert transaction["signature"] == "8e28aa5a11454d975a4841086397000de053784b04e68402da9abcf26adc8726d3ce32f415119e3aa38746f2e5524063a6176a0c8cd88c9e8e89f9626d045202" + assert ( + transaction["signature"] + == "8e28aa5a11454d975a4841086397000de053784b04e68402da9abcf26adc8726d3ce32f415119e3aa38746f2e5524063a6176a0c8cd88c9e8e89f9626d045202" + ) def test_add_nodes(capsys: Any): - validators_file = parent / "testdata" / "validators.json" - - main([ - "staking-provider", "add-nodes", - "--validators-file", str(validators_file), - "--delegation-contract", "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqthllllsy5r6rh", - "--pem", str(alice), - "--chain", "T", - "--nonce", "7", "--estimate-gas" - ]) + validators_file = parent / "testdata" / "validators.pem" + + main( + [ + "staking-provider", + "add-nodes", + "--validators-pem", + str(validators_file), + "--delegation-contract", + "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqthllllsy5r6rh", + "--pem", + str(alice), + "--chain", + "T", + "--nonce", + "7", + ] + ) tx = get_transaction(capsys) data = tx["emittedTransactionData"] transaction = tx["emittedTransaction"] - assert data == "addNodes@e7beaa95b3877f47348df4dd1cb578a4f7cabf7a20bfeefe5cdd263878ff132b765e04fef6f40c93512b666c47ed7719b8902f6c922c04247989b7137e837cc81a62e54712471c97a2ddab75aa9c2f58f813ed4c0fa722bde0ab718bff382208@307ef00b648eed52ce4e95f8155f2e65491addd49266254f7c32ff06622e335752a6a6ad2efeb2534ec0db6a431b4989@78689fd4b1e2e434d567fe01e61598a42717d83124308266bd09ccc15d2339dd318c019914b86ac29adbae5dd8a02d0307425e9bd85a296e94943708c72f8c670f0b7c50a890a5719088dbd9f1d062cad9acffa06df834106eebe1a4257ef00d@4c90003d4b535fe709b6583708ae276e29558c58d160ad6241c3063e590611a2e327f2b8299b4955179a85eab50b4587@7188b234a8bf834f2e6258012aa09a2ab93178ffab9c789480275f61fe02cd1b9a58ddc63b79a73abea9e2b7ac5cac0b0d4324eff50aca2f0ec946b9ae6797511fa3ce461b57e77129cba8ab3b51147695d4ce889cbe67905f6586b4e4f22491@1d6cf6b0a38fa5c10df6493eae0b14c5d119d9f7d3d3e790a047e4e7c09919f64f910971ceef380b49f70fc982d07d19" + assert ( + data + == "addNodes@e7beaa95b3877f47348df4dd1cb578a4f7cabf7a20bfeefe5cdd263878ff132b765e04fef6f40c93512b666c47ed7719b8902f6c922c04247989b7137e837cc81a62e54712471c97a2ddab75aa9c2f58f813ed4c0fa722bde0ab718bff382208@307ef00b648eed52ce4e95f8155f2e65491addd49266254f7c32ff06622e335752a6a6ad2efeb2534ec0db6a431b4989@78689fd4b1e2e434d567fe01e61598a42717d83124308266bd09ccc15d2339dd318c019914b86ac29adbae5dd8a02d0307425e9bd85a296e94943708c72f8c670f0b7c50a890a5719088dbd9f1d062cad9acffa06df834106eebe1a4257ef00d@4c90003d4b535fe709b6583708ae276e29558c58d160ad6241c3063e590611a2e327f2b8299b4955179a85eab50b4587@7188b234a8bf834f2e6258012aa09a2ab93178ffab9c789480275f61fe02cd1b9a58ddc63b79a73abea9e2b7ac5cac0b0d4324eff50aca2f0ec946b9ae6797511fa3ce461b57e77129cba8ab3b51147695d4ce889cbe67905f6586b4e4f22491@1d6cf6b0a38fa5c10df6493eae0b14c5d119d9f7d3d3e790a047e4e7c09919f64f910971ceef380b49f70fc982d07d19" + ) assert transaction["sender"] == "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th" assert transaction["receiver"] == "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqthllllsy5r6rh" assert transaction["gasLimit"] == 20367000 - assert transaction["signature"] == "a0a8ce8ee6c60bd44b3940e47f3598fc95b47d111cf3ce7752951c692f98ccc8e0a2eade3ac55fcc68c54f98c69a0fb7aa0c93a50e8a8bcd0206f84a95e6390a" + assert ( + transaction["signature"] + == "a0a8ce8ee6c60bd44b3940e47f3598fc95b47d111cf3ce7752951c692f98ccc8e0a2eade3ac55fcc68c54f98c69a0fb7aa0c93a50e8a8bcd0206f84a95e6390a" + ) def test_add_nodes_with_gas_limit(capsys: Any): - validators_file = parent / "testdata" / "validators.json" - - main([ - "staking-provider", "add-nodes", - "--validators-file", str(validators_file), - "--delegation-contract", "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqthllllsy5r6rh", - "--pem", str(alice), - "--chain", "T", - "--nonce", "7", - "--gas-limit", "20367001" - ]) + validators_file = parent / "testdata" / "validators.pem" + + main( + [ + "staking-provider", + "add-nodes", + "--validators-pem", + str(validators_file), + "--delegation-contract", + "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqthllllsy5r6rh", + "--pem", + str(alice), + "--chain", + "T", + "--nonce", + "7", + "--gas-limit", + "20367001", + ] + ) tx = get_transaction(capsys) data = tx["emittedTransactionData"] transaction = tx["emittedTransaction"] - assert data == "addNodes@e7beaa95b3877f47348df4dd1cb578a4f7cabf7a20bfeefe5cdd263878ff132b765e04fef6f40c93512b666c47ed7719b8902f6c922c04247989b7137e837cc81a62e54712471c97a2ddab75aa9c2f58f813ed4c0fa722bde0ab718bff382208@307ef00b648eed52ce4e95f8155f2e65491addd49266254f7c32ff06622e335752a6a6ad2efeb2534ec0db6a431b4989@78689fd4b1e2e434d567fe01e61598a42717d83124308266bd09ccc15d2339dd318c019914b86ac29adbae5dd8a02d0307425e9bd85a296e94943708c72f8c670f0b7c50a890a5719088dbd9f1d062cad9acffa06df834106eebe1a4257ef00d@4c90003d4b535fe709b6583708ae276e29558c58d160ad6241c3063e590611a2e327f2b8299b4955179a85eab50b4587@7188b234a8bf834f2e6258012aa09a2ab93178ffab9c789480275f61fe02cd1b9a58ddc63b79a73abea9e2b7ac5cac0b0d4324eff50aca2f0ec946b9ae6797511fa3ce461b57e77129cba8ab3b51147695d4ce889cbe67905f6586b4e4f22491@1d6cf6b0a38fa5c10df6493eae0b14c5d119d9f7d3d3e790a047e4e7c09919f64f910971ceef380b49f70fc982d07d19" + assert ( + data + == "addNodes@e7beaa95b3877f47348df4dd1cb578a4f7cabf7a20bfeefe5cdd263878ff132b765e04fef6f40c93512b666c47ed7719b8902f6c922c04247989b7137e837cc81a62e54712471c97a2ddab75aa9c2f58f813ed4c0fa722bde0ab718bff382208@307ef00b648eed52ce4e95f8155f2e65491addd49266254f7c32ff06622e335752a6a6ad2efeb2534ec0db6a431b4989@78689fd4b1e2e434d567fe01e61598a42717d83124308266bd09ccc15d2339dd318c019914b86ac29adbae5dd8a02d0307425e9bd85a296e94943708c72f8c670f0b7c50a890a5719088dbd9f1d062cad9acffa06df834106eebe1a4257ef00d@4c90003d4b535fe709b6583708ae276e29558c58d160ad6241c3063e590611a2e327f2b8299b4955179a85eab50b4587@7188b234a8bf834f2e6258012aa09a2ab93178ffab9c789480275f61fe02cd1b9a58ddc63b79a73abea9e2b7ac5cac0b0d4324eff50aca2f0ec946b9ae6797511fa3ce461b57e77129cba8ab3b51147695d4ce889cbe67905f6586b4e4f22491@1d6cf6b0a38fa5c10df6493eae0b14c5d119d9f7d3d3e790a047e4e7c09919f64f910971ceef380b49f70fc982d07d19" + ) assert transaction["sender"] == "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th" assert transaction["receiver"] == "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqthllllsy5r6rh" assert transaction["gasLimit"] == 20367001 - assert transaction["signature"] == "b4aa91a3c865cf82784f29a659c0884cb70948e0e3f0b74ec8fe5a3cce358e750c357f1a1f9378e855a4261fff10abd927d1e3535fd18e99c40a562e31d67406" + assert ( + transaction["signature"] + == "b4aa91a3c865cf82784f29a659c0884cb70948e0e3f0b74ec8fe5a3cce358e750c357f1a1f9378e855a4261fff10abd927d1e3535fd18e99c40a562e31d67406" + ) def test_remove_nodes_with_bls_keys(capsys: Any): - main([ - "staking-provider", "remove-nodes", - "--bls-keys", f"{first_bls_key},{second_bls_key}", - "--delegation-contract", "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqthllllsy5r6rh", - "--pem", str(alice), - "--chain", "T", - "--nonce", "7", "--estimate-gas" - ]) + main( + [ + "staking-provider", + "remove-nodes", + "--bls-keys", + f"{first_bls_key},{second_bls_key}", + "--delegation-contract", + "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqthllllsy5r6rh", + "--pem", + str(alice), + "--chain", + "T", + "--nonce", + "7", + ] + ) tx = get_transaction(capsys) data = tx["emittedTransactionData"] transaction = tx["emittedTransaction"] - assert data == "removeNodes@f8910e47cf9464777c912e6390758bb39715fffcb861b184017920e4a807b42553f2f21e7f3914b81bcf58b66a72ab16d97013ae1cff807cefc977ef8cbf116258534b9e46d19528042d16ef8374404a89b184e0a4ee18c77c49e454d04eae8d@1b4e60e6d100cdf234d3427494dac55fbac49856cadc86bcb13a01b9bb05a0d9143e86c186c948e7ae9e52427c9523102efe9019a2a9c06db02993f2e3e6756576ae5a3ec7c235d548bc79de1a6990e1120ae435cb48f7fc436c9f9098b92a0d" + assert ( + data + == "removeNodes@f8910e47cf9464777c912e6390758bb39715fffcb861b184017920e4a807b42553f2f21e7f3914b81bcf58b66a72ab16d97013ae1cff807cefc977ef8cbf116258534b9e46d19528042d16ef8374404a89b184e0a4ee18c77c49e454d04eae8d@1b4e60e6d100cdf234d3427494dac55fbac49856cadc86bcb13a01b9bb05a0d9143e86c186c948e7ae9e52427c9523102efe9019a2a9c06db02993f2e3e6756576ae5a3ec7c235d548bc79de1a6990e1120ae435cb48f7fc436c9f9098b92a0d" + ) assert transaction["sender"] == "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th" assert transaction["receiver"] == "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqthllllsy5r6rh" assert transaction["gasLimit"] == 13645500 def test_remove_nodes_with_validators_file(capsys: Any): - main([ - "staking-provider", "remove-nodes", - "--validators-file", str(validators_file), - "--delegation-contract", "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqthllllsy5r6rh", - "--pem", str(alice), - "--chain", "T", - "--nonce", "7", "--estimate-gas" - ]) + main( + [ + "staking-provider", + "remove-nodes", + "--validators-pem", + str(validators_file), + "--delegation-contract", + "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqthllllsy5r6rh", + "--pem", + str(alice), + "--chain", + "T", + "--nonce", + "7", + ] + ) tx = get_transaction(capsys) data = tx["emittedTransactionData"] transaction = tx["emittedTransaction"] - assert data == "removeNodes@f8910e47cf9464777c912e6390758bb39715fffcb861b184017920e4a807b42553f2f21e7f3914b81bcf58b66a72ab16d97013ae1cff807cefc977ef8cbf116258534b9e46d19528042d16ef8374404a89b184e0a4ee18c77c49e454d04eae8d@1b4e60e6d100cdf234d3427494dac55fbac49856cadc86bcb13a01b9bb05a0d9143e86c186c948e7ae9e52427c9523102efe9019a2a9c06db02993f2e3e6756576ae5a3ec7c235d548bc79de1a6990e1120ae435cb48f7fc436c9f9098b92a0d" + assert ( + data + == "removeNodes@f8910e47cf9464777c912e6390758bb39715fffcb861b184017920e4a807b42553f2f21e7f3914b81bcf58b66a72ab16d97013ae1cff807cefc977ef8cbf116258534b9e46d19528042d16ef8374404a89b184e0a4ee18c77c49e454d04eae8d@1b4e60e6d100cdf234d3427494dac55fbac49856cadc86bcb13a01b9bb05a0d9143e86c186c948e7ae9e52427c9523102efe9019a2a9c06db02993f2e3e6756576ae5a3ec7c235d548bc79de1a6990e1120ae435cb48f7fc436c9f9098b92a0d" + ) assert transaction["sender"] == "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th" assert transaction["receiver"] == "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqthllllsy5r6rh" assert transaction["gasLimit"] == 13645500 def test_stake_nodes_with_bls_keys(capsys: Any): - main([ - "staking-provider", "stake-nodes", - "--validators-file", str(validators_file), - "--delegation-contract", "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqthllllsy5r6rh", - "--pem", str(alice), - "--chain", "T", - "--nonce", "7", "--estimate-gas" - ]) + main( + [ + "staking-provider", + "stake-nodes", + "--validators-pem", + str(validators_file), + "--delegation-contract", + "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqthllllsy5r6rh", + "--pem", + str(alice), + "--chain", + "T", + "--nonce", + "7", + ] + ) tx = get_transaction(capsys) data = tx["emittedTransactionData"] transaction = tx["emittedTransaction"] - assert data == "stakeNodes@f8910e47cf9464777c912e6390758bb39715fffcb861b184017920e4a807b42553f2f21e7f3914b81bcf58b66a72ab16d97013ae1cff807cefc977ef8cbf116258534b9e46d19528042d16ef8374404a89b184e0a4ee18c77c49e454d04eae8d@1b4e60e6d100cdf234d3427494dac55fbac49856cadc86bcb13a01b9bb05a0d9143e86c186c948e7ae9e52427c9523102efe9019a2a9c06db02993f2e3e6756576ae5a3ec7c235d548bc79de1a6990e1120ae435cb48f7fc436c9f9098b92a0d" + assert ( + data + == "stakeNodes@f8910e47cf9464777c912e6390758bb39715fffcb861b184017920e4a807b42553f2f21e7f3914b81bcf58b66a72ab16d97013ae1cff807cefc977ef8cbf116258534b9e46d19528042d16ef8374404a89b184e0a4ee18c77c49e454d04eae8d@1b4e60e6d100cdf234d3427494dac55fbac49856cadc86bcb13a01b9bb05a0d9143e86c186c948e7ae9e52427c9523102efe9019a2a9c06db02993f2e3e6756576ae5a3ec7c235d548bc79de1a6990e1120ae435cb48f7fc436c9f9098b92a0d" + ) assert transaction["sender"] == "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th" assert transaction["receiver"] == "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqthllllsy5r6rh" assert transaction["gasLimit"] == 18644000 def test_stake_nodes_with_validators_file(capsys: Any): - main([ - "staking-provider", "stake-nodes", - "--bls-keys", f"{first_bls_key},{second_bls_key}", - "--delegation-contract", "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqthllllsy5r6rh", - "--pem", str(alice), - "--chain", "T", - "--nonce", "7", "--estimate-gas" - ]) + main( + [ + "staking-provider", + "stake-nodes", + "--bls-keys", + f"{first_bls_key},{second_bls_key}", + "--delegation-contract", + "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqthllllsy5r6rh", + "--pem", + str(alice), + "--chain", + "T", + "--nonce", + "7", + ] + ) tx = get_transaction(capsys) data = tx["emittedTransactionData"] transaction = tx["emittedTransaction"] - assert data == "stakeNodes@f8910e47cf9464777c912e6390758bb39715fffcb861b184017920e4a807b42553f2f21e7f3914b81bcf58b66a72ab16d97013ae1cff807cefc977ef8cbf116258534b9e46d19528042d16ef8374404a89b184e0a4ee18c77c49e454d04eae8d@1b4e60e6d100cdf234d3427494dac55fbac49856cadc86bcb13a01b9bb05a0d9143e86c186c948e7ae9e52427c9523102efe9019a2a9c06db02993f2e3e6756576ae5a3ec7c235d548bc79de1a6990e1120ae435cb48f7fc436c9f9098b92a0d" + assert ( + data + == "stakeNodes@f8910e47cf9464777c912e6390758bb39715fffcb861b184017920e4a807b42553f2f21e7f3914b81bcf58b66a72ab16d97013ae1cff807cefc977ef8cbf116258534b9e46d19528042d16ef8374404a89b184e0a4ee18c77c49e454d04eae8d@1b4e60e6d100cdf234d3427494dac55fbac49856cadc86bcb13a01b9bb05a0d9143e86c186c948e7ae9e52427c9523102efe9019a2a9c06db02993f2e3e6756576ae5a3ec7c235d548bc79de1a6990e1120ae435cb48f7fc436c9f9098b92a0d" + ) assert transaction["sender"] == "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th" assert transaction["receiver"] == "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqthllllsy5r6rh" assert transaction["gasLimit"] == 18644000 def test_unbond_nodes(capsys: Any): - main([ - "staking-provider", "unbond-nodes", - "--bls-keys", f"{first_bls_key},{second_bls_key}", - "--delegation-contract", "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqthllllsy5r6rh", - "--pem", str(alice), - "--chain", "T", - "--nonce", "7", "--estimate-gas" - ]) + main( + [ + "staking-provider", + "unbond-nodes", + "--bls-keys", + f"{first_bls_key},{second_bls_key}", + "--delegation-contract", + "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqthllllsy5r6rh", + "--pem", + str(alice), + "--chain", + "T", + "--nonce", + "7", + ] + ) tx = get_transaction(capsys) data = tx["emittedTransactionData"] transaction = tx["emittedTransaction"] - assert data == "unBondNodes@f8910e47cf9464777c912e6390758bb39715fffcb861b184017920e4a807b42553f2f21e7f3914b81bcf58b66a72ab16d97013ae1cff807cefc977ef8cbf116258534b9e46d19528042d16ef8374404a89b184e0a4ee18c77c49e454d04eae8d@1b4e60e6d100cdf234d3427494dac55fbac49856cadc86bcb13a01b9bb05a0d9143e86c186c948e7ae9e52427c9523102efe9019a2a9c06db02993f2e3e6756576ae5a3ec7c235d548bc79de1a6990e1120ae435cb48f7fc436c9f9098b92a0d" + assert ( + data + == "unBondNodes@f8910e47cf9464777c912e6390758bb39715fffcb861b184017920e4a807b42553f2f21e7f3914b81bcf58b66a72ab16d97013ae1cff807cefc977ef8cbf116258534b9e46d19528042d16ef8374404a89b184e0a4ee18c77c49e454d04eae8d@1b4e60e6d100cdf234d3427494dac55fbac49856cadc86bcb13a01b9bb05a0d9143e86c186c948e7ae9e52427c9523102efe9019a2a9c06db02993f2e3e6756576ae5a3ec7c235d548bc79de1a6990e1120ae435cb48f7fc436c9f9098b92a0d" + ) assert transaction["sender"] == "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th" assert transaction["receiver"] == "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqthllllsy5r6rh" assert transaction["gasLimit"] == 18645500 def test_unstake_nodes(capsys: Any): - main([ - "staking-provider", "unstake-nodes", - "--bls-keys", f"{first_bls_key},{second_bls_key}", - "--delegation-contract", "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqthllllsy5r6rh", - "--pem", str(alice), - "--chain", "T", - "--nonce", "7", "--estimate-gas" - ]) + main( + [ + "staking-provider", + "unstake-nodes", + "--bls-keys", + f"{first_bls_key},{second_bls_key}", + "--delegation-contract", + "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqthllllsy5r6rh", + "--pem", + str(alice), + "--chain", + "T", + "--nonce", + "7", + ] + ) tx = get_transaction(capsys) data = tx["emittedTransactionData"] transaction = tx["emittedTransaction"] - assert data == "unStakeNodes@f8910e47cf9464777c912e6390758bb39715fffcb861b184017920e4a807b42553f2f21e7f3914b81bcf58b66a72ab16d97013ae1cff807cefc977ef8cbf116258534b9e46d19528042d16ef8374404a89b184e0a4ee18c77c49e454d04eae8d@1b4e60e6d100cdf234d3427494dac55fbac49856cadc86bcb13a01b9bb05a0d9143e86c186c948e7ae9e52427c9523102efe9019a2a9c06db02993f2e3e6756576ae5a3ec7c235d548bc79de1a6990e1120ae435cb48f7fc436c9f9098b92a0d" + assert ( + data + == "unStakeNodes@f8910e47cf9464777c912e6390758bb39715fffcb861b184017920e4a807b42553f2f21e7f3914b81bcf58b66a72ab16d97013ae1cff807cefc977ef8cbf116258534b9e46d19528042d16ef8374404a89b184e0a4ee18c77c49e454d04eae8d@1b4e60e6d100cdf234d3427494dac55fbac49856cadc86bcb13a01b9bb05a0d9143e86c186c948e7ae9e52427c9523102efe9019a2a9c06db02993f2e3e6756576ae5a3ec7c235d548bc79de1a6990e1120ae435cb48f7fc436c9f9098b92a0d" + ) assert transaction["sender"] == "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th" assert transaction["receiver"] == "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqthllllsy5r6rh" assert transaction["gasLimit"] == 18647000 def test_unjail_nodes(capsys: Any): - main([ - "staking-provider", "unjail-nodes", - "--bls-keys", f"{first_bls_key},{second_bls_key}", - "--delegation-contract", "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqthllllsy5r6rh", - "--value", "5000000000000000000", - "--pem", str(alice), - "--chain", "T", - "--nonce", "7", "--estimate-gas" - ]) + main( + [ + "staking-provider", + "unjail-nodes", + "--bls-keys", + f"{first_bls_key},{second_bls_key}", + "--delegation-contract", + "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqthllllsy5r6rh", + "--value", + "5000000000000000000", + "--pem", + str(alice), + "--chain", + "T", + "--nonce", + "7", + ] + ) tx = get_transaction(capsys) data = tx["emittedTransactionData"] transaction = tx["emittedTransaction"] - assert data == "unJailNodes@f8910e47cf9464777c912e6390758bb39715fffcb861b184017920e4a807b42553f2f21e7f3914b81bcf58b66a72ab16d97013ae1cff807cefc977ef8cbf116258534b9e46d19528042d16ef8374404a89b184e0a4ee18c77c49e454d04eae8d@1b4e60e6d100cdf234d3427494dac55fbac49856cadc86bcb13a01b9bb05a0d9143e86c186c948e7ae9e52427c9523102efe9019a2a9c06db02993f2e3e6756576ae5a3ec7c235d548bc79de1a6990e1120ae435cb48f7fc436c9f9098b92a0d" + assert ( + data + == "unJailNodes@f8910e47cf9464777c912e6390758bb39715fffcb861b184017920e4a807b42553f2f21e7f3914b81bcf58b66a72ab16d97013ae1cff807cefc977ef8cbf116258534b9e46d19528042d16ef8374404a89b184e0a4ee18c77c49e454d04eae8d@1b4e60e6d100cdf234d3427494dac55fbac49856cadc86bcb13a01b9bb05a0d9143e86c186c948e7ae9e52427c9523102efe9019a2a9c06db02993f2e3e6756576ae5a3ec7c235d548bc79de1a6990e1120ae435cb48f7fc436c9f9098b92a0d" + ) assert transaction["sender"] == "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th" assert transaction["receiver"] == "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqthllllsy5r6rh" assert transaction["gasLimit"] == 13645500 @@ -240,14 +372,22 @@ def test_unjail_nodes(capsys: Any): def test_change_service_fee(capsys: Any): - main([ - "staking-provider", "change-service-fee", - "--service-fee", "100", - "--delegation-contract", "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqthllllsy5r6rh", - "--pem", str(alice), - "--chain", "T", - "--nonce", "7", "--estimate-gas" - ]) + main( + [ + "staking-provider", + "change-service-fee", + "--service-fee", + "100", + "--delegation-contract", + "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqthllllsy5r6rh", + "--pem", + str(alice), + "--chain", + "T", + "--nonce", + "7", + ] + ) tx = get_transaction(capsys) data = tx["emittedTransactionData"] transaction = tx["emittedTransaction"] @@ -259,14 +399,22 @@ def test_change_service_fee(capsys: Any): def test_modify_delegation_cap(capsys: Any): - main([ - "staking-provider", "modify-delegation-cap", - "--delegation-cap", "10000000000000000000000", - "--delegation-contract", "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqthllllsy5r6rh", - "--pem", str(alice), - "--chain", "T", - "--nonce", "7", "--estimate-gas" - ]) + main( + [ + "staking-provider", + "modify-delegation-cap", + "--delegation-cap", + "10000000000000000000000", + "--delegation-contract", + "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqthllllsy5r6rh", + "--pem", + str(alice), + "--chain", + "T", + "--nonce", + "7", + ] + ) tx = get_transaction(capsys) data = tx["emittedTransactionData"] transaction = tx["emittedTransaction"] @@ -278,14 +426,21 @@ def test_modify_delegation_cap(capsys: Any): def test_automatic_activation(capsys: Any): - main([ - "staking-provider", "automatic-activation", - "--set", - "--delegation-contract", "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqthllllsy5r6rh", - "--pem", str(alice), - "--nonce", "7", "--estimate-gas", - "--chain", "T" - ]) + main( + [ + "staking-provider", + "automatic-activation", + "--set", + "--delegation-contract", + "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqthllllsy5r6rh", + "--pem", + str(alice), + "--nonce", + "7", + "--chain", + "T", + ] + ) tx = get_transaction(capsys) data = tx["emittedTransactionData"] transaction = tx["emittedTransaction"] @@ -298,14 +453,21 @@ def test_automatic_activation(capsys: Any): # Clear the captured content capsys.readouterr() - main([ - "staking-provider", "automatic-activation", - "--unset", - "--delegation-contract", "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqthllllsy5r6rh", - "--pem", str(alice), - "--nonce", "7", "--estimate-gas", - "--chain", "T" - ]) + main( + [ + "staking-provider", + "automatic-activation", + "--unset", + "--delegation-contract", + "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqthllllsy5r6rh", + "--pem", + str(alice), + "--nonce", + "7", + "--chain", + "T", + ] + ) tx = get_transaction(capsys) data = tx["emittedTransactionData"] transaction = tx["emittedTransaction"] @@ -317,14 +479,21 @@ def test_automatic_activation(capsys: Any): def test_redelegate_cap(capsys: Any): - main([ - "staking-provider", "redelegate-cap", - "--set", - "--delegation-contract", "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqthllllsy5r6rh", - "--pem", str(alice), - "--nonce", "7", "--estimate-gas", - "--chain", "T" - ]) + main( + [ + "staking-provider", + "redelegate-cap", + "--set", + "--delegation-contract", + "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqthllllsy5r6rh", + "--pem", + str(alice), + "--nonce", + "7", + "--chain", + "T", + ] + ) tx = get_transaction(capsys) data = tx["emittedTransactionData"] transaction = tx["emittedTransaction"] @@ -337,14 +506,21 @@ def test_redelegate_cap(capsys: Any): # Clear the captured content capsys.readouterr() - main([ - "staking-provider", "redelegate-cap", - "--unset", - "--delegation-contract", "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqthllllsy5r6rh", - "--pem", str(alice), - "--nonce", "7", "--estimate-gas", - "--chain", "T" - ]) + main( + [ + "staking-provider", + "redelegate-cap", + "--unset", + "--delegation-contract", + "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqthllllsy5r6rh", + "--pem", + str(alice), + "--nonce", + "7", + "--chain", + "T", + ] + ) tx = get_transaction(capsys) data = tx["emittedTransactionData"] transaction = tx["emittedTransaction"] @@ -356,16 +532,26 @@ def test_redelegate_cap(capsys: Any): def test_set_metadata(capsys: Any): - main([ - "staking-provider", "set-metadata", - "--name", "Test", - "--website", "www.test.com", - "--identifier", "TEST", - "--delegation-contract", "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqthllllsy5r6rh", - "--pem", str(alice), - "--nonce", "7", "--estimate-gas", - "--chain", "T" - ]) + main( + [ + "staking-provider", + "set-metadata", + "--name", + "Test", + "--website", + "www.test.com", + "--identifier", + "TEST", + "--delegation-contract", + "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqthllllsy5r6rh", + "--pem", + str(alice), + "--nonce", + "7", + "--chain", + "T", + ] + ) tx = get_transaction(capsys) data = tx["emittedTransactionData"] transaction = tx["emittedTransaction"] @@ -377,14 +563,22 @@ def test_set_metadata(capsys: Any): def test_create_delegation_contract_from_validator(capsys: Any): - main([ - "staking-provider", "make-delegation-contract-from-validator", - "--max-cap", "0", - "--fee", "3745", - "--pem", str(alice), - "--nonce", "7", "--estimate-gas", - "--chain", "T" - ]) + main( + [ + "staking-provider", + "make-delegation-contract-from-validator", + "--max-cap", + "0", + "--fee", + "3745", + "--pem", + str(alice), + "--nonce", + "7", + "--chain", + "T", + ] + ) tx = get_transaction(capsys) data = tx["emittedTransactionData"] transaction = tx["emittedTransaction"] @@ -396,14 +590,22 @@ def test_create_delegation_contract_from_validator(capsys: Any): def test_delegate(capsys: Any): - main([ - "staking-provider", "delegate", - "--delegation-contract", "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqthllllsy5r6rh", - "--value", "1000000000000000000", - "--pem", str(alice), - "--nonce", "7", "--estimate-gas", - "--chain", "T" - ]) + main( + [ + "staking-provider", + "delegate", + "--delegation-contract", + "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqthllllsy5r6rh", + "--value", + "1000000000000000000", + "--pem", + str(alice), + "--nonce", + "7", + "--chain", + "T", + ] + ) tx = get_transaction(capsys) data = tx["emittedTransactionData"] transaction = tx["emittedTransaction"] @@ -415,13 +617,20 @@ def test_delegate(capsys: Any): def test_claim_rewards(capsys: Any): - main([ - "staking-provider", "claim-rewards", - "--delegation-contract", "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqthllllsy5r6rh", - "--pem", str(alice), - "--nonce", "7", "--estimate-gas", - "--chain", "T" - ]) + main( + [ + "staking-provider", + "claim-rewards", + "--delegation-contract", + "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqthllllsy5r6rh", + "--pem", + str(alice), + "--nonce", + "7", + "--chain", + "T", + ] + ) tx = get_transaction(capsys) data = tx["emittedTransactionData"] transaction = tx["emittedTransaction"] @@ -433,13 +642,20 @@ def test_claim_rewards(capsys: Any): def test_redelegate_rewards(capsys: Any): - main([ - "staking-provider", "redelegate-rewards", - "--delegation-contract", "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqthllllsy5r6rh", - "--pem", str(alice), - "--nonce", "7", "--estimate-gas", - "--chain", "T" - ]) + main( + [ + "staking-provider", + "redelegate-rewards", + "--delegation-contract", + "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqthllllsy5r6rh", + "--pem", + str(alice), + "--nonce", + "7", + "--chain", + "T", + ] + ) tx = get_transaction(capsys) data = tx["emittedTransactionData"] transaction = tx["emittedTransaction"] @@ -451,14 +667,22 @@ def test_redelegate_rewards(capsys: Any): def test_undelegate(capsys: Any): - main([ - "staking-provider", "undelegate", - "--delegation-contract", "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqthllllsy5r6rh", - "--value", "1000000000000000000", - "--pem", str(alice), - "--nonce", "7", "--estimate-gas", - "--chain", "T" - ]) + main( + [ + "staking-provider", + "undelegate", + "--delegation-contract", + "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqthllllsy5r6rh", + "--value", + "1000000000000000000", + "--pem", + str(alice), + "--nonce", + "7", + "--chain", + "T", + ] + ) tx = get_transaction(capsys) data = tx["emittedTransactionData"] transaction = tx["emittedTransaction"] @@ -470,13 +694,20 @@ def test_undelegate(capsys: Any): def test_withdraw(capsys: Any): - main([ - "staking-provider", "withdraw", - "--delegation-contract", "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqthllllsy5r6rh", - "--pem", str(alice), - "--nonce", "7", "--estimate-gas", - "--chain", "T" - ]) + main( + [ + "staking-provider", + "withdraw", + "--delegation-contract", + "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqthllllsy5r6rh", + "--pem", + str(alice), + "--nonce", + "7", + "--chain", + "T", + ] + ) tx = get_transaction(capsys) data = tx["emittedTransactionData"] transaction = tx["emittedTransaction"] @@ -488,7 +719,8 @@ def test_withdraw(capsys: Any): def _read_stdout(capsys: Any) -> str: - return capsys.readouterr().out.strip() + stdout: str = capsys.readouterr().out.strip() + return stdout def get_transaction(capsys: Any): diff --git a/multiversx_sdk_cli/tests/test_cli_transactions.py b/multiversx_sdk_cli/tests/test_cli_transactions.py index b7bcd4a3..3f964ab7 100644 --- a/multiversx_sdk_cli/tests/test_cli_transactions.py +++ b/multiversx_sdk_cli/tests/test_cli_transactions.py @@ -8,114 +8,160 @@ testdata_out = Path(__file__).parent / "testdata-out" -def test_relayed_v1_transaction(capsys: Any): - multi_user_pem = testdata_path / "multiple_addresses.pem" - address_index = 1 - - return_code = main([ - "tx", "new", - "--pem", str(multi_user_pem), - "--pem-index", str(address_index), - "--receiver", "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqzllls8a5w6u", - "--nonce", "198", - "--gas-limit", "60000000", - "--data", "getContractConfig", - "--version", "1", - "--chain", "T", - "--relay" - ]) - assert return_code == 0 - - relayed_tx = _read_stdout(capsys) - assert relayed_tx == "relayedTx@7b226e6f6e6365223a3139382c2273656e646572223a2267456e574f65576d6d413063306a6b71764d354241707a61644b46574e534f69417643575163776d4750673d222c227265636569766572223a22414141414141414141414141415141414141414141414141414141414141414141414141414141432f2f383d222c2276616c7565223a302c226761735072696365223a313030303030303030302c226761734c696d6974223a36303030303030302c2264617461223a225a3256305132397564484a68593352446232356d6157633d222c227369676e6174757265223a2239682b6e6742584f5536776674315464437368534d4b3454446a5a32794f74686336564c576e3478724d5a706248427738677a6c6659596d362b766b505258303764634a562b4745635462616a7049692b5a5a5942773d3d222c22636861696e4944223a2256413d3d222c2276657273696f6e223a317d" - - def test_create_tx_and_sign_by_hash(capsys: Any): - return_code = main([ - "tx", "new", - "--pem", str(testdata_path / "alice.pem"), - "--receiver", "erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx", - "--nonce", "89", - "--gas-limit", "50000", - "--version", "2", - "--options", "1", - "--chain", "integration tests chain ID", - ]) + return_code = main( + [ + "tx", + "new", + "--pem", + str(testdata_path / "alice.pem"), + "--receiver", + "erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx", + "--nonce", + "89", + "--gas-limit", + "50000", + "--version", + "2", + "--options", + "1", + "--chain", + "integration tests chain ID", + ] + ) assert return_code == 0 tx = _read_stdout(capsys) tx_json = json.loads(tx) signature = tx_json["emittedTransaction"]["signature"] - assert signature == "f0c81f2393b1ec5972c813f817bae8daa00ade91c6f75ea604ab6a4d2797aca4378d783023ff98f1a02717fe4f24240cdfba0b674ee9abb18042203d713bc70a" + assert ( + signature + == "f0c81f2393b1ec5972c813f817bae8daa00ade91c6f75ea604ab6a4d2797aca4378d783023ff98f1a02717fe4f24240cdfba0b674ee9abb18042203d713bc70a" + ) def test_create_move_balance_transaction(capsys: Any): - return_code = main([ - "tx", "new", - "--pem", str(testdata_path / "alice.pem"), - "--receiver", "erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx", - "--nonce", "215", - "--gas-limit", "500000", - "--value", "1000000000000", - "--data", "hello", - "--version", "2", - "--options", "0", - "--chain", "T", - ]) + return_code = main( + [ + "tx", + "new", + "--pem", + str(testdata_path / "alice.pem"), + "--receiver", + "erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx", + "--nonce", + "215", + "--gas-limit", + "500000", + "--value", + "1000000000000", + "--data", + "hello", + "--version", + "2", + "--options", + "0", + "--chain", + "T", + ] + ) assert return_code == 0 tx = _read_stdout(capsys) tx_json = json.loads(tx) signature = tx_json["emittedTransaction"]["signature"] - assert signature == "e88d846800bab1751e222c4461a310a3882312ef6d75fd8b861a2f3b572837b58f146ff9d60d16e617f53358d6cfa87cbcc65ad624c77003779d474059264901" + assert ( + signature + == "e88d846800bab1751e222c4461a310a3882312ef6d75fd8b861a2f3b572837b58f146ff9d60d16e617f53358d6cfa87cbcc65ad624c77003779d474059264901" + ) def test_create_multi_transfer_transaction(capsys: Any): - return_code = main([ - "tx", "new", - "--pem", str(testdata_path / "alice.pem"), - "--receiver", "erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx", - "--nonce", "212", - "--gas-limit", "5000000", - "--token-transfers", "SSSSS-941b91-01", "1", "TEST-738c3d", "1200000000", - "--version", "2", - "--options", "0", - "--chain", "T", - ]) + return_code = main( + [ + "tx", + "new", + "--pem", + str(testdata_path / "alice.pem"), + "--receiver", + "erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx", + "--nonce", + "212", + "--gas-limit", + "5000000", + "--token-transfers", + "SSSSS-941b91-01", + "1", + "TEST-738c3d", + "1200000000", + "--version", + "2", + "--options", + "0", + "--chain", + "T", + ] + ) assert return_code == 0 tx = _read_stdout(capsys) tx_json = json.loads(tx) signature = tx_json["emittedTransaction"]["signature"] - assert signature == "575b029d52ff5ffbfb7bab2f04052de88a6f7d022a6ad368459b8af9acaed3717d3f95db09f460649a8f405800838bc2c432496bd03c9039ea166bd32b84660e" + assert ( + signature + == "575b029d52ff5ffbfb7bab2f04052de88a6f7d022a6ad368459b8af9acaed3717d3f95db09f460649a8f405800838bc2c432496bd03c9039ea166bd32b84660e" + ) def test_create_multi_transfer_transaction_with_single_egld_transfer(capsys: Any): - return_code = main([ - "tx", "new", - "--pem", str(testdata_path / "alice.pem"), - "--receiver", "erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx", - "--nonce", "7", - "--gas-limit", "1300000", - "--token-transfers", "EGLD-000000", "1000000000000000000", - "--chain", "T", - ]) + return_code = main( + [ + "tx", + "new", + "--pem", + str(testdata_path / "alice.pem"), + "--receiver", + "erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx", + "--nonce", + "7", + "--gas-limit", + "1300000", + "--token-transfers", + "EGLD-000000", + "1000000000000000000", + "--chain", + "T", + ] + ) assert return_code == 0 tx = _read_stdout(capsys) tx_json = json.loads(tx) data = tx_json["emittedTransactionData"] - assert data == "MultiESDTNFTTransfer@8049d639e5a6980d1cd2392abcce41029cda74a1563523a202f09641cc2618f8@01@45474c442d303030303030@@0de0b6b3a7640000" + assert ( + data + == "MultiESDTNFTTransfer@8049d639e5a6980d1cd2392abcce41029cda74a1563523a202f09641cc2618f8@01@45474c442d303030303030@@0de0b6b3a7640000" + ) def test_relayed_v3_without_relayer_wallet(capsys: Any): - return_code = main([ - "tx", "new", - "--pem", str(testdata_path / "alice.pem"), - "--receiver", "erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx", - "--nonce", "7", - "--gas-limit", "1300000", - "--value", "1000000000000000000", - "--chain", "T", - "--relayer", "erd1cqqxak4wun7508e0yj9ng843r6hv4mzd0hhpjpsejkpn9wa9yq8sj7u2u5" - ]) + return_code = main( + [ + "tx", + "new", + "--pem", + str(testdata_path / "alice.pem"), + "--receiver", + "erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx", + "--nonce", + "7", + "--gas-limit", + "1300000", + "--value", + "1000000000000000000", + "--chain", + "T", + "--relayer", + "erd1cqqxak4wun7508e0yj9ng843r6hv4mzd0hhpjpsejkpn9wa9yq8sj7u2u5", + ] + ) assert return_code == 0 tx = _read_stdout(capsys) tx_json = json.loads(tx)["emittedTransaction"] @@ -127,17 +173,28 @@ def test_relayed_v3_without_relayer_wallet(capsys: Any): def test_relayed_v3_incorrect_relayer(): - return_code = main([ - "tx", "new", - "--pem", str(testdata_path / "alice.pem"), - "--receiver", "erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx", - "--nonce", "7", - "--gas-limit", "1300000", - "--value", "1000000000000000000", - "--chain", "T", - "--relayer", "erd1cqqxak4wun7508e0yj9ng843r6hv4mzd0hhpjpsejkpn9wa9yq8sj7u2u5", - "--relayer-pem", str(testdata_path / "alice.pem") - ]) + return_code = main( + [ + "tx", + "new", + "--pem", + str(testdata_path / "alice.pem"), + "--receiver", + "erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx", + "--nonce", + "7", + "--gas-limit", + "1300000", + "--value", + "1000000000000000000", + "--chain", + "T", + "--relayer", + "erd1cqqxak4wun7508e0yj9ng843r6hv4mzd0hhpjpsejkpn9wa9yq8sj7u2u5", + "--relayer-pem", + str(testdata_path / "alice.pem"), + ] + ) assert return_code @@ -145,17 +202,28 @@ def test_create_relayed_v3_transaction(capsys: Any): # create relayed v3 tx and save signature and relayer signature # create the same tx, save to file # sign from file with relayer wallet and make sure signatures match - return_code = main([ - "tx", "new", - "--pem", str(testdata_path / "alice.pem"), - "--receiver", "erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx", - "--nonce", "7", - "--gas-limit", "1300000", - "--value", "1000000000000000000", - "--chain", "T", - "--relayer", "erd1cqqxak4wun7508e0yj9ng843r6hv4mzd0hhpjpsejkpn9wa9yq8sj7u2u5", - "--relayer-pem", str(testdata_path / "testUser.pem") - ]) + return_code = main( + [ + "tx", + "new", + "--pem", + str(testdata_path / "alice.pem"), + "--receiver", + "erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx", + "--nonce", + "7", + "--gas-limit", + "1300000", + "--value", + "1000000000000000000", + "--chain", + "T", + "--relayer", + "erd1cqqxak4wun7508e0yj9ng843r6hv4mzd0hhpjpsejkpn9wa9yq8sj7u2u5", + "--relayer-pem", + str(testdata_path / "testUser.pem"), + ] + ) assert return_code == 0 tx = _read_stdout(capsys) @@ -173,27 +241,43 @@ def test_create_relayed_v3_transaction(capsys: Any): capsys.readouterr() # save tx to file then load and sign tx by relayer - return_code = main([ - "tx", "new", - "--pem", str(testdata_path / "alice.pem"), - "--receiver", "erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx", - "--nonce", "7", - "--gas-limit", "1300000", - "--value", "1000000000000000000", - "--chain", "T", - "--relayer", "erd1cqqxak4wun7508e0yj9ng843r6hv4mzd0hhpjpsejkpn9wa9yq8sj7u2u5", - "--outfile", str(testdata_out / "relayed.json") - ]) + return_code = main( + [ + "tx", + "new", + "--pem", + str(testdata_path / "alice.pem"), + "--receiver", + "erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx", + "--nonce", + "7", + "--gas-limit", + "1300000", + "--value", + "1000000000000000000", + "--chain", + "T", + "--relayer", + "erd1cqqxak4wun7508e0yj9ng843r6hv4mzd0hhpjpsejkpn9wa9yq8sj7u2u5", + "--outfile", + str(testdata_out / "relayed.json"), + ] + ) assert return_code == 0 # Clear the captured content capsys.readouterr() - return_code = main([ - "tx", "relay", - "--relayer-pem", str(testdata_path / "testUser.pem"), - "--infile", str(testdata_out / "relayed.json") - ]) + return_code = main( + [ + "tx", + "relay", + "--relayer-pem", + str(testdata_path / "testUser.pem"), + "--infile", + str(testdata_out / "relayed.json"), + ] + ) assert return_code == 0 tx = _read_stdout(capsys) @@ -206,12 +290,109 @@ def test_create_relayed_v3_transaction(capsys: Any): def test_check_relayer_wallet_is_provided(): - return_code = main([ - "tx", "relay", - "--infile", str(testdata_out / "relayed.json") - ]) + return_code = main(["tx", "relay", "--infile", str(testdata_out / "relayed.json")]) assert return_code +def test_create_plain_transaction(capsys: Any): + return_code = main( + [ + "tx", + "new", + "--pem", + str(testdata_path / "alice.pem"), + "--receiver", + "erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx", + "--nonce", + "89", + "--gas-limit", + "50000", + "--chain", + "test", + ] + ) + assert return_code == 0 + + tx = _read_stdout(capsys) + tx_json = json.loads(tx)["emittedTransaction"] + + assert tx_json["sender"] == "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th" + assert tx_json["receiver"] == "erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx" + assert tx_json["chainID"] == "test" + assert tx_json["gasLimit"] == 50000 + assert tx_json["version"] == 2 + assert tx_json["options"] == 0 + assert ( + tx_json["signature"] + == "0cbb3cb4d6feaf9d2e6d17a529ddb5eeb0fd547af1dde65362beb6aaf54b78d90d429fa951b6ce7b52724be8da9737d7efaf13631816d034a2d7d1f5ae19510b" + ) + + +def test_sign_transaction(capsys: Any): + return_code = main( + [ + "tx", + "new", + "--pem", + str(testdata_path / "alice.pem"), + "--receiver", + "erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx", + "--nonce", + "89", + "--gas-limit", + "50000", + "--chain", + "test", + "--outfile", + str(testdata_out / "transaction.json"), + "--guardian", + "erd1cqqxak4wun7508e0yj9ng843r6hv4mzd0hhpjpsejkpn9wa9yq8sj7u2u5", + "--relayer", + "erd1ssmsc9022udc8pdw7wk3hxw74jr900xg28vwpz3z60gep66fasasl2nkm4", + ] + ) + assert return_code == 0 + assert (testdata_out / "transaction.json").is_file() + + return_code = main( + [ + "tx", + "sign", + "--infile", + str(testdata_out / "transaction.json"), + "--guardian-pem", + str(testdata_path / "testUser.pem"), + "--relayer-pem", + str(testdata_path / "testUser2.pem"), + ] + ) + assert return_code == 0 + + tx = _read_stdout(capsys) + tx_json = json.loads(tx)["emittedTransaction"] + + assert tx_json["sender"] == "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th" + assert tx_json["receiver"] == "erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx" + assert tx_json["chainID"] == "test" + assert tx_json["gasLimit"] == 50000 + assert tx_json["version"] == 2 + assert tx_json["options"] == 2 + assert tx_json["guardian"] == "erd1cqqxak4wun7508e0yj9ng843r6hv4mzd0hhpjpsejkpn9wa9yq8sj7u2u5" + assert tx_json["relayer"] == "erd1ssmsc9022udc8pdw7wk3hxw74jr900xg28vwpz3z60gep66fasasl2nkm4" + assert ( + tx_json["signature"] + == "96cd2db86f8f63e8fd40a3eb9a9a5a7b0e33a8c5879fe2feb6eba19657ddaf966f9c1d67bbb20c93dcea833a29531426414afe6b716142c9e4e82e7b87454b05" + ) + assert ( + tx_json["guardianSignature"] + == "3197aaa08cc5589c782928c3d123f38c03c5ddcecb666ce72d6751655c08fe2eb684592a165bc214044e549541d2dd3f2f209e7fab158e0adbf47f0af322200b" + ) + assert ( + tx_json["relayerSignature"] + == "01357f907ad0dd83d37a916d39aa4922d831279075f25a039bd9ebfc050eb67e72faeac07463bc1bc2c5e84711dd1ca9d0d21283664ba92c2036a2f01eae2c0d" + ) + + def _read_stdout(capsys: Any) -> str: - return capsys.readouterr().out.strip() + stdout: str = capsys.readouterr().out.strip() + return stdout diff --git a/multiversx_sdk_cli/tests/test_cli_validator_wallet.py b/multiversx_sdk_cli/tests/test_cli_validator_wallet.py new file mode 100644 index 00000000..a3f91771 --- /dev/null +++ b/multiversx_sdk_cli/tests/test_cli_validator_wallet.py @@ -0,0 +1,128 @@ +import json +from pathlib import Path +from typing import Any + +from multiversx_sdk import ValidatorPEM + +from multiversx_sdk_cli.cli import main + +testdata_path = Path(__file__).parent / "testdata" +testdata_out_path = Path(__file__).parent / "testdata-out" + + +def test_create_validator_wallet(): + outfile = testdata_out_path / "validator.pem" + outfile.unlink(missing_ok=True) + + return_code = main( + [ + "validator-wallet", + "new", + "--outfile", + str(outfile), + ] + ) + assert not return_code + assert outfile.is_file() + + wallet = ValidatorPEM.from_file(outfile) + assert wallet.label + assert wallet.secret_key + + +def test_validator_sign_and_verify_message(capsys: Any): + message = "test" + validator = testdata_path / "validator_01.pem" + + return_code = main( + [ + "validator-wallet", + "sign-message", + "--message", + message, + "--pem", + str(validator), + ] + ) + assert not return_code + + out = json.loads(_read_stdout(capsys)) + assert out == { + "address": "f8910e47cf9464777c912e6390758bb39715fffcb861b184017920e4a807b42553f2f21e7f3914b81bcf58b66a72ab16d97013ae1cff807cefc977ef8cbf116258534b9e46d19528042d16ef8374404a89b184e0a4ee18c77c49e454d04eae8d", + "message": "test", + "signature": "0x1c1dc0f6ef4f7c2a335cabd1da4bf3f333902c90ad9ecff0873854453419cd092f490238fb2f1bc6bf9f89337dea188f", + } + + # Clear the captured content + capsys.readouterr() + + return_code = main( + [ + "validator-wallet", + "verify-message-signature", + "--pubkey", + "f8910e47cf9464777c912e6390758bb39715fffcb861b184017920e4a807b42553f2f21e7f3914b81bcf58b66a72ab16d97013ae1cff807cefc977ef8cbf116258534b9e46d19528042d16ef8374404a89b184e0a4ee18c77c49e454d04eae8d", + "--message", + message, + "--signature", + "0x1c1dc0f6ef4f7c2a335cabd1da4bf3f333902c90ad9ecff0873854453419cd092f490238fb2f1bc6bf9f89337dea188f", + ] + ) + assert not return_code + out = _read_stdout(capsys) + + success = "SUCCESS:" + assert success in out.split() + + # repeate signature check with invalid signature + # Clear the captured content + capsys.readouterr() + + return_code = main( + [ + "validator-wallet", + "verify-message-signature", + "--pubkey", + "f8910e47cf9464777c912e6390758bb39715fffcb861b184017920e4a807b42553f2f21e7f3914b81bcf58b66a72ab16d97013ae1cff807cefc977ef8cbf116258534b9e46d19528042d16ef8374404a89b184e0a4ee18c77c49e454d04eae8d", + "--message", + message, + "--signature", + "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + ] + ) + assert not return_code + out = _read_stdout(capsys) + + success = "SUCCESS:" + assert success not in out.split() + + fail = "FAILED:" + assert fail in out.split() + + +def test_validator_wallet_convert_to_hex_secret_key(capsys: Any): + infile = testdata_path / "validator_01.pem" + + return_code = main( + [ + "validator-wallet", + "convert", + "--infile", + str(infile), + ] + ) + assert not return_code + + output = _read_stdout(capsys) + lines = output.splitlines() + + assert ( + lines[0] + == "Public key: f8910e47cf9464777c912e6390758bb39715fffcb861b184017920e4a807b42553f2f21e7f3914b81bcf58b66a72ab16d97013ae1cff807cefc977ef8cbf116258534b9e46d19528042d16ef8374404a89b184e0a4ee18c77c49e454d04eae8d" + ) + assert lines[1] == "Secret key: 7c19bf3a0c57cdd1fb08e4607cebaa3647d6b9261b4693f61e96e54b218d442a" + + +def _read_stdout(capsys: Any) -> str: + stdout: str = capsys.readouterr().out.strip() + return stdout diff --git a/multiversx_sdk_cli/tests/test_cli_validators.py b/multiversx_sdk_cli/tests/test_cli_validators.py index 8b7cc521..c482673d 100644 --- a/multiversx_sdk_cli/tests/test_cli_validators.py +++ b/multiversx_sdk_cli/tests/test_cli_validators.py @@ -1,198 +1,601 @@ -import pytest - +import json from pathlib import Path +from typing import Any from multiversx_sdk_cli.cli import main testdata_path = Path(__file__).parent / "testdata" testdata_out = Path(__file__).parent / "testdata-out" -proxy_url = "http://localhost:7950/network/config" alice_pem = testdata_path / "alice.pem" reward_address = "erd1k2s324ww2g0yj38qn2ch2jwctdy8mnfxep94q9arncc6xecg3xaq6mjse8" bls_key = "e7beaa95b3877f47348df4dd1cb578a4f7cabf7a20bfeefe5cdd263878ff132b765e04fef6f40c93512b666c47ed7719b8902f6c922c04247989b7137e837cc81a62e54712471c97a2ddab75aa9c2f58f813ed4c0fa722bde0ab718bff382208" +relayer = testdata_path / "testUser.pem" +guardian = testdata_path / "testUser2.pem" + + +def test_stake(capsys: Any): + validators_pem = testdata_path / "validators_file.pem" -@pytest.mark.require_localnet -def test_stake(): - validators_json = testdata_path / "validators_ci.json" - - # Stake with recall nonce - return_code = main([ - "validator", "stake", - "--pem", str(alice_pem), - "--value", "2500000000000000000000", - "--validators-file", str(validators_json), - "--reward-address", reward_address, - "--chain", "localnet", - "--proxy", "http://127.0.0.1:7950", - "--estimate-gas", "--recall-nonce" - ]) + return_code = main( + [ + "validator", + "stake", + "--pem", + str(alice_pem), + "--value", + "2500000000000000000000", + "--validators-pem", + str(validators_pem), + "--reward-address", + reward_address, + "--chain", + "localnet", + "--nonce=0", + ] + ) assert return_code == 0 - # Stake with provided nonce - return_code = main([ - "validator", "stake", - "--pem", str(alice_pem), - "--value", "2500000000000000000000", - "--validators-file", str(validators_json), - "--reward-address", reward_address, - "--chain", "localnet", - "--proxy", "http://127.0.0.1:7950", - "--estimate-gas", "--nonce=0" - ]) + output = get_output(capsys) + tx = output["emittedTransaction"] + data = output["emittedTransactionData"] + + assert tx["sender"] == "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th" + assert tx["receiver"] == "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqplllst77y4l" + assert tx["value"] == "2500000000000000000000" + assert tx["nonce"] == 0 + assert tx["gasLimit"] == 11029500 + assert tx["chainID"] == "localnet" + assert tx["version"] == 2 + assert tx["options"] == 0 + assert ( + tx["signature"] + == "e9dd1159bc55bde84872f0595c9e24b0b210deb8cb02a1df7d7b0e1277436d2043437ffeaff540e11200c00417cf5ce396b3154f968ad62ab4359fb05a493b0d" + ) + assert ( + data + == "stake@02@f8910e47cf9464777c912e6390758bb39715fffcb861b184017920e4a807b42553f2f21e7f3914b81bcf58b66a72ab16d97013ae1cff807cefc977ef8cbf116258534b9e46d19528042d16ef8374404a89b184e0a4ee18c77c49e454d04eae8d@1865870f7f69162a2dfefd33fe232a9ca984c6f22d1ee3f6a5b34a8eb8c9f7319001f29d5a2eed85c1500aca19fa4189@1b4e60e6d100cdf234d3427494dac55fbac49856cadc86bcb13a01b9bb05a0d9143e86c186c948e7ae9e52427c9523102efe9019a2a9c06db02993f2e3e6756576ae5a3ec7c235d548bc79de1a6990e1120ae435cb48f7fc436c9f9098b92a0d@12b309791213aac8ad9f34f0d912261e30f9ab060859e4d515e020a98b91d82a7cd334e4b504bb93d6b75347cccd6318@b2a11555ce521e4944e09ab17549d85b487dcd26c84b5017a39e31a3670889ba" + ) + + +def test_top_up(capsys: Any): + return_code = main( + [ + "validator", + "stake", + "--pem", + str(alice_pem), + "--value", + "2500000000000000000000", + "--top-up", + "--chain", + "localnet", + "--nonce=0", + "--reward-address", + "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th", + ] + ) assert return_code == 0 + output = get_output(capsys) + tx = output["emittedTransaction"] + data = output["emittedTransactionData"] -@pytest.mark.require_localnet -def test_stake_top_up(): - # Stake with topUp - return_code = main([ - "validator", "stake", "--top-up", - "--pem", str(alice_pem), - "--value", "2711000000000000000000", - "--chain", "localnet", - "--proxy", "http://127.0.0.1:7950", - "--estimate-gas", "--recall-nonce" - ]) + assert tx["sender"] == "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th" + assert tx["receiver"] == "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqplllst77y4l" + assert tx["value"] == "2500000000000000000000" + assert tx["nonce"] == 0 + assert tx["gasLimit"] == 5057500 + assert tx["chainID"] == "localnet" + assert tx["version"] == 2 + assert tx["options"] == 0 + assert ( + tx["signature"] + == "ca4ebd1b9c92b0479351e9f84b0394ce15f529f4a5c056ab2dd37b923d7af81cbb7bfc8fbbea571843a797e3382795c0419f69ab357acbd9611899d39e449107" + ) + assert data == "stake" + + +def test_stake_with_relayer_and_guardian(capsys: Any): + validators_pem = testdata_path / "validators_file.pem" + + return_code = main( + [ + "validator", + "stake", + "--pem", + str(alice_pem), + "--value", + "2500000000000000000000", + "--validators-pem", + str(validators_pem), + "--reward-address", + reward_address, + "--chain", + "localnet", + "--nonce=0", + "--options=2", + "--relayer", + "erd1cqqxak4wun7508e0yj9ng843r6hv4mzd0hhpjpsejkpn9wa9yq8sj7u2u5", + "--guardian", + "erd1ssmsc9022udc8pdw7wk3hxw74jr900xg28vwpz3z60gep66fasasl2nkm4", + "--guardian-pem", + str(guardian), + ] + ) assert return_code == 0 + output = get_output(capsys) + tx = output["emittedTransaction"] + data = output["emittedTransactionData"] -@pytest.mark.require_localnet -def test_unstake(): - # Unstake - return_code = main([ - "validator", "unstake", - "--pem", str(alice_pem), - "--nodes-public-key", bls_key, - "--chain", "localnet", - "--proxy", "http://127.0.0.1:7950", - "--estimate-gas", "--recall-nonce" - ]) + assert tx["sender"] == "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th" + assert tx["receiver"] == "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqplllst77y4l" + assert tx["value"] == "2500000000000000000000" + assert tx["nonce"] == 0 + assert tx["gasLimit"] == 11129500 + assert tx["chainID"] == "localnet" + assert tx["version"] == 2 + assert tx["options"] == 2 + assert tx["guardian"] == "erd1ssmsc9022udc8pdw7wk3hxw74jr900xg28vwpz3z60gep66fasasl2nkm4" + assert tx["relayer"] == "erd1cqqxak4wun7508e0yj9ng843r6hv4mzd0hhpjpsejkpn9wa9yq8sj7u2u5" + assert ( + tx["signature"] + == "87e80ebb4bb31837a268ec209353615e77ecf8799353dff9a7c6c78f049d7a3ebca715840db14428e98925fc3b8b9137dbbe35ea33affe2420fa3b2f1e701d06" + ) + assert ( + tx["guardianSignature"] + == "afb5e45bec458dae4b5ab80f8c6048a15faaeb2bcfaad90e6707de62bf3a931b440835ae8ec1dd767d52a5ed290583fe73b5302801ecfcd8e7c5908fdbf9b001" + ) + assert ( + data + == "stake@02@f8910e47cf9464777c912e6390758bb39715fffcb861b184017920e4a807b42553f2f21e7f3914b81bcf58b66a72ab16d97013ae1cff807cefc977ef8cbf116258534b9e46d19528042d16ef8374404a89b184e0a4ee18c77c49e454d04eae8d@1865870f7f69162a2dfefd33fe232a9ca984c6f22d1ee3f6a5b34a8eb8c9f7319001f29d5a2eed85c1500aca19fa4189@1b4e60e6d100cdf234d3427494dac55fbac49856cadc86bcb13a01b9bb05a0d9143e86c186c948e7ae9e52427c9523102efe9019a2a9c06db02993f2e3e6756576ae5a3ec7c235d548bc79de1a6990e1120ae435cb48f7fc436c9f9098b92a0d@12b309791213aac8ad9f34f0d912261e30f9ab060859e4d515e020a98b91d82a7cd334e4b504bb93d6b75347cccd6318@b2a11555ce521e4944e09ab17549d85b487dcd26c84b5017a39e31a3670889ba" + ) + + +def test_stake_top_up(capsys: Any): + return_code = main( + [ + "validator", + "stake", + "--top-up", + "--pem", + str(alice_pem), + "--value", + "2711000000000000000000", + "--chain", + "localnet", + "--nonce=7", + ] + ) assert return_code == 0 + output = get_output(capsys) + tx = output["emittedTransaction"] + data = output["emittedTransactionData"] + + assert tx["sender"] == "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th" + assert tx["receiver"] == "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqplllst77y4l" + assert tx["value"] == "2711000000000000000000" + assert tx["nonce"] == 7 + assert tx["gasLimit"] == 5057500 + assert tx["chainID"] == "localnet" + assert tx["version"] == 2 + assert tx["options"] == 0 + assert ( + tx["signature"] + == "3af38033e7661311e2e180b29930271ad54c05716e13bb33bafdb89e48250db525c056a51a25faf8e5bba31f6ddc03ed48f9e0e79b5f3da5ccc9f0b0a9a83207" + ) + assert data == "stake" -@pytest.mark.require_localnet -def test_unbond(): - # Unbond - return_code = main([ - "validator", "unbond", - "--pem", str(alice_pem), - "--nodes-public-key", bls_key, - "--chain", "localnet", - "--proxy", "http://127.0.0.1:7950", - "--estimate-gas", "--recall-nonce" - ]) + +def test_unstake(capsys: Any): + return_code = main( + [ + "validator", + "unstake", + "--pem", + str(alice_pem), + "--nodes-public-key", + bls_key, + "--chain", + "localnet", + "--nonce=7", + ] + ) assert return_code == 0 + output = get_output(capsys) + tx = output["emittedTransaction"] + data = output["emittedTransactionData"] + + assert tx["sender"] == "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th" + assert tx["receiver"] == "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqplllst77y4l" + assert tx["value"] == "0" + assert tx["nonce"] == 7 + assert tx["gasLimit"] == 5350000 + assert tx["chainID"] == "localnet" + assert tx["version"] == 2 + assert tx["options"] == 0 + assert ( + tx["signature"] + == "b387f3255670f17dbc4dd84bbed1f70631d2528f8f8ece7fb5c0fcc29a8b0b142583fe216c9de0086ebb69f9a50fc087ac4e5570fa2f61694df3b2cdb9389008" + ) + assert ( + data + == "unStake@e7beaa95b3877f47348df4dd1cb578a4f7cabf7a20bfeefe5cdd263878ff132b765e04fef6f40c93512b666c47ed7719b8902f6c922c04247989b7137e837cc81a62e54712471c97a2ddab75aa9c2f58f813ed4c0fa722bde0ab718bff382208" + ) + -@pytest.mark.require_localnet -def test_unjail(): - # Unjail - return_code = main([ - "validator", "unjail", - "--pem", str(alice_pem), - "--value", "2500000000000000000000", - "--nodes-public-key", bls_key, - "--chain", "localnet", - "--proxy", "http://127.0.0.1:7950", - "--estimate-gas", "--recall-nonce" - ]) +def test_unbond(capsys: Any): + return_code = main( + [ + "validator", + "unbond", + "--pem", + str(alice_pem), + "--nodes-public-key", + bls_key, + "--chain", + "localnet", + "--nonce=7", + ] + ) assert return_code == 0 + output = get_output(capsys) + tx = output["emittedTransaction"] + data = output["emittedTransactionData"] -@pytest.mark.require_localnet -def test_change_reward_address(): - # Change reward address - return_code = main([ - "validator", "change-reward-address", - "--pem", str(alice_pem), - "--reward-address", reward_address, - "--chain", "localnet", - "--proxy", "http://127.0.0.1:7950", - "--estimate-gas", "--recall-nonce" - ]) + assert tx["sender"] == "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th" + assert tx["receiver"] == "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqplllst77y4l" + assert tx["value"] == "0" + assert tx["nonce"] == 7 + assert tx["gasLimit"] == 5348500 + assert tx["chainID"] == "localnet" + assert tx["version"] == 2 + assert tx["options"] == 0 + assert ( + tx["signature"] + == "65c01d7c0ac26169d74d56612d1eab3a00c82b2f57af6b4aebbf39aa75e0f5e973d7daabb00bfad14d2f8bf63936ca9d69ef8fb76b8ac9c8ad0d2f808936930e" + ) + assert ( + data + == "unBond@e7beaa95b3877f47348df4dd1cb578a4f7cabf7a20bfeefe5cdd263878ff132b765e04fef6f40c93512b666c47ed7719b8902f6c922c04247989b7137e837cc81a62e54712471c97a2ddab75aa9c2f58f813ed4c0fa722bde0ab718bff382208" + ) + + +def test_unjail(capsys: Any): + return_code = main( + [ + "validator", + "unjail", + "--pem", + str(alice_pem), + "--value", + "2500000000000000000000", + "--nodes-public-key", + bls_key, + "--chain", + "localnet", + "--nonce=7", + ] + ) assert return_code == 0 + output = get_output(capsys) + tx = output["emittedTransaction"] + data = output["emittedTransactionData"] + + assert tx["sender"] == "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th" + assert tx["receiver"] == "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqplllst77y4l" + assert tx["value"] == "2500000000000000000000" + assert tx["nonce"] == 7 + assert tx["gasLimit"] == 5348500 + assert tx["chainID"] == "localnet" + assert tx["version"] == 2 + assert tx["options"] == 0 + assert ( + tx["signature"] + == "5e682ba1e16b62971d3c6e6943ec954eb29fc31d8794d21afdd9e2c4ea5ba209a59cba8bd39c2a6ab9f80f066d558679e5b22bfca561caedbfa6a7297ad97d00" + ) + assert ( + data + == "unJail@e7beaa95b3877f47348df4dd1cb578a4f7cabf7a20bfeefe5cdd263878ff132b765e04fef6f40c93512b666c47ed7719b8902f6c922c04247989b7137e837cc81a62e54712471c97a2ddab75aa9c2f58f813ed4c0fa722bde0ab718bff382208" + ) -@pytest.mark.require_localnet -def test_unstake_nodes(): - # Unstake Nodes - return_code = main([ - "validator", "unstake-nodes", - "--pem", str(alice_pem), - "--nodes-public-key", bls_key, - "--chain", "localnet", - "--proxy", "http://127.0.0.1:7950", - "--estimate-gas", "--recall-nonce" - ]) + +def test_change_reward_address(capsys: Any): + return_code = main( + [ + "validator", + "change-reward-address", + "--pem", + str(alice_pem), + "--reward-address", + reward_address, + "--chain", + "localnet", + "--nonce=7", + ] + ) assert return_code == 0 + output = get_output(capsys) + tx = output["emittedTransaction"] + data = output["emittedTransactionData"] + + assert tx["sender"] == "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th" + assert tx["receiver"] == "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqplllst77y4l" + assert tx["value"] == "0" + assert tx["nonce"] == 7 + assert tx["gasLimit"] == 5176000 + assert tx["chainID"] == "localnet" + assert tx["version"] == 2 + assert tx["options"] == 0 + assert ( + tx["signature"] + == "565e64ea28d48150e3798e20c0a0a0294374992ed05e5153e93339b3f86acdd25db308b474dd8315a544d6e375ed9e07b01c3475bdc7986fde79eae26bd5a40c" + ) + assert data == "changeRewardAddress@b2a11555ce521e4944e09ab17549d85b487dcd26c84b5017a39e31a3670889ba" + -@pytest.mark.require_localnet -def test_unstake_tokens(): - # Unstake Tokens - return_code = main([ - "validator", "unstake-tokens", - "--pem", str(alice_pem), - "--unstake-value", "11000000000000000000", - "--chain", "localnet", - "--proxy", "http://127.0.0.1:7950", - "--estimate-gas", "--recall-nonce" - ]) +def test_claim(capsys: Any): + return_code = main( + [ + "validator", + "claim", + "--pem", + str(alice_pem), + "--chain", + "localnet", + "--nonce=7", + ] + ) assert return_code == 0 + output = get_output(capsys) + tx = output["emittedTransaction"] + data = output["emittedTransactionData"] + + assert tx["sender"] == "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th" + assert tx["receiver"] == "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqplllst77y4l" + assert tx["value"] == "0" + assert tx["nonce"] == 7 + assert tx["gasLimit"] == 5057500 + assert tx["chainID"] == "localnet" + assert tx["version"] == 2 + assert tx["options"] == 0 + assert ( + tx["signature"] + == "be19a2c0bf5ce1da5f72a7451bff57725161bb67a8b85e397d44570585e6b7ff40858b0d30fd9a12f06c70655b1c417389f25059e0a95dc76e488185cea68208" + ) + assert data == "claim" + -@pytest.mark.require_localnet -def test_unbond_nodes(): - # Unbond nodes - return_code = main([ - "validator", "unbond-nodes", - "--pem", str(alice_pem), - "--nodes-public-keys", bls_key, - "--chain", "localnet", - "--proxy", "http://127.0.0.1:7950", - "--estimate-gas", "--recall-nonce" - ]) +def test_unstake_nodes(capsys: Any): + return_code = main( + [ + "validator", + "unstake-nodes", + "--pem", + str(alice_pem), + "--nodes-public-key", + bls_key, + "--chain", + "localnet", + "--nonce=7", + ] + ) assert return_code == 0 + output = get_output(capsys) + tx = output["emittedTransaction"] + data = output["emittedTransactionData"] -@pytest.mark.require_localnet -def test_unbond_tokens(): - # Unbond nodes - return_code = main([ - "validator", "unbond-tokens", - "--pem", str(alice_pem), - "--unbond-value", "20000000000000000000", - "--chain", "localnet", - "--proxy", "http://127.0.0.1:7950", - "--estimate-gas", "--recall-nonce" - ]) + assert tx["sender"] == "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th" + assert tx["receiver"] == "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqplllst77y4l" + assert tx["value"] == "0" + assert tx["nonce"] == 7 + assert tx["gasLimit"] == 5357500 + assert tx["chainID"] == "localnet" + assert tx["version"] == 2 + assert tx["options"] == 0 + assert ( + tx["signature"] + == "983b3127490949bc29e722a40a87871e88a6eb085fffb4d2801b2a79b39ff0aa395a39dc03367edb5f3858ed7ad9bea7b0c141110717cb639c45010055254606" + ) + assert ( + data + == "unStakeNodes@e7beaa95b3877f47348df4dd1cb578a4f7cabf7a20bfeefe5cdd263878ff132b765e04fef6f40c93512b666c47ed7719b8902f6c922c04247989b7137e837cc81a62e54712471c97a2ddab75aa9c2f58f813ed4c0fa722bde0ab718bff382208" + ) + + +def test_unstake_tokens(capsys: Any): + return_code = main( + [ + "validator", + "unstake-tokens", + "--pem", + str(alice_pem), + "--unstake-value", + "11000000000000000000", + "--chain", + "localnet", + "--nonce=7", + ] + ) + assert return_code == 0 + + output = get_output(capsys) + tx = output["emittedTransaction"] + data = output["emittedTransactionData"] + + assert tx["sender"] == "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th" + assert tx["receiver"] == "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqplllst77y4l" + assert tx["value"] == "0" + assert tx["nonce"] == 7 + assert tx["gasLimit"] == 5095000 + assert tx["chainID"] == "localnet" + assert tx["version"] == 2 + assert tx["options"] == 0 + assert ( + tx["signature"] + == "ed8e401e875d70bc3a62bf966fc8a9ecda2d49a851fe216f265176be5ab43040a85df55798dc828c928079573e2aa8dc52627e87c92824d8c91fdc3f3d195e0a" + ) + assert data == "unStakeTokens@98a7d9b8314c0000" + + +def test_unbond_nodes(capsys: Any): + return_code = main( + [ + "validator", + "unbond-nodes", + "--pem", + str(alice_pem), + "--nodes-public-keys", + bls_key, + "--chain", + "localnet", + "--nonce=7", + ] + ) + assert return_code == 0 + + output = get_output(capsys) + tx = output["emittedTransaction"] + data = output["emittedTransactionData"] + + assert tx["sender"] == "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th" + assert tx["receiver"] == "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqplllst77y4l" + assert tx["value"] == "0" + assert tx["nonce"] == 7 + assert tx["gasLimit"] == 5356000 + assert tx["chainID"] == "localnet" + assert tx["version"] == 2 + assert tx["options"] == 0 + assert ( + tx["signature"] + == "ee261c7e7f1dc7822b31c609c570ba1b1da3e39ea68e231f2aea30ca9f70e0f61679f81739f22eee338fa9b8c14d498ca8892cde118d53b08f1440fe3737eb02" + ) + assert ( + data + == "unBondNodes@e7beaa95b3877f47348df4dd1cb578a4f7cabf7a20bfeefe5cdd263878ff132b765e04fef6f40c93512b666c47ed7719b8902f6c922c04247989b7137e837cc81a62e54712471c97a2ddab75aa9c2f58f813ed4c0fa722bde0ab718bff382208" + ) + + +def test_unbond_tokens(capsys: Any): + return_code = main( + [ + "validator", + "unbond-tokens", + "--pem", + str(alice_pem), + "--unbond-value", + "20000000000000000000", + "--chain", + "localnet", + "--nonce=7", + ] + ) assert return_code == 0 + output = get_output(capsys) + tx = output["emittedTransaction"] + data = output["emittedTransactionData"] + + assert tx["sender"] == "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th" + assert tx["receiver"] == "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqplllst77y4l" + assert tx["value"] == "0" + assert tx["nonce"] == 7 + assert tx["gasLimit"] == 5096500 + assert tx["chainID"] == "localnet" + assert tx["version"] == 2 + assert tx["options"] == 0 + assert ( + tx["signature"] + == "a7c96028a97d035c0068b9c2a4bbc4ee3b9613d81dfa4c388fd8a90e66f4e200e715e87a760c0a7d456f3b3a4dc225f760084477cb15ac690c9e1cb7c006f70d" + ) + assert data == "unBondTokens@01158e460913d00000" + -@pytest.mark.require_localnet -def test_clean_registration_data(): - # Clean registration data - return_code = main([ - "validator", "clean-registered-data", - "--pem", str(alice_pem), - "--chain", "localnet", - "--proxy", "http://127.0.0.1:7950", - "--estimate-gas", "--recall-nonce" - ]) +def test_clean_registration_data(capsys: Any): + return_code = main( + [ + "validator", + "clean-registered-data", + "--pem", + str(alice_pem), + "--chain", + "localnet", + "--nonce=7", + ] + ) assert return_code == 0 + output = get_output(capsys) + tx = output["emittedTransaction"] + data = output["emittedTransactionData"] -@pytest.mark.require_localnet -def test_re_stake_unstaked_nodes(): - # Clean registration data - return_code = main([ - "validator", "restake-unstaked-nodes", - "--pem", str(alice_pem), - "--nodes-public-keys", bls_key, - "--chain", "localnet", - "--proxy", "http://127.0.0.1:7950", - "--estimate-gas", "--recall-nonce" - ]) + assert tx["sender"] == "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th" + assert tx["receiver"] == "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqplllst77y4l" + assert tx["value"] == "0" + assert tx["nonce"] == 7 + assert tx["gasLimit"] == 5078500 + assert tx["chainID"] == "localnet" + assert tx["version"] == 2 + assert tx["options"] == 0 + assert ( + tx["signature"] + == "005c35ccf2bbffbc753c8971aba1edffb43dbad1db62a88a26d295445937bb7f84dfd26e31329f8622ec9b53c5be4a39f1dd8ab83189f2cd5211c1c8541d7b00" + ) + assert data == "cleanRegisteredData" + + +def test_re_stake_unstaked_nodes(capsys: Any): + return_code = main( + [ + "validator", + "restake-unstaked-nodes", + "--pem", + str(alice_pem), + "--nodes-public-keys", + bls_key, + "--chain", + "localnet", + "--nonce=7", + ] + ) assert return_code == 0 + + output = get_output(capsys) + tx = output["emittedTransaction"] + data = output["emittedTransactionData"] + + assert tx["sender"] == "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th" + assert tx["receiver"] == "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqplllst77y4l" + assert tx["value"] == "0" + assert tx["nonce"] == 7 + assert tx["gasLimit"] == 5369500 + assert tx["chainID"] == "localnet" + assert tx["version"] == 2 + assert tx["options"] == 0 + assert ( + tx["signature"] + == "5f2196b81d9a72df401655becfc31e4167d89e76235f52abf506f9d9b10375b8b699339693b3f2d12552366e38ec2722a2ed50490a2beeaee0ae819d08f1ea0e" + ) + assert ( + data + == "reStakeUnStakedNodes@e7beaa95b3877f47348df4dd1cb578a4f7cabf7a20bfeefe5cdd263878ff132b765e04fef6f40c93512b666c47ed7719b8902f6c922c04247989b7137e837cc81a62e54712471c97a2ddab75aa9c2f58f813ed4c0fa722bde0ab718bff382208" + ) + + +def get_output(capsys: Any): + tx = _read_stdout(capsys) + return json.loads(tx) + + +def _read_stdout(capsys: Any) -> str: + stdout: str = capsys.readouterr().out.strip() + return stdout diff --git a/multiversx_sdk_cli/tests/test_cli_validators_localnet.py b/multiversx_sdk_cli/tests/test_cli_validators_localnet.py new file mode 100644 index 00000000..89149f18 --- /dev/null +++ b/multiversx_sdk_cli/tests/test_cli_validators_localnet.py @@ -0,0 +1,294 @@ +from pathlib import Path + +import pytest + +from multiversx_sdk_cli.cli import main + +testdata_path = Path(__file__).parent / "testdata" +testdata_out = Path(__file__).parent / "testdata-out" + +proxy_url = "http://localhost:7950/network/config" +alice_pem = testdata_path / "alice.pem" +reward_address = "erd1k2s324ww2g0yj38qn2ch2jwctdy8mnfxep94q9arncc6xecg3xaq6mjse8" +bls_key = "e7beaa95b3877f47348df4dd1cb578a4f7cabf7a20bfeefe5cdd263878ff132b765e04fef6f40c93512b666c47ed7719b8902f6c922c04247989b7137e837cc81a62e54712471c97a2ddab75aa9c2f58f813ed4c0fa722bde0ab718bff382208" + + +@pytest.mark.require_localnet +def test_stake(): + validators_json = testdata_path / "validators_ci.pem" + + # Stake with recall nonce + return_code = main( + [ + "validator", + "stake", + "--pem", + str(alice_pem), + "--value", + "2500000000000000000000", + "--validators-pem", + str(validators_json), + "--reward-address", + reward_address, + "--chain", + "localnet", + "--proxy", + "http://127.0.0.1:7950", + "--recall-nonce", + ] + ) + assert return_code == 0 + + # Stake with provided nonce + return_code = main( + [ + "validator", + "stake", + "--pem", + str(alice_pem), + "--value", + "2500000000000000000000", + "--validators-pem", + str(validators_json), + "--reward-address", + reward_address, + "--chain", + "localnet", + "--proxy", + "http://127.0.0.1:7950", + "--nonce=0", + ] + ) + assert return_code == 0 + + +@pytest.mark.require_localnet +def test_stake_top_up(): + # Stake with topUp + return_code = main( + [ + "validator", + "stake", + "--top-up", + "--pem", + str(alice_pem), + "--value", + "2711000000000000000000", + "--chain", + "localnet", + "--proxy", + "http://127.0.0.1:7950", + "--recall-nonce", + ] + ) + assert return_code == 0 + + +@pytest.mark.require_localnet +def test_unstake(): + # Unstake + return_code = main( + [ + "validator", + "unstake", + "--pem", + str(alice_pem), + "--nodes-public-key", + bls_key, + "--chain", + "localnet", + "--proxy", + "http://127.0.0.1:7950", + "--recall-nonce", + ] + ) + assert return_code == 0 + + +@pytest.mark.require_localnet +def test_unbond(): + # Unbond + return_code = main( + [ + "validator", + "unbond", + "--pem", + str(alice_pem), + "--nodes-public-key", + bls_key, + "--chain", + "localnet", + "--proxy", + "http://127.0.0.1:7950", + "--recall-nonce", + ] + ) + assert return_code == 0 + + +@pytest.mark.require_localnet +def test_unjail(): + # Unjail + return_code = main( + [ + "validator", + "unjail", + "--pem", + str(alice_pem), + "--value", + "2500000000000000000000", + "--nodes-public-key", + bls_key, + "--chain", + "localnet", + "--proxy", + "http://127.0.0.1:7950", + "--recall-nonce", + ] + ) + assert return_code == 0 + + +@pytest.mark.require_localnet +def test_change_reward_address(): + # Change reward address + return_code = main( + [ + "validator", + "change-reward-address", + "--pem", + str(alice_pem), + "--reward-address", + reward_address, + "--chain", + "localnet", + "--proxy", + "http://127.0.0.1:7950", + "--recall-nonce", + ] + ) + assert return_code == 0 + + +@pytest.mark.require_localnet +def test_unstake_nodes(): + # Unstake Nodes + return_code = main( + [ + "validator", + "unstake-nodes", + "--pem", + str(alice_pem), + "--nodes-public-key", + bls_key, + "--chain", + "localnet", + "--proxy", + "http://127.0.0.1:7950", + "--recall-nonce", + ] + ) + assert return_code == 0 + + +@pytest.mark.require_localnet +def test_unstake_tokens(): + # Unstake Tokens + return_code = main( + [ + "validator", + "unstake-tokens", + "--pem", + str(alice_pem), + "--unstake-value", + "11000000000000000000", + "--chain", + "localnet", + "--proxy", + "http://127.0.0.1:7950", + "--recall-nonce", + ] + ) + assert return_code == 0 + + +@pytest.mark.require_localnet +def test_unbond_nodes(): + # Unbond nodes + return_code = main( + [ + "validator", + "unbond-nodes", + "--pem", + str(alice_pem), + "--nodes-public-keys", + bls_key, + "--chain", + "localnet", + "--proxy", + "http://127.0.0.1:7950", + "--recall-nonce", + ] + ) + assert return_code == 0 + + +@pytest.mark.require_localnet +def test_unbond_tokens(): + # Unbond nodes + return_code = main( + [ + "validator", + "unbond-tokens", + "--pem", + str(alice_pem), + "--unbond-value", + "20000000000000000000", + "--chain", + "localnet", + "--proxy", + "http://127.0.0.1:7950", + "--recall-nonce", + ] + ) + assert return_code == 0 + + +@pytest.mark.require_localnet +def test_clean_registration_data(): + # Clean registration data + return_code = main( + [ + "validator", + "clean-registered-data", + "--pem", + str(alice_pem), + "--chain", + "localnet", + "--proxy", + "http://127.0.0.1:7950", + "--recall-nonce", + ] + ) + assert return_code == 0 + + +@pytest.mark.require_localnet +def test_re_stake_unstaked_nodes(): + # Clean registration data + return_code = main( + [ + "validator", + "restake-unstaked-nodes", + "--pem", + str(alice_pem), + "--nodes-public-keys", + bls_key, + "--chain", + "localnet", + "--proxy", + "http://127.0.0.1:7950", + "--recall-nonce", + ] + ) + assert return_code == 0 diff --git a/multiversx_sdk_cli/tests/test_cli_wallet.py b/multiversx_sdk_cli/tests/test_cli_wallet.py index 9a6060f6..d938add0 100644 --- a/multiversx_sdk_cli/tests/test_cli_wallet.py +++ b/multiversx_sdk_cli/tests/test_cli_wallet.py @@ -3,8 +3,7 @@ from pathlib import Path from typing import Any -from multiversx_sdk import (Address, AddressComputer, Mnemonic, UserPEM, - UserWallet) +from multiversx_sdk import Address, AddressComputer, Mnemonic, UserPEM, UserWallet from multiversx_sdk_cli.cli import main @@ -60,11 +59,7 @@ def test_wallet_new_as_mnemonic(): outfile = testdata_out_path / "wallet.txt" outfile.unlink(missing_ok=True) - main([ - "wallet", "new", - "--format", "raw-mnemonic", - "--outfile", str(outfile) - ]) + main(["wallet", "new", "--format", "raw-mnemonic", "--outfile", str(outfile)]) assert Mnemonic.is_text_valid(outfile.read_text()) @@ -73,23 +68,35 @@ def test_wallet_new_as_pem(): outfile = testdata_out_path / "wallet.pem" outfile.unlink(missing_ok=True) - main([ - "wallet", "new", - "--format", "pem", - "--outfile", str(outfile), - "--address-hrp", "erd" - ]) + main( + [ + "wallet", + "new", + "--format", + "pem", + "--outfile", + str(outfile), + "--address-hrp", + "erd", + ] + ) assert UserPEM.from_file(outfile).label.startswith("erd1") outfile.unlink(missing_ok=True) - main([ - "wallet", "new", - "--format", "pem", - "--outfile", str(outfile), - "--address-hrp", "test" - ]) + main( + [ + "wallet", + "new", + "--format", + "pem", + "--outfile", + str(outfile), + "--address-hrp", + "test", + ] + ) assert UserPEM.from_file(outfile).label.startswith("test1") @@ -99,11 +106,7 @@ def test_wallet_new_as_keystore_with_mnemonic(capsys: Any, monkeypatch: Any): outfile.unlink(missing_ok=True) _mock_getpass(monkeypatch, "password") - main([ - "wallet", "new", - "--format", "keystore-mnemonic", - "--outfile", str(outfile) - ]) + main(["wallet", "new", "--format", "keystore-mnemonic", "--outfile", str(outfile)]) expected_mnemonic = _read_stdout_mnemonic(capsys) keyfile = json.loads(outfile.read_text()) @@ -116,11 +119,7 @@ def test_wallet_new_as_keystore_with_secret_key(capsys: Any, monkeypatch: Any): outfile.unlink(missing_ok=True) _mock_getpass(monkeypatch, "password") - main([ - "wallet", "new", - "--format", "keystore-secret-key", - "--outfile", str(outfile) - ]) + main(["wallet", "new", "--format", "keystore-secret-key", "--outfile", str(outfile)]) expected_secret_key = Mnemonic(_read_stdout_mnemonic(capsys)).derive_key(0) actual_secret_key = UserWallet.load_secret_key(outfile, "password") @@ -132,12 +131,22 @@ def test_wallet_convert_raw_mnemonic_to_pem(): outfile = testdata_out_path / "alice.pem" outfile.unlink(missing_ok=True) - main([ - "wallet", "convert", - "--in-format", "raw-mnemonic", "--infile", str(infile), - "--out-format", "pem", "--outfile", str(outfile), - "--address-index", "0" - ]) + main( + [ + "wallet", + "convert", + "--in-format", + "raw-mnemonic", + "--infile", + str(infile), + "--out-format", + "pem", + "--outfile", + str(outfile), + "--address-index", + "0", + ] + ) pem = UserPEM.from_file(outfile) assert pem.label == "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th" @@ -151,11 +160,20 @@ def test_wallet_convert_raw_mnemonic_to_keystore_with_mnemonic(monkeypatch: Any) outfile.unlink(missing_ok=True) _mock_getpass(monkeypatch, "password") - main([ - "wallet", "convert", - "--in-format", "raw-mnemonic", "--infile", str(infile), - "--out-format", "keystore-mnemonic", "--outfile", str(outfile) - ]) + main( + [ + "wallet", + "convert", + "--in-format", + "raw-mnemonic", + "--infile", + str(infile), + "--out-format", + "keystore-mnemonic", + "--outfile", + str(outfile), + ] + ) keyfile_json = outfile.read_text() keyfile = json.loads(keyfile_json) @@ -171,12 +189,22 @@ def test_wallet_convert_raw_mnemonic_to_keystore_with_secret_key(monkeypatch: An outfile.unlink(missing_ok=True) _mock_getpass(monkeypatch, "password") - main([ - "wallet", "convert", - "--in-format", "raw-mnemonic", "--infile", str(infile), - "--out-format", "keystore-secret-key", "--outfile", str(outfile), - "--address-index", "0" - ]) + main( + [ + "wallet", + "convert", + "--in-format", + "raw-mnemonic", + "--infile", + str(infile), + "--out-format", + "keystore-secret-key", + "--outfile", + str(outfile), + "--address-index", + "0", + ] + ) keyfile_json = outfile.read_text() keyfile = json.loads(keyfile_json) @@ -185,12 +213,22 @@ def test_wallet_convert_raw_mnemonic_to_keystore_with_secret_key(monkeypatch: An # Bob outfile.unlink(missing_ok=True) - main([ - "wallet", "convert", - "--in-format", "raw-mnemonic", "--infile", str(infile), - "--out-format", "keystore-secret-key", "--outfile", str(outfile), - "--address-index", "1" - ]) + main( + [ + "wallet", + "convert", + "--in-format", + "raw-mnemonic", + "--infile", + str(infile), + "--out-format", + "keystore-secret-key", + "--outfile", + str(outfile), + "--address-index", + "1", + ] + ) keyfile_json = outfile.read_text() keyfile = json.loads(keyfile_json) @@ -205,11 +243,20 @@ def test_wallet_convert_keystore_with_secret_key_to_pem(monkeypatch: Any): outfile.unlink(missing_ok=True) _mock_getpass(monkeypatch, "password") - main([ - "wallet", "convert", - "--in-format", "keystore-secret-key", "--infile", str(infile), - "--out-format", "pem", "--outfile", str(outfile) - ]) + main( + [ + "wallet", + "convert", + "--in-format", + "keystore-secret-key", + "--infile", + str(infile), + "--out-format", + "pem", + "--outfile", + str(outfile), + ] + ) pem = UserPEM.from_file(outfile) assert pem.label == "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th" @@ -217,18 +264,28 @@ def test_wallet_convert_keystore_with_secret_key_to_pem(monkeypatch: Any): def test_wallet_bech32_encode(capsys: Any): - main([ - "wallet", "bech32", "--encode", "0139472eff6886771a982f3083da5d421f24c29181e63888228dc81ca60d69e1" - ]) + main( + [ + "wallet", + "bech32", + "--encode", + "0139472eff6886771a982f3083da5d421f24c29181e63888228dc81ca60d69e1", + ] + ) out = _read_stdout(capsys) assert out == "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th" def test_wallet_bech32_decode(capsys: Any): - main([ - "wallet", "bech32", "--decode", "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th" - ]) + main( + [ + "wallet", + "bech32", + "--decode", + "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th", + ] + ) out = _read_stdout(capsys) assert out == "0139472eff6886771a982f3083da5d421f24c29181e63888228dc81ca60d69e1" @@ -237,9 +294,18 @@ def test_wallet_bech32_decode(capsys: Any): def test_wallet_convert_pem_to_bech32_address(capsys: Any): infile = testdata_path / "alice.pem" - main([ - "wallet", "convert", "--infile", str(infile), "--in-format", "pem", "--out-format", "address-bech32" - ]) + main( + [ + "wallet", + "convert", + "--infile", + str(infile), + "--in-format", + "pem", + "--out-format", + "address-bech32", + ] + ) out = _read_stdout(capsys).strip("Output:\n\n") assert out == "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th" @@ -248,9 +314,18 @@ def test_wallet_convert_pem_to_bech32_address(capsys: Any): def test_wallet_convert_pem_to_pubkey(capsys: Any): infile = testdata_path / "alice.pem" - main([ - "wallet", "convert", "--infile", str(infile), "--in-format", "pem", "--out-format", "address-hex" - ]) + main( + [ + "wallet", + "convert", + "--infile", + str(infile), + "--in-format", + "pem", + "--out-format", + "address-hex", + ] + ) out = _read_stdout(capsys).strip("Output:\n\n") assert out == "0139472eff6886771a982f3083da5d421f24c29181e63888228dc81ca60d69e1" @@ -259,9 +334,7 @@ def test_wallet_convert_pem_to_pubkey(capsys: Any): def test_wallet_convert_pem_to_secret_key(capsys: Any): infile = testdata_path / "alice.pem" - main([ - "wallet", "convert", "--infile", str(infile), "--in-format", "pem", "--out-format", "secret-key" - ]) + main(["wallet", "convert", "--infile", str(infile), "--in-format", "pem", "--out-format", "secret-key"]) out = _read_stdout(capsys).strip("Output:\n\n") assert out == "413f42575f7f26fad3317a778771212fdb80245850981e48b58a4f25e344e8f9" @@ -278,7 +351,7 @@ def test_wallet_sign_message(capsys: Any): assert out == { "address": "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th", "message": "test", - "signature": "0x7aff43cd6e3d880a65033bf0a1b16274854fd7dfa9fe5faa7fa9a665ee851afd4c449310f5f1697d348e42d1819eaef69080e33e7652d7393521ed50d7427a0e" + "signature": "0x7aff43cd6e3d880a65033bf0a1b16274854fd7dfa9fe5faa7fa9a665ee851afd4c449310f5f1697d348e42d1819eaef69080e33e7652d7393521ed50d7427a0e", } @@ -287,16 +360,18 @@ def test_verify_previously_signed_message(capsys: Any): address = "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th" signature = "0x7aff43cd6e3d880a65033bf0a1b16274854fd7dfa9fe5faa7fa9a665ee851afd4c449310f5f1697d348e42d1819eaef69080e33e7652d7393521ed50d7427a0e" - return_code = main([ - "wallet", - "verify-message", - "--address", - address, - "--message", - message, - "--signature", - signature - ]) + return_code = main( + [ + "wallet", + "verify-message", + "--address", + address, + "--message", + message, + "--signature", + signature, + ] + ) assert False if return_code else True out = _read_stdout(capsys) @@ -309,16 +384,18 @@ def test_verify_not_signed_message(capsys: Any): address = "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th" signature = "0x7aff43cd6e3d880a65033bf0a1b16274854fd7dfa9fe5faa7fa9a665ee851afd4c449310f5f1697d348e42d1819eaef69080e33e7652d7393521ed50d7427a0e" - return_code = main([ - "wallet", - "verify-message", - "--address", - address, - "--message", - message, - "--signature", - signature - ]) + return_code = main( + [ + "wallet", + "verify-message", + "--address", + address, + "--message", + message, + "--signature", + signature, + ] + ) assert False if return_code else True out = _read_stdout(capsys) @@ -330,63 +407,117 @@ def test_sign_and_verify_message_with_multi_address_pem(capsys: Any): multi_address_pem_path = testdata_path / "multiple_addresses.pem" message = "test" - return_code = main(["wallet", "sign-message", "--message", message, "--pem", str(multi_address_pem_path), "--pem-index", "0"]) + return_code = main( + [ + "wallet", + "sign-message", + "--message", + message, + "--pem", + str(multi_address_pem_path), + "--sender-wallet-index", + "0", + ] + ) out = json.loads(_read_stdout(capsys)) assert False if return_code else True assert out == { "address": "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th", "message": "test", - "signature": "0x7aff43cd6e3d880a65033bf0a1b16274854fd7dfa9fe5faa7fa9a665ee851afd4c449310f5f1697d348e42d1819eaef69080e33e7652d7393521ed50d7427a0e" + "signature": "0x7aff43cd6e3d880a65033bf0a1b16274854fd7dfa9fe5faa7fa9a665ee851afd4c449310f5f1697d348e42d1819eaef69080e33e7652d7393521ed50d7427a0e", } - return_code = main(["wallet", "verify-message", - "--address", "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th", - "--message", message, - "--signature", "0x7aff43cd6e3d880a65033bf0a1b16274854fd7dfa9fe5faa7fa9a665ee851afd4c449310f5f1697d348e42d1819eaef69080e33e7652d7393521ed50d7427a0e" - ]) + return_code = main( + [ + "wallet", + "verify-message", + "--address", + "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th", + "--message", + message, + "--signature", + "0x7aff43cd6e3d880a65033bf0a1b16274854fd7dfa9fe5faa7fa9a665ee851afd4c449310f5f1697d348e42d1819eaef69080e33e7652d7393521ed50d7427a0e", + ] + ) assert False if return_code else True out = _read_stdout(capsys) text = """SUCCESS: The message "test" was signed by erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th""".split() assert all(word in out for word in text) - return_code = main(["wallet", "sign-message", "--message", message, "--pem", str(multi_address_pem_path), "--pem-index", "1"]) + return_code = main( + [ + "wallet", + "sign-message", + "--message", + message, + "--pem", + str(multi_address_pem_path), + "--sender-wallet-index", + "1", + ] + ) out = json.loads(_read_stdout(capsys)) assert False if return_code else True assert out == { "address": "erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx", "message": "test", - "signature": "0x50024bd755f8801e4ffbe76f93e6b8d82220673981cc44952a52bbfa02d262020ce933a2216e0e16e838dc5691f5701a1e0c250279cdae29268344d1523ea805" + "signature": "0x50024bd755f8801e4ffbe76f93e6b8d82220673981cc44952a52bbfa02d262020ce933a2216e0e16e838dc5691f5701a1e0c250279cdae29268344d1523ea805", } - return_code = main(["wallet", "verify-message", - "--address", "erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx", - "--message", message, - "--signature", "0x50024bd755f8801e4ffbe76f93e6b8d82220673981cc44952a52bbfa02d262020ce933a2216e0e16e838dc5691f5701a1e0c250279cdae29268344d1523ea805" - ]) + return_code = main( + [ + "wallet", + "verify-message", + "--address", + "erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx", + "--message", + message, + "--signature", + "0x50024bd755f8801e4ffbe76f93e6b8d82220673981cc44952a52bbfa02d262020ce933a2216e0e16e838dc5691f5701a1e0c250279cdae29268344d1523ea805", + ] + ) assert False if return_code else True out = _read_stdout(capsys) text = """SUCCESS: The message "test" was signed by erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx""".split() assert all(word in out for word in text) - return_code = main(["wallet", "sign-message", "--message", message, "--pem", str(multi_address_pem_path), "--pem-index", "2"]) + return_code = main( + [ + "wallet", + "sign-message", + "--message", + message, + "--pem", + str(multi_address_pem_path), + "--sender-wallet-index", + "2", + ] + ) out = json.loads(_read_stdout(capsys)) assert False if return_code else True assert out == { "address": "erd1k2s324ww2g0yj38qn2ch2jwctdy8mnfxep94q9arncc6xecg3xaq6mjse8", "message": "test", - "signature": "0x99dd1ebf2bf47a7cb99f362d2458b5c83c727686517c97c877babeb8be7f840c543785edf2f9688a1fc2c076b0887d8ee6d2be7b181bba4dc3984a4ee406fa0f" + "signature": "0x99dd1ebf2bf47a7cb99f362d2458b5c83c727686517c97c877babeb8be7f840c543785edf2f9688a1fc2c076b0887d8ee6d2be7b181bba4dc3984a4ee406fa0f", } - return_code = main(["wallet", "verify-message", - "--address", "erd1k2s324ww2g0yj38qn2ch2jwctdy8mnfxep94q9arncc6xecg3xaq6mjse8", - "--message", message, - "--signature", "0x99dd1ebf2bf47a7cb99f362d2458b5c83c727686517c97c877babeb8be7f840c543785edf2f9688a1fc2c076b0887d8ee6d2be7b181bba4dc3984a4ee406fa0f" - ]) + return_code = main( + [ + "wallet", + "verify-message", + "--address", + "erd1k2s324ww2g0yj38qn2ch2jwctdy8mnfxep94q9arncc6xecg3xaq6mjse8", + "--message", + message, + "--signature", + "0x99dd1ebf2bf47a7cb99f362d2458b5c83c727686517c97c877babeb8be7f840c543785edf2f9688a1fc2c076b0887d8ee6d2be7b181bba4dc3984a4ee406fa0f", + ] + ) assert False if return_code else True out = _read_stdout(capsys) @@ -405,7 +536,8 @@ def _read_stdout_wallet_address(capsys: Any) -> str: def _read_stdout(capsys: Any) -> str: - return capsys.readouterr().out.strip() + stdout: str = capsys.readouterr().out.strip() + return stdout def _mock_getpass(monkeypatch: Any, password: str): diff --git a/multiversx_sdk_cli/tests/test_contracts.py b/multiversx_sdk_cli/tests/test_contracts.py index 38f214ce..d81cad3f 100644 --- a/multiversx_sdk_cli/tests/test_contracts.py +++ b/multiversx_sdk_cli/tests/test_contracts.py @@ -1,14 +1,11 @@ import logging from pathlib import Path -import pytest from Cryptodome.Hash import keccak -from multiversx_sdk import Address, TransactionsFactoryConfig +from multiversx_sdk import Account, Address, TransactionsFactoryConfig -from multiversx_sdk_cli import errors -from multiversx_sdk_cli.accounts import Account from multiversx_sdk_cli.contract_verification import _create_request_signature -from multiversx_sdk_cli.contracts import SmartContract, _prepare_argument +from multiversx_sdk_cli.contracts import SmartContract logging.basicConfig(level=logging.INFO) @@ -20,59 +17,36 @@ def test_playground_keccak(): assert hexhash == "c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470" -def test_prepare_argument(): - assert _prepare_argument('0x5') == '05' - assert _prepare_argument('5') == '05' - assert _prepare_argument('0x05f') == '005F' - assert _prepare_argument('0xaaa') == '0AAA' - assert _prepare_argument('str:a') == '61' - assert _prepare_argument('str:aaa') == '616161' - - assert _prepare_argument(155) == '9B' - assert _prepare_argument('155') == '9B' - - assert \ - _prepare_argument('erd1qr9av6ar4ymr05xj93jzdxyezdrp6r4hz6u0scz4dtzvv7kmlldse7zktc') == \ - '00CBD66BA3A93637D0D22C6426989913461D0EB716B8F860556AC4C67ADBFFDB' - - assert _prepare_argument('str:TOK-123456') == '544F4B2D313233343536' - assert _prepare_argument('str:TOK-a1c2ef') == '544F4B2D613163326566' - assert _prepare_argument('str:TokenName') == '546F6B656E4E616D65' - assert _prepare_argument('str:/#%placeholder&*') == '2F2325706C616365686F6C646572262A' - - assert _prepare_argument(True) == "01" - assert _prepare_argument(False) == "00" - assert _prepare_argument("TrUe") == "01" - assert _prepare_argument("fAlSe") == "00" - - with pytest.raises(errors.UnknownArgumentFormat): - _ = _prepare_argument('0x05fq') - - assert _prepare_argument("str:") == "" - assert _prepare_argument("0x") == "" - - def test_contract_verification_create_request_signature(): - account = Account(pem_file=str(testdata_folder / "walletKey.pem")) + account = Account.new_from_pem(file_path=testdata_folder / "walletKey.pem") contract_address = Address.from_bech32("erd1qqqqqqqqqqqqqpgqeyj9g344pqguukajpcfqz9p0rfqgyg4l396qespdck") request_payload = b"test" signature = _create_request_signature(account, contract_address, request_payload) - assert signature.hex() == "30111258cc42ea08e0c6a3e053cc7086a88d614b8b119a244904e9a19896c73295b2fe5c520a1cb07cfe20f687deef9f294a0a05071e85c78a70a448ea5f0605" + assert ( + signature.hex() + == "30111258cc42ea08e0c6a3e053cc7086a88d614b8b119a244904e9a19896c73295b2fe5c520a1cb07cfe20f687deef9f294a0a05071e85c78a70a448ea5f0605" + ) def test_prepare_args_for_factories(): sc = SmartContract(TransactionsFactoryConfig("mock")) args = [ - "0x5", "123", "false", "true", + "0x5", + "123", + "false", + "true", "str:test-string", - "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th" + "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th", ] arguments = sc._prepare_args_for_factory(args) - assert arguments[0] == b"\x05" - assert arguments[1] == 123 - assert arguments[2] is False - assert arguments[3] is True - assert arguments[4] == "test-string" - assert arguments[5].to_bech32() == "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th" + assert arguments[0].get_payload() == b"\x05" + assert arguments[1].get_payload() == 123 + assert arguments[2].get_payload() is False + assert arguments[3].get_payload() is True + assert arguments[4].get_payload() == "test-string" + assert ( + arguments[5].get_payload() + == Address.new_from_bech32("erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th").get_public_key() + ) diff --git a/multiversx_sdk_cli/tests/test_converters.py b/multiversx_sdk_cli/tests/test_converters.py deleted file mode 100644 index 0c6d62fe..00000000 --- a/multiversx_sdk_cli/tests/test_converters.py +++ /dev/null @@ -1,19 +0,0 @@ -from multiversx_sdk_cli import utils - - -def test_str_to_hex_str(): - my_str = "1000000" - hex_str = utils.str_int_to_hex_str(my_str) - assert hex_str == "0f4240" - - my_str = "100000000000000000" - hex_str = utils.str_int_to_hex_str(my_str) - assert hex_str == "00016345785d8a0000" - - -def test_parse_keys(): - keys = "myKey,newKey,anotherKey" - parsed_keys, num_keys = utils.parse_keys(keys) - - assert num_keys == 3 - assert parsed_keys == "@myKey@newKey@anotherKey" diff --git a/multiversx_sdk_cli/tests/test_ledger.py b/multiversx_sdk_cli/tests/test_ledger.py deleted file mode 100644 index fff0b272..00000000 --- a/multiversx_sdk_cli/tests/test_ledger.py +++ /dev/null @@ -1,17 +0,0 @@ -from multiversx_sdk_cli.ledger.config import compare_versions -from multiversx_sdk_cli.ledger.ledger_app_handler import get_error - - -class TestLedger: - def test_compare_versions(self): - assert compare_versions("v1.0.0", "v1.0.1") == -1 - assert compare_versions("v1.0.1", "v1.0.1") == 0 - assert compare_versions("v1.0.1", "v1.0.0") == 1 - assert compare_versions("v1.0.0.1", "v1.0.0") == 1 - assert compare_versions("v1.0.1", "v1.0.1.0.0.4") == -1 - - def test_get_error(self): - assert get_error(0x6E0C) == "invalid fee" - assert get_error(0x6E11) == "regular signing is deprecated" - assert get_error(0x9000) == "" - assert get_error(0x9999999999) == "unknown error code: 0x9999999999" diff --git a/multiversx_sdk_cli/tests/test_native_auth_client.py b/multiversx_sdk_cli/tests/test_native_auth_client.py deleted file mode 100644 index 92678941..00000000 --- a/multiversx_sdk_cli/tests/test_native_auth_client.py +++ /dev/null @@ -1,128 +0,0 @@ -from typing import Any, List - -import pytest - -from multiversx_sdk_cli.native_auth_client import (NativeAuthClient, - NativeAuthClientConfig) - - -def mock(mocker: Any, code: int, response: Any): - mock_response = mocker.Mock() - mock_response.status_code = code - mock_response.json.return_value = response - mocker.patch("requests.get", return_value=mock_response) - - -def mock_side_effect(mocker: Any, responses: List[Any]): - def side_effect(*args: Any, **kwargs: Any): - response = responses.pop(0) - mock_response = mocker.Mock() - mock_response.status_code = response["code"] - mock_response.json.return_value = response["response"] - return mock_response - - mocker.patch("requests.get", side_effect=side_effect) - - -class TestNativeAuth: - ADDRESS = 'erd1qnk2vmuqywfqtdnkmauvpm8ls0xh00k8xeupuaf6cm6cd4rx89qqz0ppgl' - SIGNATURE = '906e79d54e69e688680abee54ec0c49ce2561eb5abfd01865b31cb3ed738272c7cfc4fc8cc1c3590dd5757e622639b01a510945d7f7c9d1ceda20a50a817080d' - BLOCK_HASH = 'ab459013b27fdc6fe98eed567bd0c1754e0628a4cc16883bf0170a29da37ad46' - TTL = 86400 - ORIGIN = 'https://api.multiversx.com' - TOKEN = f"aHR0cHM6Ly9hcGkubXVsdGl2ZXJzeC5jb20.{BLOCK_HASH}.{TTL}.e30" - ACCESS_TOKEN = 'ZXJkMXFuazJ2bXVxeXdmcXRkbmttYXV2cG04bHMweGgwMGs4eGV1cHVhZjZjbTZjZDRyeDg5cXF6MHBwZ2w.YUhSMGNITTZMeTloY0drdWJYVnNkR2wyWlhKemVDNWpiMjAuYWI0NTkwMTNiMjdmZGM2ZmU5OGVlZDU2N2JkMGMxNzU0ZTA2MjhhNGNjMTY4ODNiZjAxNzBhMjlkYTM3YWQ0Ni44NjQwMC5lMzA.906e79d54e69e688680abee54ec0c49ce2561eb5abfd01865b31cb3ed738272c7cfc4fc8cc1c3590dd5757e622639b01a510945d7f7c9d1ceda20a50a817080d' - INVALID_HASH_ERROR = 'Validation failed for block hash \'hash\'. Length should be 64.' - - def test_latest_block_should_return_signable_token(self, mocker: Any): - mock(mocker, 200, [{"hash": self.BLOCK_HASH}]) - config = NativeAuthClientConfig(origin=self.ORIGIN, expiry_seconds=self.TTL) - client = NativeAuthClient(config) - token = client.initialize() - assert token == self.TOKEN - - def test_throws_internal_server_error(self, mocker: Any): - mock(mocker, 500, {}) - client = NativeAuthClient() - with pytest.raises(Exception): - client.initialize() - - # if `/blocks/latest` raises error should fallback to `/blocks?size=1` - def test_fallback_mechanism(self, mocker: Any): - mock(mocker, 400, [{"statusCode": 400, - "message": self.INVALID_HASH_ERROR, - "error": "Bad request"}]) - mock(mocker, 200, {"hash": self.BLOCK_HASH}) - - config = NativeAuthClientConfig(origin=self.ORIGIN, expiry_seconds=self.TTL) - client = NativeAuthClient(config) - - token = client.initialize() - assert token == self.TOKEN - - def test_generate_access_token(self): - client = NativeAuthClient() - access_token = client.get_token(self.ADDRESS, self.TOKEN, self.SIGNATURE) - assert access_token == self.ACCESS_TOKEN - - -class TestNativeAuthWithGateway: - ADDRESS = 'erd1qnk2vmuqywfqtdnkmauvpm8ls0xh00k8xeupuaf6cm6cd4rx89qqz0ppgl' - SIGNATURE = '906e79d54e69e688680abee54ec0c49ce2561eb5abfd01865b31cb3ed738272c7cfc4fc8cc1c3590dd5757e622639b01a510945d7f7c9d1ceda20a50a817080d' - BLOCK_HASH = 'ab459013b27fdc6fe98eed567bd0c1754e0628a4cc16883bf0170a29da37ad46' - TTL = 86400 - ORIGIN = 'https://api.multiversx.com' - TOKEN = f"aHR0cHM6Ly9hcGkubXVsdGl2ZXJzeC5jb20.{BLOCK_HASH}.{TTL}.e30" - ACCESS_TOKEN = 'ZXJkMXFuazJ2bXVxeXdmcXRkbmttYXV2cG04bHMweGgwMGs4eGV1cHVhZjZjbTZjZDRyeDg5cXF6MHBwZ2w.YUhSMGNITTZMeTloY0drdWJYVnNkR2wyWlhKemVDNWpiMjAuYWI0NTkwMTNiMjdmZGM2ZmU5OGVlZDU2N2JkMGMxNzU0ZTA2MjhhNGNjMTY4ODNiZjAxNzBhMjlkYTM3YWQ0Ni44NjQwMC5lMzA.906e79d54e69e688680abee54ec0c49ce2561eb5abfd01865b31cb3ed738272c7cfc4fc8cc1c3590dd5757e622639b01a510945d7f7c9d1ceda20a50a817080d' - LATEST_ROUND = 115656 - METASHARD = 4294967295 - GATEWAY = 'https://gateway.multiversx.com' - - def test_latest_block_should_return_signable_token(self, mocker: Any): - responses = [ - {"code": 200, "response": {"data": {"status": {"erd_current_round": self.LATEST_ROUND}}}}, - {"code": 200, "response": {"data": {"blocks": [{"shard": self.METASHARD, "hash": self.BLOCK_HASH}]}}} - ] - mock_side_effect(mocker, responses) - - config = NativeAuthClientConfig(origin=self.ORIGIN, gateway_url=self.GATEWAY, block_hash_shard=self.METASHARD, expiry_seconds=self.TTL) - client = NativeAuthClient(config) - token = client.initialize() - assert token == self.TOKEN - - def test_should_raise_internal_server_error(self, mocker: Any): - responses = [ - {"code": 500, "response": {"data": {"status": {"erd_current_round": self.LATEST_ROUND}}}} - ] - mock_side_effect(mocker, responses) - - config = NativeAuthClientConfig(gateway_url=self.GATEWAY, block_hash_shard=self.METASHARD) - client = NativeAuthClient(config) - - with pytest.raises(Exception): - client.initialize() - - def test_raises_internal_server_error_on_second_request(self, mocker: Any): - responses = [ - {"code": 200, "response": {"data": {"status": {"erd_current_round": self.LATEST_ROUND}}}}, - {"code": 500, "response": {""}} - ] - mock_side_effect(mocker, responses) - - config = NativeAuthClientConfig(gateway_url=self.GATEWAY, block_hash_shard=self.METASHARD) - client = NativeAuthClient(config) - - with pytest.raises(Exception): - client.initialize() - - def test_generate_access_token(self): - config = NativeAuthClientConfig(gateway_url=self.GATEWAY, block_hash_shard=self.METASHARD) - client = NativeAuthClient(config) - - access_token = client.get_token( - address=self.ADDRESS, - token=self.TOKEN, - signature=self.SIGNATURE - ) - - assert access_token == self.ACCESS_TOKEN diff --git a/multiversx_sdk_cli/tests/test_playground_proxy.py b/multiversx_sdk_cli/tests/test_playground_proxy.py index 3bd999ee..29d0851d 100644 --- a/multiversx_sdk_cli/tests/test_playground_proxy.py +++ b/multiversx_sdk_cli/tests/test_playground_proxy.py @@ -3,7 +3,7 @@ class TestPlaygroundProxy: - @pytest.mark.skip('manual run only') + @pytest.mark.skip("manual run only") def test_do_request(self): # use a valid proxy address url = "http://localhost:8001" @@ -14,7 +14,7 @@ def test_do_request(self): print(response.json()) assert response is not None - @pytest.mark.skip('manual run only') + @pytest.mark.skip("manual run only") def test_do_request_node_status(self): # use a valid proxy address url = "http://localhost:8001" diff --git a/multiversx_sdk_cli/tests/test_proxy.py b/multiversx_sdk_cli/tests/test_proxy.py index 8234f7fb..6b49e889 100644 --- a/multiversx_sdk_cli/tests/test_proxy.py +++ b/multiversx_sdk_cli/tests/test_proxy.py @@ -1,30 +1,15 @@ from multiversx_sdk import Address, ProxyNetworkProvider -from multiversx_sdk_cli.accounts import Account from multiversx_sdk_cli.cli import main from multiversx_sdk_cli.config import get_config_for_network_providers -def test_get_account(): - result = main( - [ - "account", - "get", - "--address", - "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th", - "--proxy", - "https://testnet-api.multiversx.com" - ] - ) - assert False if result else True - - def test_sync_nonce(): - account = Account(address=Address.new_from_bech32("erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th")) + account = Address.new_from_bech32("erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th") config = get_config_for_network_providers() proxy = ProxyNetworkProvider("https://testnet-api.multiversx.com", config=config) - account.sync_nonce(proxy) - assert True if account.nonce else False + nonce = proxy.get_account(account).nonce + assert True if nonce else False def test_query_contract(): @@ -40,17 +25,3 @@ def test_query_contract(): ] ) assert False if result else True - - -def test_get_transaction(): - result = main( - [ - "tx", - "get", - "--proxy", - "https://devnet-api.multiversx.com", - "--hash", - "06f381ee88ed27ba08a35f995f17dceb737e1a99c5c4da0c247bbe7aa1d18551", - ] - ) - assert False if result else True diff --git a/multiversx_sdk_cli/tests/test_shared.py b/multiversx_sdk_cli/tests/test_shared.py deleted file mode 100644 index caa43b3c..00000000 --- a/multiversx_sdk_cli/tests/test_shared.py +++ /dev/null @@ -1,34 +0,0 @@ -from typing import Any - -import pytest - -from multiversx_sdk_cli.cli_shared import prepare_chain_id_in_args -from multiversx_sdk_cli.errors import ArgumentsNotProvidedError - - -class Args: - pass - - -def test_prepare_chain_id_in_args(): - args: Any = Args() - args.chain = None - args.proxy = None - - with pytest.raises(ArgumentsNotProvidedError): - prepare_chain_id_in_args(args) - - args.chain = "I" - args.proxy = "https://testnet-api.multiversx.com" - - prepare_chain_id_in_args(args) - assert args.chain == "T" - - args.chain = None - prepare_chain_id_in_args(args) - assert args.chain == "T" - - args.chain = "T" - args.proxy = None - prepare_chain_id_in_args(args) - assert args.chain == "T" diff --git a/multiversx_sdk_cli/tests/test_sign.py b/multiversx_sdk_cli/tests/test_sign.py index 77bc8bba..14870a99 100644 --- a/multiversx_sdk_cli/tests/test_sign.py +++ b/multiversx_sdk_cli/tests/test_sign.py @@ -1,5 +1,6 @@ import json from pathlib import Path + from multiversx_sdk_cli.cli import main @@ -9,16 +10,18 @@ def test_sign_tx(): signed_transaction = parent / "testdata-out" / "signed_transaction.json" expected_signature = "7b0fa3bd477a9aacdfd8d6b41628e525afbbc94b4b56c2a30a10f78514c2f6558b27eef701633481f1ef54b62697c91e9dc06cc6d2038bd13cf9557467142005" - main([ - "tx", - "sign", - "--pem", - f"{parent}/testdata/testUser.pem", - "--infile", - f"{unsigned_transaction}", - "--outfile", - f"{signed_transaction}" - ]) + main( + [ + "tx", + "sign", + "--pem", + f"{parent}/testdata/testUser.pem", + "--infile", + f"{unsigned_transaction}", + "--outfile", + f"{signed_transaction}", + ] + ) with open(signed_transaction) as f: signed_tx = json.load(f) diff --git a/multiversx_sdk_cli/tests/test_testnet.py b/multiversx_sdk_cli/tests/test_testnet.py index 40c999fe..999219b5 100644 --- a/multiversx_sdk_cli/tests/test_testnet.py +++ b/multiversx_sdk_cli/tests/test_testnet.py @@ -8,7 +8,7 @@ sys.path = [os.getcwd() + "/.."] + sys.path -def test_override_config(): +def test_override_config() -> None: config = ConfigRoot() # Check a few default values @@ -17,7 +17,10 @@ def test_override_config(): assert config.metashard.consensus_size == 1 assert config.networking.port_proxy == 7950 assert config.software.mx_chain_go.resolution == SoftwareResolution.Remote - assert config.software.mx_chain_go.archive_url == "https://github.com/multiversx/mx-chain-go/archive/refs/heads/master.zip" + assert ( + config.software.mx_chain_go.archive_url + == "https://github.com/multiversx/mx-chain-go/archive/refs/heads/master.zip" + ) # Now partly override the config config_patch: Dict[str, Any] = dict() @@ -32,9 +35,7 @@ def test_override_config(): "port_proxy": 7951, } config_patch["software"] = { - "mx_chain_go": { - "archive_url": "https://github.com/multiversx/mx-chain-go/archive/refs/tags/v1.5.1.zip" - } + "mx_chain_go": {"archive_url": "https://github.com/multiversx/mx-chain-go/archive/refs/tags/v1.5.1.zip"} } config.override(config_patch) @@ -45,4 +46,7 @@ def test_override_config(): assert config.metashard.consensus_size == 2 assert config.networking.port_proxy == 7951 assert config.software.mx_chain_go.resolution == SoftwareResolution.Remote - assert config.software.mx_chain_go.archive_url == "https://github.com/multiversx/mx-chain-go/archive/refs/tags/v1.5.1.zip" + assert ( + config.software.mx_chain_go.archive_url + == "https://github.com/multiversx/mx-chain-go/archive/refs/tags/v1.5.1.zip" + ) diff --git a/multiversx_sdk_cli/tests/test_transactions.py b/multiversx_sdk_cli/tests/test_transactions.py new file mode 100644 index 00000000..8a8c4e4c --- /dev/null +++ b/multiversx_sdk_cli/tests/test_transactions.py @@ -0,0 +1,256 @@ +from pathlib import Path + +from multiversx_sdk import Account, Address + +from multiversx_sdk_cli.guardian_relayer_data import GuardianRelayerData +from multiversx_sdk_cli.transactions import TransactionsController + +testdata = Path(__file__).parent / "testdata" + + +class TestTransactionsController: + controller = TransactionsController("D") + alice = Account.new_from_pem(testdata / "alice.pem") + + def test_create_transaction_without_data_and_value(self): + guardian_relayer_data = GuardianRelayerData() + transaction = self.controller.create_transaction( + sender=self.alice, + receiver=Address.new_from_bech32("erd1cqqxak4wun7508e0yj9ng843r6hv4mzd0hhpjpsejkpn9wa9yq8sj7u2u5"), + native_amount=0, + gas_limit=50000, + gas_price=1000000000, + nonce=7, + version=2, + options=0, + guardian_and_relayer_data=guardian_relayer_data, + ) + + assert transaction.sender == self.alice.address + assert transaction.receiver.to_bech32() == "erd1cqqxak4wun7508e0yj9ng843r6hv4mzd0hhpjpsejkpn9wa9yq8sj7u2u5" + assert transaction.value == 0 + assert transaction.chain_id == "D" + assert transaction.gas_limit == 50000 + assert transaction.gas_price == 1000000000 + assert transaction.nonce == 7 + assert transaction.version == 2 + assert transaction.options == 0 + assert not transaction.data + assert not transaction.guardian + assert not transaction.relayer + assert not transaction.guardian_signature + assert not transaction.relayer_signature + assert ( + transaction.signature.hex() + == "ecf9e9f8d395741c0fbb61eaba74295448ba380dd00a228463e29a278d4e3c9219b2fa54133e32510837e03d7cc9f17738b99b701e148ce098b8340064a5f409" + ) + + def test_create_transfer_transaction(self): + guardian_relayer_data = GuardianRelayerData() + transaction = self.controller.create_transaction( + sender=self.alice, + receiver=Address.new_from_bech32("erd1cqqxak4wun7508e0yj9ng843r6hv4mzd0hhpjpsejkpn9wa9yq8sj7u2u5"), + native_amount=123456789, + gas_limit=50000, + gas_price=1000000000, + nonce=7, + version=2, + options=0, + guardian_and_relayer_data=guardian_relayer_data, + ) + + assert transaction.sender == self.alice.address + assert transaction.receiver.to_bech32() == "erd1cqqxak4wun7508e0yj9ng843r6hv4mzd0hhpjpsejkpn9wa9yq8sj7u2u5" + assert transaction.value == 123456789 + assert transaction.chain_id == "D" + assert transaction.gas_limit == 50000 + assert transaction.gas_price == 1000000000 + assert transaction.nonce == 7 + assert transaction.version == 2 + assert transaction.options == 0 + assert not transaction.data + assert not transaction.guardian + assert not transaction.relayer + assert not transaction.guardian_signature + assert not transaction.relayer_signature + assert ( + transaction.signature.hex() + == "fe0ff59c06c453eed882db562f98c0684a32763a79580a33e269af6edbebc150007118b298f34d17c54b90d55cee76ed1e5254832db8acb2c34ef12f14b8b40d" + ) + + def test_create_transaction_with_data(self): + guardian_relayer_data = GuardianRelayerData() + transaction = self.controller.create_transaction( + sender=self.alice, + receiver=Address.new_from_bech32("erd1cqqxak4wun7508e0yj9ng843r6hv4mzd0hhpjpsejkpn9wa9yq8sj7u2u5"), + native_amount=0, + gas_limit=50000, + gas_price=1000000000, + nonce=7, + version=2, + options=0, + data="testdata", + guardian_and_relayer_data=guardian_relayer_data, + ) + + assert transaction.sender == self.alice.address + assert transaction.receiver.to_bech32() == "erd1cqqxak4wun7508e0yj9ng843r6hv4mzd0hhpjpsejkpn9wa9yq8sj7u2u5" + assert transaction.value == 0 + assert transaction.chain_id == "D" + assert transaction.gas_limit == 50000 + assert transaction.gas_price == 1000000000 + assert transaction.nonce == 7 + assert transaction.version == 2 + assert transaction.options == 0 + assert transaction.data == b"testdata" + assert not transaction.guardian + assert not transaction.relayer + assert not transaction.guardian_signature + assert not transaction.relayer_signature + assert ( + transaction.signature.hex() + == "74bb3bd0c4e87ed64a01456f888236a74b5672cf194a33a2225d868f5f43d65e9149f3145b9431075828ff842f70b977b778895925f37037db979e71563c540c" + ) + + def test_create_guarded_transaction(self): + guardian_relayer_data = GuardianRelayerData( + guardian=Account.new_from_pem(testdata / "testUser2.pem"), + guardian_address=Address.new_from_bech32("erd1ssmsc9022udc8pdw7wk3hxw74jr900xg28vwpz3z60gep66fasasl2nkm4"), + ) + + transaction = self.controller.create_transaction( + sender=self.alice, + receiver=Address.new_from_bech32("erd1cqqxak4wun7508e0yj9ng843r6hv4mzd0hhpjpsejkpn9wa9yq8sj7u2u5"), + native_amount=0, + gas_limit=200000, + gas_price=1000000000, + nonce=7, + version=2, + options=0, + data="testdata", + guardian_and_relayer_data=guardian_relayer_data, + ) + + assert transaction.sender == self.alice.address + assert transaction.receiver.to_bech32() == "erd1cqqxak4wun7508e0yj9ng843r6hv4mzd0hhpjpsejkpn9wa9yq8sj7u2u5" + assert transaction.value == 0 + assert transaction.chain_id == "D" + assert transaction.gas_limit == 200000 + assert transaction.gas_price == 1000000000 + assert transaction.nonce == 7 + assert transaction.version == 2 + assert transaction.options == 2 + assert transaction.data == b"testdata" + assert not transaction.relayer + assert not transaction.relayer_signature + assert ( + transaction.guardian + and transaction.guardian.to_bech32() == "erd1ssmsc9022udc8pdw7wk3hxw74jr900xg28vwpz3z60gep66fasasl2nkm4" + ) + assert ( + transaction.guardian_signature.hex() + == "c8b186f85c6e79e157aac48ec55d3f915abbc396e257d30aff193135485395c4b026e7921853366a16bcbbb8dbe5f6cf98a917a8ca11ccdc3f29f5a5a7d7af0c" + ) + assert ( + transaction.signature.hex() + == "8e9338de8f1d66bc6cd2f8e42cb62b5456d82ba66fdf9b8ace750ab3fa697f348084b41996b19d172b627b0b0a312ef76d29191d627b4d9959a3bf17409f780b" + ) + + def test_create_relayed_transaction(self): + guardian_relayer_data = GuardianRelayerData( + relayer=Account.new_from_pem(testdata / "testUser2.pem"), + relayer_address=Address.new_from_bech32("erd1ssmsc9022udc8pdw7wk3hxw74jr900xg28vwpz3z60gep66fasasl2nkm4"), + ) + + transaction = self.controller.create_transaction( + sender=self.alice, + receiver=Address.new_from_bech32("erd1cqqxak4wun7508e0yj9ng843r6hv4mzd0hhpjpsejkpn9wa9yq8sj7u2u5"), + native_amount=0, + gas_limit=200000, + gas_price=1000000000, + nonce=7, + version=2, + options=0, + data="testdata", + guardian_and_relayer_data=guardian_relayer_data, + ) + + assert transaction.sender == self.alice.address + assert transaction.receiver.to_bech32() == "erd1cqqxak4wun7508e0yj9ng843r6hv4mzd0hhpjpsejkpn9wa9yq8sj7u2u5" + assert transaction.value == 0 + assert transaction.chain_id == "D" + assert transaction.gas_limit == 200000 + assert transaction.gas_price == 1000000000 + assert transaction.nonce == 7 + assert transaction.version == 2 + assert transaction.options == 0 + assert transaction.data == b"testdata" + assert not transaction.guardian + assert not transaction.guardian_signature + assert ( + transaction.relayer + and transaction.relayer.to_bech32() == "erd1ssmsc9022udc8pdw7wk3hxw74jr900xg28vwpz3z60gep66fasasl2nkm4" + ) + assert ( + transaction.relayer_signature.hex() + == "385fb15816d52118b97a20451c2b225a81ce9be130ad13987453cab36e858f79af0473effc845bef1537ad9f878001e6fcdfeefa36c46c5e8bb6aab83c9b2a0b" + ) + assert ( + transaction.signature.hex() + == "89c2de2939bdd4ed2bdaf8f859f9020b6f7510b3a7298daaebdef53ce1de588181e1c27f8933745927e610ebfe6c41a8875e2b052a48fa22464903f3821b830e" + ) + + def test_create_guarded_relayed_transaction(self): + guardian_relayer_data = GuardianRelayerData( + guardian=Account.new_from_pem(testdata / "testUser.pem"), + guardian_address=Address.new_from_bech32("erd1cqqxak4wun7508e0yj9ng843r6hv4mzd0hhpjpsejkpn9wa9yq8sj7u2u5"), + relayer=Account.new_from_pem(testdata / "testUser2.pem"), + relayer_address=Address.new_from_bech32("erd1ssmsc9022udc8pdw7wk3hxw74jr900xg28vwpz3z60gep66fasasl2nkm4"), + ) + + transaction = self.controller.create_transaction( + sender=self.alice, + receiver=Address.new_from_bech32("erd1cqqxak4wun7508e0yj9ng843r6hv4mzd0hhpjpsejkpn9wa9yq8sj7u2u5"), + native_amount=0, + gas_limit=200000, + gas_price=1000000000, + nonce=7, + version=2, + options=0, + data="testdata", + guardian_and_relayer_data=guardian_relayer_data, + ) + + assert transaction.sender == self.alice.address + assert transaction.receiver.to_bech32() == "erd1cqqxak4wun7508e0yj9ng843r6hv4mzd0hhpjpsejkpn9wa9yq8sj7u2u5" + assert transaction.value == 0 + assert transaction.chain_id == "D" + assert transaction.gas_limit == 200000 + assert transaction.gas_price == 1000000000 + assert transaction.nonce == 7 + assert transaction.version == 2 + assert transaction.options == 2 + assert transaction.data == b"testdata" + + assert ( + transaction.guardian + and transaction.guardian.to_bech32() == "erd1cqqxak4wun7508e0yj9ng843r6hv4mzd0hhpjpsejkpn9wa9yq8sj7u2u5" + ) + assert ( + transaction.guardian_signature.hex() + == "06bc457b510cae975e1cc6ab863ed765aadbd948ad23cbd204d66a2b92bee683922d308148af47e28d186bceba13474c79f16b26803c65c908f1d4a2e2ac6a0e" + ) + + assert ( + transaction.relayer + and transaction.relayer.to_bech32() == "erd1ssmsc9022udc8pdw7wk3hxw74jr900xg28vwpz3z60gep66fasasl2nkm4" + ) + assert ( + transaction.relayer_signature.hex() + == "86e2460a6045ac3142c23f06c3b31fc132f38227faa25ffc8f7e9ce32c4542f6ed37d740d3c83c4454f2390befd223b968d4767c28991f574689b36faa978209" + ) + + assert ( + transaction.signature.hex() + == "b0bce606f2cfd52d6641f8e96aa29dcfd334af4707d88f81dba01dac67e72a99d7143dccadc8d120b387c4eaac84dc01865e57488a2162480a0f15acf50b6306" + ) diff --git a/multiversx_sdk_cli/tests/test_validators_core.py b/multiversx_sdk_cli/tests/test_validators_core.py deleted file mode 100644 index 0066b345..00000000 --- a/multiversx_sdk_cli/tests/test_validators_core.py +++ /dev/null @@ -1,17 +0,0 @@ -from pathlib import Path - -from multiversx_sdk import Address - -from multiversx_sdk_cli.validators.core import \ - prepare_transaction_data_for_stake - -TESTDATA_FOLDER = Path(__file__).parent.joinpath("testdata") - - -def test_prepare_transaction_data_for_stake(): - node_operator_address = Address.new_from_bech32("erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th") - validators_file_path = TESTDATA_FOLDER / "validators.json" - data, gas_limit = prepare_transaction_data_for_stake(node_operator_address, validators_file_path, node_operator_address) - - assert data == "stake@03@E7BEAA95B3877F47348DF4DD1CB578A4F7CABF7A20BFEEFE5CDD263878FF132B765E04FEF6F40C93512B666C47ED7719B8902F6C922C04247989B7137E837CC81A62E54712471C97A2DDAB75AA9C2F58F813ED4C0FA722BDE0AB718BFF382208@604882237A9845F508AD03877B5AAB90569683EEB51FAFCBBEB87440BA359992B3C0B837A8757C25BE18132549404F88@78689FD4B1E2E434D567FE01E61598A42717D83124308266BD09CCC15D2339DD318C019914B86AC29ADBAE5DD8A02D0307425E9BD85A296E94943708C72F8C670F0B7C50A890A5719088DBD9F1D062CAD9ACFFA06DF834106EEBE1A4257EF00D@EC54A009695AF56C3585EF623387B67B6DF1974B0B3C9138EB64BDE6EB33978AE9851112B20C99BF63588E8E949E4388@7188B234A8BF834F2E6258012AA09A2AB93178FFAB9C789480275F61FE02CD1B9A58DDC63B79A73ABEA9E2B7AC5CAC0B0D4324EFF50ACA2F0EC946B9AE6797511FA3CE461B57E77129CBA8AB3B51147695D4CE889CBE67905F6586B4E4F22491@C6C637DE17DB5F89A2FA1D1D935CB60C0E5E8958D3BFC47F903F774DD97398C8FE22093E113865EE98C3AFDD1DE62694@0139472EFF6886771A982F3083DA5D421F24C29181E63888228DC81CA60D69E1" - assert gas_limit == 16464500 diff --git a/multiversx_sdk_cli/tests/test_validators_file.py b/multiversx_sdk_cli/tests/test_validators_file.py deleted file mode 100644 index 0015a741..00000000 --- a/multiversx_sdk_cli/tests/test_validators_file.py +++ /dev/null @@ -1,20 +0,0 @@ -from pathlib import Path - -from multiversx_sdk_cli.validators.validators_file import ValidatorsFile - - -class ValidatorsFileTestCase: - testdata = Path(__file__).parent.joinpath("testdata") - validators_file_path = testdata / "validators.json" - - def test_read_validators_files_num_of_nodes(self): - validators_file = ValidatorsFile(self.validators_file_path) - - num_of_nodes = validators_file.get_num_of_nodes() - assert num_of_nodes == 3 - - def test_read_validators_files_get_validators_list(self): - validators_file = ValidatorsFile(self.validators_file_path) - - validators_list = validators_file.get_validators_list() - assert len(validators_list) == 3 diff --git a/multiversx_sdk_cli/tests/testdata/validators.json b/multiversx_sdk_cli/tests/testdata/validators.json deleted file mode 100644 index 96f6315f..00000000 --- a/multiversx_sdk_cli/tests/testdata/validators.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "validators": [ - { - "pemFile": "~/multiversx-sdk/testwallets/latest/validators/validatorKey00.pem" - }, - { - "pemFile": "~/multiversx-sdk/testwallets/latest/validators/validatorKey01.pem" - }, - { - "pemFile": "~/multiversx-sdk/testwallets/latest/validators/validatorKey02.pem" - } - ] -} diff --git a/multiversx_sdk_cli/tests/testdata/validators.pem b/multiversx_sdk_cli/tests/testdata/validators.pem new file mode 100644 index 00000000..3e598c61 --- /dev/null +++ b/multiversx_sdk_cli/tests/testdata/validators.pem @@ -0,0 +1,12 @@ +-----BEGIN PRIVATE KEY for e7beaa95b3877f47348df4dd1cb578a4f7cabf7a20bfeefe5cdd263878ff132b765e04fef6f40c93512b666c47ed7719b8902f6c922c04247989b7137e837cc81a62e54712471c97a2ddab75aa9c2f58f813ed4c0fa722bde0ab718bff382208----- +N2NmZjk5YmQ2NzE1MDJkYjdkMTViYzhhYmMwYzlhODA0ZmI5MjU0MDZmYmRkNTBm +MWU0YzE3YTRjZDc3NDI0Nw== +-----END PRIVATE KEY for e7beaa95b3877f47348df4dd1cb578a4f7cabf7a20bfeefe5cdd263878ff132b765e04fef6f40c93512b666c47ed7719b8902f6c922c04247989b7137e837cc81a62e54712471c97a2ddab75aa9c2f58f813ed4c0fa722bde0ab718bff382208----- +-----BEGIN PRIVATE KEY for 78689fd4b1e2e434d567fe01e61598a42717d83124308266bd09ccc15d2339dd318c019914b86ac29adbae5dd8a02d0307425e9bd85a296e94943708c72f8c670f0b7c50a890a5719088dbd9f1d062cad9acffa06df834106eebe1a4257ef00d----- +ODA4NWJhMWQ3ZjdjM2RiOTM4YWQ3MDU5NWEyYmRhYjA5NjQ0ZjFlYzM4MDNiZTE3 +MWMzM2YxNGJjODBkNGUzYg== +-----END PRIVATE KEY for 78689fd4b1e2e434d567fe01e61598a42717d83124308266bd09ccc15d2339dd318c019914b86ac29adbae5dd8a02d0307425e9bd85a296e94943708c72f8c670f0b7c50a890a5719088dbd9f1d062cad9acffa06df834106eebe1a4257ef00d----- +-----BEGIN PRIVATE KEY for 7188b234a8bf834f2e6258012aa09a2ab93178ffab9c789480275f61fe02cd1b9a58ddc63b79a73abea9e2b7ac5cac0b0d4324eff50aca2f0ec946b9ae6797511fa3ce461b57e77129cba8ab3b51147695d4ce889cbe67905f6586b4e4f22491----- +ZjFkYjBkMjE4NmJiZDEzOGQ4MTk5MzM3MDJlMmMxMmFkNDk5MTI0YzQ3N2Q0OGM3 +ZDM3MTM2N2E3MWZmMmM1Zg== +-----END PRIVATE KEY for 7188b234a8bf834f2e6258012aa09a2ab93178ffab9c789480275f61fe02cd1b9a58ddc63b79a73abea9e2b7ac5cac0b0d4324eff50aca2f0ec946b9ae6797511fa3ce461b57e77129cba8ab3b51147695d4ce889cbe67905f6586b4e4f22491----- diff --git a/multiversx_sdk_cli/tests/testdata/validators_ci.json b/multiversx_sdk_cli/tests/testdata/validators_ci.json deleted file mode 100644 index 99a390c6..00000000 --- a/multiversx_sdk_cli/tests/testdata/validators_ci.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "validators": [ - { - "pemFile": "~/work/mx-sdk-py-cli/mx-sdk-py-cli/localnet/validator00/config/validatorKey.pem" - }, - { - "pemFile": "~/work/mx-sdk-py-cli/mx-sdk-py-cli/localnet/validator01/config/validatorKey.pem" - }, - { - "pemFile": "~/work/mx-sdk-py-cli/mx-sdk-py-cli/localnet/validator02/config/validatorKey.pem" - } - ] -} diff --git a/multiversx_sdk_cli/tests/testdata/validators_ci.pem b/multiversx_sdk_cli/tests/testdata/validators_ci.pem new file mode 100644 index 00000000..3e598c61 --- /dev/null +++ b/multiversx_sdk_cli/tests/testdata/validators_ci.pem @@ -0,0 +1,12 @@ +-----BEGIN PRIVATE KEY for e7beaa95b3877f47348df4dd1cb578a4f7cabf7a20bfeefe5cdd263878ff132b765e04fef6f40c93512b666c47ed7719b8902f6c922c04247989b7137e837cc81a62e54712471c97a2ddab75aa9c2f58f813ed4c0fa722bde0ab718bff382208----- +N2NmZjk5YmQ2NzE1MDJkYjdkMTViYzhhYmMwYzlhODA0ZmI5MjU0MDZmYmRkNTBm +MWU0YzE3YTRjZDc3NDI0Nw== +-----END PRIVATE KEY for e7beaa95b3877f47348df4dd1cb578a4f7cabf7a20bfeefe5cdd263878ff132b765e04fef6f40c93512b666c47ed7719b8902f6c922c04247989b7137e837cc81a62e54712471c97a2ddab75aa9c2f58f813ed4c0fa722bde0ab718bff382208----- +-----BEGIN PRIVATE KEY for 78689fd4b1e2e434d567fe01e61598a42717d83124308266bd09ccc15d2339dd318c019914b86ac29adbae5dd8a02d0307425e9bd85a296e94943708c72f8c670f0b7c50a890a5719088dbd9f1d062cad9acffa06df834106eebe1a4257ef00d----- +ODA4NWJhMWQ3ZjdjM2RiOTM4YWQ3MDU5NWEyYmRhYjA5NjQ0ZjFlYzM4MDNiZTE3 +MWMzM2YxNGJjODBkNGUzYg== +-----END PRIVATE KEY for 78689fd4b1e2e434d567fe01e61598a42717d83124308266bd09ccc15d2339dd318c019914b86ac29adbae5dd8a02d0307425e9bd85a296e94943708c72f8c670f0b7c50a890a5719088dbd9f1d062cad9acffa06df834106eebe1a4257ef00d----- +-----BEGIN PRIVATE KEY for 7188b234a8bf834f2e6258012aa09a2ab93178ffab9c789480275f61fe02cd1b9a58ddc63b79a73abea9e2b7ac5cac0b0d4324eff50aca2f0ec946b9ae6797511fa3ce461b57e77129cba8ab3b51147695d4ce889cbe67905f6586b4e4f22491----- +ZjFkYjBkMjE4NmJiZDEzOGQ4MTk5MzM3MDJlMmMxMmFkNDk5MTI0YzQ3N2Q0OGM3 +ZDM3MTM2N2E3MWZmMmM1Zg== +-----END PRIVATE KEY for 7188b234a8bf834f2e6258012aa09a2ab93178ffab9c789480275f61fe02cd1b9a58ddc63b79a73abea9e2b7ac5cac0b0d4324eff50aca2f0ec946b9ae6797511fa3ce461b57e77129cba8ab3b51147695d4ce889cbe67905f6586b4e4f22491----- diff --git a/multiversx_sdk_cli/tests/testdata/validators_file.json b/multiversx_sdk_cli/tests/testdata/validators_file.json deleted file mode 100644 index f9fedd19..00000000 --- a/multiversx_sdk_cli/tests/testdata/validators_file.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "validators": [ - { - "pemFile": "validator_01.pem" - }, - { - "pemFile": "validator_02.pem" - } - ] -} diff --git a/multiversx_sdk_cli/tests/testdata/validators_file.pem b/multiversx_sdk_cli/tests/testdata/validators_file.pem new file mode 100644 index 00000000..14bc776d --- /dev/null +++ b/multiversx_sdk_cli/tests/testdata/validators_file.pem @@ -0,0 +1,8 @@ +-----BEGIN PRIVATE KEY for f8910e47cf9464777c912e6390758bb39715fffcb861b184017920e4a807b42553f2f21e7f3914b81bcf58b66a72ab16d97013ae1cff807cefc977ef8cbf116258534b9e46d19528042d16ef8374404a89b184e0a4ee18c77c49e454d04eae8d----- +N2MxOWJmM2EwYzU3Y2RkMWZiMDhlNDYwN2NlYmFhMzY0N2Q2YjkyNjFiNDY5M2Y2 +MWU5NmU1NGIyMThkNDQyYQ== +-----END PRIVATE KEY for f8910e47cf9464777c912e6390758bb39715fffcb861b184017920e4a807b42553f2f21e7f3914b81bcf58b66a72ab16d97013ae1cff807cefc977ef8cbf116258534b9e46d19528042d16ef8374404a89b184e0a4ee18c77c49e454d04eae8d----- +-----BEGIN PRIVATE KEY for 1b4e60e6d100cdf234d3427494dac55fbac49856cadc86bcb13a01b9bb05a0d9143e86c186c948e7ae9e52427c9523102efe9019a2a9c06db02993f2e3e6756576ae5a3ec7c235d548bc79de1a6990e1120ae435cb48f7fc436c9f9098b92a0d----- +MzAzNGIxZDU4NjI4YTg0Mjk4NGRhMGM3MGRhMGI1YTI1MWViYjJhZWJmNTFhZmM1 +YjU4NmUyODM5YjVlNTI2Mw== +-----END PRIVATE KEY for 1b4e60e6d100cdf234d3427494dac55fbac49856cadc86bcb13a01b9bb05a0d9143e86c186c948e7ae9e52427c9523102efe9019a2a9c06db02993f2e3e6756576ae5a3ec7c235d548bc79de1a6990e1120ae435cb48f7fc436c9f9098b92a0d----- diff --git a/multiversx_sdk_cli/transactions.py b/multiversx_sdk_cli/transactions.py index 2c58c4a7..0d8d9772 100644 --- a/multiversx_sdk_cli/transactions.py +++ b/multiversx_sdk_cli/transactions.py @@ -1,260 +1,171 @@ -import base64 import json import logging -import time -from typing import Any, Dict, List, Optional, Protocol, TextIO, Union - -from multiversx_sdk import (Address, Token, TokenComputer, TokenTransfer, - Transaction, TransactionsConverter, - TransactionsFactoryConfig, - TransferTransactionsFactory, TransactionComputer) +from typing import Any, Optional, Protocol, TextIO, Union + +from multiversx_sdk import ( + Address, + AwaitingOptions, + SmartContractResult, + TokenTransfer, + Transaction, + TransactionEvent, + TransactionLogs, + TransactionOnNetwork, + TransactionsFactoryConfig, + TransferTransactionsFactory, +) from multiversx_sdk_cli import errors -from multiversx_sdk_cli.accounts import Account, AccountBase, LedgerAccount -from multiversx_sdk_cli.cli_password import (load_guardian_password, - load_password) -from multiversx_sdk_cli.cosign_transaction import cosign_transaction -from multiversx_sdk_cli.errors import IncorrectWalletError, NoWalletProvided -from multiversx_sdk_cli.interfaces import ITransaction -from multiversx_sdk_cli.ledger.ledger_functions import do_get_ledger_address +from multiversx_sdk_cli.base_transactions_controller import BaseTransactionsController +from multiversx_sdk_cli.constants import MIN_GAS_LIMIT +from multiversx_sdk_cli.guardian_relayer_data import GuardianRelayerData +from multiversx_sdk_cli.interfaces import IAccount logger = logging.getLogger("transactions") -class ITransactionOnNetwork(Protocol): - hash: str - is_completed: Optional[bool] - - def to_dictionary(self) -> Dict[str, Any]: - ... +ONE_SECOND_IN_MILLISECONDS = 1000 +# fmt: off class INetworkProvider(Protocol): - def send_transaction(self, transaction: ITransaction) -> str: + def send_transaction(self, transaction: Transaction) -> bytes: ... - def get_transaction(self, tx_hash: str, with_process_status: Optional[bool] = False) -> ITransactionOnNetwork: + def get_transaction(self, transaction_hash: Union[bytes, str]) -> TransactionOnNetwork: ... - -def do_prepare_transaction(args: Any) -> Transaction: - account = load_sender_account_from_args(args) - - native_amount = int(args.value) - transfers = getattr(args, "token_transfers", []) - transfers = prepare_token_transfers(transfers) if transfers else None - - config = TransactionsFactoryConfig(args.chain) - factory = TransferTransactionsFactory(config) - receiver = Address.new_from_bech32(args.receiver) - - if native_amount or transfers: - tx = factory.create_transaction_for_transfer( - sender=account.address, - receiver=receiver, - native_amount=native_amount, - token_transfers=transfers, - data=str(args.data).encode() - ) - else: - # this is for transactions with no token transfers(egld/esdt); useful for setting the data field - tx = Transaction( - sender=account.address.to_bech32(), - receiver=receiver.to_bech32(), - data=str(args.data).encode(), - gas_limit=int(args.gas_limit), - chain_id=args.chain + def await_transaction_completed(self, transaction_hash: Union[bytes, str], options: Optional[AwaitingOptions] = None) -> TransactionOnNetwork: + ... +# fmt: on + + +class TransactionsController(BaseTransactionsController): + def __init__(self, chain_id: str) -> None: + config = TransactionsFactoryConfig(chain_id) + self.factory = TransferTransactionsFactory(config) + + def create_transaction( + self, + sender: IAccount, + receiver: Address, + native_amount: int, + gas_limit: int, + gas_price: int, + nonce: int, + version: int, + options: int, + guardian_and_relayer_data: GuardianRelayerData, + token_transfers: Optional[list[TokenTransfer]] = None, + data: Optional[str] = None, + ) -> Transaction: + # if no value, token transfers or data provided, create plain transaction + if not native_amount and not token_transfers and not data: + transaction = Transaction( + sender=sender.address, + receiver=receiver, + gas_limit=MIN_GAS_LIMIT, + chain_id=self.factory.config.chain_id, + ) + else: + transaction = self.factory.create_transaction_for_transfer( + sender=sender.address, + receiver=receiver, + native_amount=native_amount, + token_transfers=token_transfers, + data=data.encode() if data else None, + ) + + transaction.gas_price = gas_price + transaction.nonce = nonce + transaction.version = version + transaction.options = options + transaction.guardian = guardian_and_relayer_data.guardian_address + transaction.relayer = guardian_and_relayer_data.relayer_address + + self.add_extra_gas_limit_if_required(transaction) + + if gas_limit: + transaction.gas_limit = gas_limit + + self.sign_transaction( + transaction=transaction, + sender=sender, + guardian=guardian_and_relayer_data.guardian, + relayer=guardian_and_relayer_data.relayer, + guardian_service_url=guardian_and_relayer_data.guardian_service_url, + guardian_2fa_code=guardian_and_relayer_data.guardian_2fa_code, ) - tx.gas_limit = int(args.gas_limit) - tx.sender_username = getattr(args, "sender_username", None) or "" - tx.receiver_username = getattr(args, "receiver_username", None) or "" - tx.gas_price = int(args.gas_price) - tx.nonce = int(args.nonce) - tx.value = int(args.value) - tx.version = int(args.version) - tx.options = int(args.options) - - tx_computer = TransactionComputer() - if isinstance(account, LedgerAccount): - tx_computer.apply_options_for_hash_signing(tx) - - if args.guardian: - tx.guardian = args.guardian - - if args.relayer: - tx.relayer = args.relayer - - try: - relayer_account = load_relayer_account_from_args(args) - if relayer_account.address.to_bech32() != tx.relayer: - raise IncorrectWalletError("") - - if isinstance(relayer_account, LedgerAccount): - tx_computer.apply_options_for_hash_signing(tx) - - tx.relayer_signature = bytes.fromhex(relayer_account.sign_transaction(tx)) - except NoWalletProvided: - logger.warning("Relayer wallet not provided. Transaction will not be signed by relayer.") - except IncorrectWalletError: - raise IncorrectWalletError("Relayer wallet does not match the relayer's address set in the transaction.") - - try: - guardian_account = get_guardian_account_from_args(args) - if isinstance(guardian_account, LedgerAccount): - tx_computer.apply_options_for_hash_signing(tx) - - except NoWalletProvided: - guardian_account = None - - tx.signature = bytes.fromhex(account.sign_transaction(tx)) - tx = sign_tx_by_guardian(args, tx, guardian_account) - - return tx - - -def load_sender_account_from_args(args: Any) -> Account: - account = Account() - if args.ledger: - account = LedgerAccount(account_index=args.ledger_account_index, address_index=args.ledger_address_index) - if args.pem: - account = Account(pem_file=args.pem, pem_index=args.pem_index) - elif args.keyfile: - password = load_password(args) - account = Account(key_file=args.keyfile, password=password) - - return account - - -def load_relayer_account_from_args(args: Any) -> Account: - if args.relayer_ledger: - account = LedgerAccount(account_index=args.relayer_ledger_account_index, address_index=args.relayer_ledger_address_index) - if args.relayer_pem: - account = Account(pem_file=args.relayer_pem, pem_index=args.relayer_pem_index) - elif args.relayer_keyfile: - password = load_password(args) - account = Account(key_file=args.relayer_keyfile, password=password) - else: - raise errors.NoWalletProvided() - - return account - - -def prepare_token_transfers(transfers: List[Any]) -> List[TokenTransfer]: - token_computer = TokenComputer() - token_transfers: List[TokenTransfer] = [] + return transaction - for i in range(0, len(transfers) - 1, 2): - identifier = transfers[i] - amount = int(transfers[i + 1]) - nonce = token_computer.extract_nonce_from_extended_identifier(identifier) - token = Token(identifier, nonce) - transfer = TokenTransfer(token, amount) - token_transfers.append(transfer) - - return token_transfers - - -def sign_tx_by_guardian(args: Any, tx: Transaction, guardian_account: Union[AccountBase, None]) -> Transaction: - if guardian_account: - tx.guardian_signature = bytes.fromhex(guardian_account.sign_transaction(tx)) - elif args.guardian: - tx = cosign_transaction(tx, args.guardian_service_url, args.guardian_2fa_code) # type: ignore - - return tx - - -# TODO: this is duplicated code; a proper refactoring will come later -def get_guardian_account_from_args(args: Any): - if args.guardian_pem: - account = Account(pem_file=args.guardian_pem, pem_index=args.guardian_pem_index) - elif args.guardian_keyfile: - password = load_guardian_password(args) - account = Account(key_file=args.guardian_keyfile, password=password) - elif args.guardian_ledger: - address = do_get_ledger_address(account_index=args.guardian_ledger_account_index, address_index=args.guardian_ledger_address_index) - account = Account(address=Address.new_from_bech32(address)) - else: - raise errors.NoWalletProvided() - - return account - - -def send_and_wait_for_result(transaction: ITransaction, proxy: INetworkProvider, timeout: int) -> ITransactionOnNetwork: +def send_and_wait_for_result(transaction: Transaction, proxy: INetworkProvider, timeout: int) -> TransactionOnNetwork: if not transaction.signature: raise errors.TransactionIsNotSigned() - txOnNetwork = _send_transaction_and_wait_for_result(proxy, transaction, timeout) - return txOnNetwork - - -def _send_transaction_and_wait_for_result(proxy: INetworkProvider, payload: ITransaction, num_seconds_timeout: int = 100) -> ITransactionOnNetwork: - AWAIT_TRANSACTION_PERIOD = 5 - - tx_hash = proxy.send_transaction(payload) - num_periods_to_wait = int(num_seconds_timeout / AWAIT_TRANSACTION_PERIOD) - - for _ in range(0, num_periods_to_wait): - time.sleep(AWAIT_TRANSACTION_PERIOD) - - tx = proxy.get_transaction(tx_hash, True) - if tx.is_completed: - return tx - else: - logger.info("Transaction not yet done.") - - raise errors.KnownError("Took too long to get transaction.") - - -def tx_to_dictionary_as_inner_for_relayed_V1(tx: Transaction) -> Dict[str, Any]: - dictionary: Dict[str, Any] = {} - - dictionary["nonce"] = tx.nonce - dictionary["sender"] = base64.b64encode(Address.new_from_bech32(tx.sender).get_public_key()).decode() - dictionary["receiver"] = base64.b64encode(Address.new_from_bech32(tx.receiver).get_public_key()).decode() - dictionary["value"] = tx.value - dictionary["gasPrice"] = tx.gas_price - dictionary["gasLimit"] = tx.gas_limit - dictionary["data"] = base64.b64encode(tx.data).decode() - dictionary["signature"] = base64.b64encode(tx.signature).decode() - dictionary["chainID"] = base64.b64encode(tx.chain_id.encode()).decode() - dictionary["version"] = tx.version - - if tx.options: - dictionary["options"] = tx.options - - if tx.guardian: - guardian = Address.new_from_bech32(tx.guardian).to_hex() - dictionary["guardian"] = base64.b64encode(bytes.fromhex(guardian)).decode() + options = AwaitingOptions(timeout_in_milliseconds=timeout * ONE_SECOND_IN_MILLISECONDS) - if tx.guardian_signature: - dictionary["guardianSignature"] = base64.b64encode(tx.guardian_signature).decode() + tx_hash = proxy.send_transaction(transaction) + tx_on_network = proxy.await_transaction_completed(tx_hash, options) - if tx.sender_username: - dictionary["sndUserName"] = base64.b64encode(tx.sender_username.encode()).decode() - - if tx.receiver_username: - dictionary["rcvUserName"] = base64.b64encode(tx.receiver_username.encode()).decode() - - return dictionary - - -def _dict_to_json(dictionary: Dict[str, Any]) -> bytes: - serialized = json.dumps(dictionary, separators=(',', ':')).encode("utf8") - return serialized - - -def compute_relayed_v1_data(tx: Transaction) -> str: - inner_dictionary = tx_to_dictionary_as_inner_for_relayed_V1(tx) - serialized = _dict_to_json(inner_dictionary) - serialized_hex = serialized.hex() - return f"relayedTx@{serialized_hex}" + return tx_on_network def load_transaction_from_file(f: TextIO) -> Transaction: data_json: bytes = f.read().encode() transaction_dictionary = json.loads(data_json).get("tx") or json.loads(data_json).get("emittedTransaction") - - tx_converter = TransactionsConverter() - return tx_converter.dictionary_to_transaction(transaction_dictionary) + return Transaction.new_from_dictionary(transaction_dictionary) + + +def transaction_event_to_dictionary(event: TransactionEvent) -> dict[str, Any]: + return { + "address": event.address.to_bech32(), + "identifier": event.identifier, + "topics": [topic.hex() for topic in event.topics], + "data": event.data.decode(), + "additional_data": [data.hex() for data in event.additional_data], + } + + +def transaction_logs_to_dictionary(logs: TransactionLogs) -> dict[str, Any]: + return { + "address": logs.address.to_bech32(), + "events": [transaction_event_to_dictionary(event) for event in logs.events], + } + + +def smart_contract_result_to_dictionary(result: SmartContractResult) -> dict[str, Any]: + return { + "sender": result.sender.to_bech32(), + "receiver": result.receiver.to_bech32(), + "data": result.data.decode(), + "logs": transaction_logs_to_dictionary(result.logs), + } + + +def transaction_on_network_to_dictionary(tx: TransactionOnNetwork) -> dict[str, Any]: + return { + "sender": tx.sender.to_bech32(), + "receiver": tx.receiver.to_bech32(), + "hash": tx.hash.hex(), + "nonce": tx.nonce, + "round": tx.round, + "epoch": tx.epoch, + "timestamp": tx.timestamp, + "blockHash": tx.block_hash.hex(), + "miniBlockHash": tx.miniblock_hash.hex(), + "senderShard": tx.sender_shard, + "receiverShard": tx.receiver_shard, + "value": tx.value, + "gasLimit": tx.gas_limit, + "gasPrice": tx.gas_price, + "function": tx.function, + "data": tx.data.decode(), + "version": tx.version, + "options": tx.options, + "signature": tx.signature.hex(), + "status": tx.status.status, + "smartContractResults": [smart_contract_result_to_dictionary(res) for res in tx.smart_contract_results], + "logs": transaction_logs_to_dictionary(tx.logs), + } diff --git a/multiversx_sdk_cli/utils.py b/multiversx_sdk_cli/utils.py index a61e41fa..f15518a4 100644 --- a/multiversx_sdk_cli/utils.py +++ b/multiversx_sdk_cli/utils.py @@ -3,24 +3,21 @@ import os import pathlib import shutil -import stat import sys import tarfile import zipfile from pathlib import Path -from typing import (Any, Dict, List, Optional, Protocol, Union, - runtime_checkable) +from types import SimpleNamespace +from typing import Any, Optional, Protocol, Union, runtime_checkable import toml -from multiversx_sdk_cli import errors - logger = logging.getLogger("utils") @runtime_checkable class ISerializable(Protocol): - def to_dictionary(self) -> Dict[str, Any]: + def to_dictionary(self) -> dict[str, Any]: return self.__dict__ @@ -42,15 +39,15 @@ def default(self, o: Any) -> Any: return o.to_dictionary() if isinstance(o, bytes): return o.hex() + # needed because sdk-py returns SimpleNamespace objects that the json library does not know to serialize + if isinstance(o, SimpleNamespace): + return o.__dict__ return super().default(o) -def omit_fields(data: Any, fields: List[str] = []): - if isinstance(data, dict): - for field in fields: - data.pop(field, None) - return data - raise errors.ProgrammingError("omit_fields: only dictionaries are supported.") +def omit_fields(data: dict[str, Any], fields: list[str] = []) -> None: + for field in fields: + data.pop(field, None) def untar(archive_path: Path, destination_folder: Path) -> None: @@ -78,7 +75,7 @@ def ensure_folder(folder: Union[str, Path]): pathlib.Path(folder).mkdir(parents=True, exist_ok=True) -def read_lines(file: Path) -> List[str]: +def read_lines(file: Path) -> list[str]: with open(file) as f: lines = f.readlines() lines = [line.strip() for line in lines] @@ -86,22 +83,6 @@ def read_lines(file: Path) -> List[str]: return lines -def read_binary_file(path: Path) -> bytes: - try: - with open(path, 'rb') as binary_file: - return binary_file.read() - except Exception as err: - raise errors.BadFile(str(path), err) from None - - -def read_text_file(path: Path) -> str: - try: - with open(path, 'r') as text_file: - return text_file.read() - except Exception as err: - raise errors.BadFile(str(path), err) from None - - def write_file(file_path: Path, text: str): with open(file_path, "w") as file: return file.write(text) @@ -134,24 +115,13 @@ def dump_out_json(data: Any, outfile: Any = None): outfile.write("\n") -def prettify_json_file(filename: str): - data = read_json_file(filename) - write_json_file(filename, data) - - -def get_subfolders(folder: Path) -> List[str]: +def get_subfolders(folder: Path) -> list[str]: return [item.name for item in os.scandir(folder) if item.is_dir() and not item.name.startswith(".")] -def mark_executable(file: str) -> None: - logger.debug(f"Mark [{file}] as executable") - st = os.stat(file) - os.chmod(file, st.st_mode | stat.S_IEXEC) - - -def list_files(folder: Path, suffix: Optional[str] = None) -> List[Path]: +def list_files(folder: Path, suffix: Optional[str] = None) -> list[Path]: folder = folder.expanduser() - files: List[Path] = [folder / file for file in os.listdir(folder)] + files: list[Path] = [folder / file for file in os.listdir(folder)] files = [file for file in files if file.is_file()] if suffix: @@ -170,16 +140,7 @@ def symlink(real: str, link: str) -> None: os.symlink(real, link) -def as_object(data: Object) -> Object: - if isinstance(data, dict): - result = Object() - result.__dict__.update(data) - return result - - return data - - -def is_arg_present(args: List[str], key: str) -> bool: +def is_arg_present(args: list[str], key: str) -> bool: for arg in args: if arg.find("--data") != -1: continue @@ -189,24 +150,6 @@ def is_arg_present(args: List[str], key: str) -> bool: return False -def str_int_to_hex_str(number_str: str) -> str: - num_of_bytes = 1 - if len(number_str) > 2: - num_of_bytes = int(len(number_str) / 2) - int_str = int(number_str) - int_bytes = int_str.to_bytes(num_of_bytes, byteorder="big") - bytes_str = int_bytes.hex() - return bytes_str - - -def parse_keys(bls_public_keys: str): - keys = bls_public_keys.split(',') - parsed_keys = '' - for key in keys: - parsed_keys += '@' + key - return parsed_keys, len(keys) - - def log_explorer(chain: str, name: str, path: str, details: str): networks = { "1": ("MultiversX Mainnet Explorer", "https://explorer.multiversx.com"), diff --git a/multiversx_sdk_cli/validators.py b/multiversx_sdk_cli/validators.py new file mode 100644 index 00000000..74796834 --- /dev/null +++ b/multiversx_sdk_cli/validators.py @@ -0,0 +1,537 @@ +from typing import Optional + +from multiversx_sdk import ( + Address, + Transaction, + TransactionsFactoryConfig, + ValidatorPublicKey, + ValidatorsSigners, + ValidatorsTransactionsFactory, +) + +from multiversx_sdk_cli.base_transactions_controller import BaseTransactionsController +from multiversx_sdk_cli.guardian_relayer_data import GuardianRelayerData +from multiversx_sdk_cli.interfaces import IAccount +from multiversx_sdk_cli.transactions import TransactionsController + + +class ValidatorsController(BaseTransactionsController): + def __init__(self, chain_id: str) -> None: + self.transactions_controller = TransactionsController(chain_id) + self.factory = ValidatorsTransactionsFactory(TransactionsFactoryConfig(chain_id)) + + def create_transaction_for_staking( + self, + sender: IAccount, + validators: ValidatorsSigners, + native_amount: int, + gas_limit: int, + gas_price: int, + nonce: int, + version: int, + options: int, + guardian_and_relayer_data: GuardianRelayerData, + rewards_address: Optional[Address] = None, + ) -> Transaction: + transaction = self.factory.create_transaction_for_staking( + sender=sender.address, + validators_file=validators, + amount=native_amount, + rewards_address=rewards_address, + ) + transaction.gas_price = gas_price + transaction.nonce = nonce + transaction.version = version + transaction.options = options + transaction.guardian = guardian_and_relayer_data.guardian_address + transaction.relayer = guardian_and_relayer_data.relayer_address + + self.add_extra_gas_limit_if_required(transaction) + + if gas_limit: + transaction.gas_limit = gas_limit + + self.sign_transaction( + transaction=transaction, + sender=sender, + guardian=guardian_and_relayer_data.guardian, + relayer=guardian_and_relayer_data.relayer, + guardian_service_url=guardian_and_relayer_data.guardian_service_url, + guardian_2fa_code=guardian_and_relayer_data.guardian_2fa_code, + ) + + return transaction + + def create_transaction_for_topping_up( + self, + sender: IAccount, + native_amount: int, + gas_limit: int, + gas_price: int, + nonce: int, + version: int, + options: int, + guardian_and_relayer_data: GuardianRelayerData, + ) -> Transaction: + transaction = self.factory.create_transaction_for_topping_up( + sender=sender.address, + amount=native_amount, + ) + transaction.gas_price = gas_price + transaction.nonce = nonce + transaction.version = version + transaction.options = options + transaction.guardian = guardian_and_relayer_data.guardian_address + transaction.relayer = guardian_and_relayer_data.relayer_address + + self.add_extra_gas_limit_if_required(transaction) + + if gas_limit: + transaction.gas_limit = gas_limit + + self.sign_transaction( + transaction=transaction, + sender=sender, + guardian=guardian_and_relayer_data.guardian, + relayer=guardian_and_relayer_data.relayer, + guardian_service_url=guardian_and_relayer_data.guardian_service_url, + guardian_2fa_code=guardian_and_relayer_data.guardian_2fa_code, + ) + + return transaction + + def create_transaction_for_unstaking( + self, + sender: IAccount, + keys: list[ValidatorPublicKey], + native_amount: int, + gas_limit: int, + gas_price: int, + nonce: int, + version: int, + options: int, + guardian_and_relayer_data: GuardianRelayerData, + ) -> Transaction: + transaction = self.factory.create_transaction_for_unstaking( + sender=sender.address, + public_keys=keys, + ) + transaction.value = native_amount + transaction.gas_price = gas_price + transaction.nonce = nonce + transaction.version = version + transaction.options = options + transaction.guardian = guardian_and_relayer_data.guardian_address + transaction.relayer = guardian_and_relayer_data.relayer_address + + self.add_extra_gas_limit_if_required(transaction) + + if gas_limit: + transaction.gas_limit = gas_limit + + self.sign_transaction( + transaction=transaction, + sender=sender, + guardian=guardian_and_relayer_data.guardian, + relayer=guardian_and_relayer_data.relayer, + guardian_service_url=guardian_and_relayer_data.guardian_service_url, + guardian_2fa_code=guardian_and_relayer_data.guardian_2fa_code, + ) + + return transaction + + def create_transaction_for_unjailing( + self, + sender: IAccount, + keys: list[ValidatorPublicKey], + native_amount: int, + gas_limit: int, + gas_price: int, + nonce: int, + version: int, + options: int, + guardian_and_relayer_data: GuardianRelayerData, + ) -> Transaction: + transaction = self.factory.create_transaction_for_unjailing( + sender=sender.address, + public_keys=keys, + amount=native_amount, + ) + transaction.gas_price = gas_price + transaction.nonce = nonce + transaction.version = version + transaction.options = options + transaction.guardian = guardian_and_relayer_data.guardian_address + transaction.relayer = guardian_and_relayer_data.relayer_address + + self.add_extra_gas_limit_if_required(transaction) + + if gas_limit: + transaction.gas_limit = gas_limit + + self.sign_transaction( + transaction=transaction, + sender=sender, + guardian=guardian_and_relayer_data.guardian, + relayer=guardian_and_relayer_data.relayer, + guardian_service_url=guardian_and_relayer_data.guardian_service_url, + guardian_2fa_code=guardian_and_relayer_data.guardian_2fa_code, + ) + + return transaction + + def create_transaction_for_unbonding( + self, + sender: IAccount, + keys: list[ValidatorPublicKey], + native_amount: int, + gas_limit: int, + gas_price: int, + nonce: int, + version: int, + options: int, + guardian_and_relayer_data: GuardianRelayerData, + ) -> Transaction: + transaction = self.factory.create_transaction_for_unbonding( + sender=sender.address, + public_keys=keys, + ) + transaction.value = native_amount + transaction.gas_price = gas_price + transaction.nonce = nonce + transaction.version = version + transaction.options = options + transaction.guardian = guardian_and_relayer_data.guardian_address + transaction.relayer = guardian_and_relayer_data.relayer_address + + self.add_extra_gas_limit_if_required(transaction) + + if gas_limit: + transaction.gas_limit = gas_limit + + self.sign_transaction( + transaction=transaction, + sender=sender, + guardian=guardian_and_relayer_data.guardian, + relayer=guardian_and_relayer_data.relayer, + guardian_service_url=guardian_and_relayer_data.guardian_service_url, + guardian_2fa_code=guardian_and_relayer_data.guardian_2fa_code, + ) + + return transaction + + def create_transaction_for_changing_rewards_address( + self, + sender: IAccount, + rewards_address: Address, + native_amount: int, + gas_limit: int, + gas_price: int, + nonce: int, + version: int, + options: int, + guardian_and_relayer_data: GuardianRelayerData, + ) -> Transaction: + transaction = self.factory.create_transaction_for_changing_rewards_address( + sender=sender.address, + rewards_address=rewards_address, + ) + transaction.value = native_amount + transaction.gas_price = gas_price + transaction.nonce = nonce + transaction.version = version + transaction.options = options + transaction.guardian = guardian_and_relayer_data.guardian_address + transaction.relayer = guardian_and_relayer_data.relayer_address + + self.add_extra_gas_limit_if_required(transaction) + + if gas_limit: + transaction.gas_limit = gas_limit + + self.sign_transaction( + transaction=transaction, + sender=sender, + guardian=guardian_and_relayer_data.guardian, + relayer=guardian_and_relayer_data.relayer, + guardian_service_url=guardian_and_relayer_data.guardian_service_url, + guardian_2fa_code=guardian_and_relayer_data.guardian_2fa_code, + ) + + return transaction + + def create_transaction_for_claiming( + self, + sender: IAccount, + native_amount: int, + gas_limit: int, + gas_price: int, + nonce: int, + version: int, + options: int, + guardian_and_relayer_data: GuardianRelayerData, + ) -> Transaction: + transaction = self.factory.create_transaction_for_claiming( + sender=sender.address, + ) + transaction.value = native_amount + transaction.gas_price = gas_price + transaction.nonce = nonce + transaction.version = version + transaction.options = options + transaction.guardian = guardian_and_relayer_data.guardian_address + transaction.relayer = guardian_and_relayer_data.relayer_address + + self.add_extra_gas_limit_if_required(transaction) + + if gas_limit: + transaction.gas_limit = gas_limit + + self.sign_transaction( + transaction=transaction, + sender=sender, + guardian=guardian_and_relayer_data.guardian, + relayer=guardian_and_relayer_data.relayer, + guardian_service_url=guardian_and_relayer_data.guardian_service_url, + guardian_2fa_code=guardian_and_relayer_data.guardian_2fa_code, + ) + + return transaction + + def create_transaction_for_unstaking_nodes( + self, + sender: IAccount, + keys: list[ValidatorPublicKey], + native_amount: int, + gas_limit: int, + gas_price: int, + nonce: int, + version: int, + options: int, + guardian_and_relayer_data: GuardianRelayerData, + ) -> Transaction: + transaction = self.factory.create_transaction_for_unstaking_nodes( + sender=sender.address, + public_keys=keys, + ) + transaction.value = native_amount + transaction.gas_price = gas_price + transaction.nonce = nonce + transaction.version = version + transaction.options = options + transaction.guardian = guardian_and_relayer_data.guardian_address + transaction.relayer = guardian_and_relayer_data.relayer_address + + self.add_extra_gas_limit_if_required(transaction) + + if gas_limit: + transaction.gas_limit = gas_limit + + self.sign_transaction( + transaction=transaction, + sender=sender, + guardian=guardian_and_relayer_data.guardian, + relayer=guardian_and_relayer_data.relayer, + guardian_service_url=guardian_and_relayer_data.guardian_service_url, + guardian_2fa_code=guardian_and_relayer_data.guardian_2fa_code, + ) + + return transaction + + def create_transaction_for_unstaking_tokens( + self, + sender: IAccount, + value: int, + native_amount: int, + gas_limit: int, + gas_price: int, + nonce: int, + version: int, + options: int, + guardian_and_relayer_data: GuardianRelayerData, + ) -> Transaction: + transaction = self.factory.create_transaction_for_unstaking_tokens( + sender=sender.address, + amount=value, + ) + transaction.value = native_amount + transaction.gas_price = gas_price + transaction.nonce = nonce + transaction.version = version + transaction.options = options + transaction.guardian = guardian_and_relayer_data.guardian_address + transaction.relayer = guardian_and_relayer_data.relayer_address + + self.add_extra_gas_limit_if_required(transaction) + + if gas_limit: + transaction.gas_limit = gas_limit + + self.sign_transaction( + transaction=transaction, + sender=sender, + guardian=guardian_and_relayer_data.guardian, + relayer=guardian_and_relayer_data.relayer, + guardian_service_url=guardian_and_relayer_data.guardian_service_url, + guardian_2fa_code=guardian_and_relayer_data.guardian_2fa_code, + ) + + return transaction + + def create_transaction_for_unbonding_nodes( + self, + sender: IAccount, + keys: list[ValidatorPublicKey], + native_amount: int, + gas_limit: int, + gas_price: int, + nonce: int, + version: int, + options: int, + guardian_and_relayer_data: GuardianRelayerData, + ) -> Transaction: + transaction = self.factory.create_transaction_for_unbonding_nodes( + sender=sender.address, + public_keys=keys, + ) + transaction.value = native_amount + transaction.gas_price = gas_price + transaction.nonce = nonce + transaction.version = version + transaction.options = options + transaction.guardian = guardian_and_relayer_data.guardian_address + transaction.relayer = guardian_and_relayer_data.relayer_address + + self.add_extra_gas_limit_if_required(transaction) + + if gas_limit: + transaction.gas_limit = gas_limit + + self.sign_transaction( + transaction=transaction, + sender=sender, + guardian=guardian_and_relayer_data.guardian, + relayer=guardian_and_relayer_data.relayer, + guardian_service_url=guardian_and_relayer_data.guardian_service_url, + guardian_2fa_code=guardian_and_relayer_data.guardian_2fa_code, + ) + + return transaction + + def create_transaction_for_unbonding_tokens( + self, + sender: IAccount, + value: int, + native_amount: int, + gas_limit: int, + gas_price: int, + nonce: int, + version: int, + options: int, + guardian_and_relayer_data: GuardianRelayerData, + ) -> Transaction: + transaction = self.factory.create_transaction_for_unbonding_tokens( + sender=sender.address, + amount=value, + ) + transaction.value = native_amount + transaction.gas_price = gas_price + transaction.nonce = nonce + transaction.version = version + transaction.options = options + transaction.guardian = guardian_and_relayer_data.guardian_address + transaction.relayer = guardian_and_relayer_data.relayer_address + + self.add_extra_gas_limit_if_required(transaction) + + if gas_limit: + transaction.gas_limit = gas_limit + + self.sign_transaction( + transaction=transaction, + sender=sender, + guardian=guardian_and_relayer_data.guardian, + relayer=guardian_and_relayer_data.relayer, + guardian_service_url=guardian_and_relayer_data.guardian_service_url, + guardian_2fa_code=guardian_and_relayer_data.guardian_2fa_code, + ) + + return transaction + + def create_transaction_for_cleaning_registered_data( + self, + sender: IAccount, + native_amount: int, + gas_limit: int, + gas_price: int, + nonce: int, + version: int, + options: int, + guardian_and_relayer_data: GuardianRelayerData, + ) -> Transaction: + transaction = self.factory.create_transaction_for_cleaning_registered_data( + sender=sender.address, + ) + transaction.value = native_amount + transaction.gas_price = gas_price + transaction.nonce = nonce + transaction.version = version + transaction.options = options + transaction.guardian = guardian_and_relayer_data.guardian_address + transaction.relayer = guardian_and_relayer_data.relayer_address + + self.add_extra_gas_limit_if_required(transaction) + + if gas_limit: + transaction.gas_limit = gas_limit + + self.sign_transaction( + transaction=transaction, + sender=sender, + guardian=guardian_and_relayer_data.guardian, + relayer=guardian_and_relayer_data.relayer, + guardian_service_url=guardian_and_relayer_data.guardian_service_url, + guardian_2fa_code=guardian_and_relayer_data.guardian_2fa_code, + ) + + return transaction + + def create_transaction_for_restaking_unstaked_nodes( + self, + sender: IAccount, + keys: list[ValidatorPublicKey], + native_amount: int, + gas_limit: int, + gas_price: int, + nonce: int, + version: int, + options: int, + guardian_and_relayer_data: GuardianRelayerData, + ) -> Transaction: + transaction = self.factory.create_transaction_for_restaking_unstaked_nodes( + sender=sender.address, + public_keys=keys, + ) + transaction.value = native_amount + transaction.gas_price = gas_price + transaction.nonce = nonce + transaction.version = version + transaction.options = options + transaction.guardian = guardian_and_relayer_data.guardian_address + transaction.relayer = guardian_and_relayer_data.relayer_address + + self.add_extra_gas_limit_if_required(transaction) + + if gas_limit: + transaction.gas_limit = gas_limit + + self.sign_transaction( + transaction=transaction, + sender=sender, + guardian=guardian_and_relayer_data.guardian, + relayer=guardian_and_relayer_data.relayer, + guardian_service_url=guardian_and_relayer_data.guardian_service_url, + guardian_2fa_code=guardian_and_relayer_data.guardian_2fa_code, + ) + + return transaction diff --git a/multiversx_sdk_cli/validators/__init__.py b/multiversx_sdk_cli/validators/__init__.py deleted file mode 100644 index 74a11e54..00000000 --- a/multiversx_sdk_cli/validators/__init__.py +++ /dev/null @@ -1,23 +0,0 @@ -from multiversx_sdk_cli.validators.core import ( - prepare_args_for_change_reward_address, prepare_args_for_claim, - prepare_args_for_clean_registered_data, - prepare_args_for_restake_unstaked_nodes, prepare_args_for_stake, - prepare_args_for_unbond, prepare_args_for_unbond_nodes, - prepare_args_for_unbond_tokens, prepare_args_for_unjail, - prepare_args_for_unstake, prepare_args_for_unstake_nodes, - prepare_args_for_unstake_tokens) -from multiversx_sdk_cli.validators.validators_file import ValidatorsFile - -__all__ = ["prepare_args_for_stake", - "prepare_args_for_unstake", - "prepare_args_for_unbond", - "prepare_args_for_unjail", - "prepare_args_for_change_reward_address", - "prepare_args_for_claim", - "prepare_args_for_unstake_nodes", - "prepare_args_for_unstake_tokens", - "prepare_args_for_unbond_nodes", - "prepare_args_for_unbond_tokens", - "prepare_args_for_clean_registered_data", - "prepare_args_for_restake_unstaked_nodes", - "ValidatorsFile"] diff --git a/multiversx_sdk_cli/validators/core.py b/multiversx_sdk_cli/validators/core.py deleted file mode 100644 index f71d64ee..00000000 --- a/multiversx_sdk_cli/validators/core.py +++ /dev/null @@ -1,184 +0,0 @@ -import logging -from pathlib import Path -from typing import Any, List, Tuple, Union - -from multiversx_sdk import Address, ValidatorPEM, ValidatorSigner - -from multiversx_sdk_cli import utils -from multiversx_sdk_cli.accounts import Account -from multiversx_sdk_cli.cli_password import load_password -from multiversx_sdk_cli.config import (GAS_PER_DATA_BYTE, MIN_GAS_LIMIT, - MetaChainSystemSCsCost, get_address_hrp) -from multiversx_sdk_cli.contracts import prepare_execute_transaction_data -from multiversx_sdk_cli.errors import BadUsage -from multiversx_sdk_cli.validators.validators_file import ValidatorsFile - -logger = logging.getLogger("validators") - -VALIDATORS_SMART_CONTRACT_ADDRESS_HEX = "000000000000000000010000000000000000000000000000000000000001ffff" - - -def prepare_args_for_stake(args: Any): - if args.top_up: - prepare_args_for_top_up(args) - return - - if args.pem: - node_operator = Account(pem_file=args.pem) - elif args.keyfile: - password = load_password(args) - node_operator = Account(key_file=args.keyfile, password=password) - else: - raise BadUsage("cannot initialize node operator") - - validators_file_path = Path(args.validators_file) - reward_address = Address.new_from_bech32(args.reward_address) if args.reward_address else None - - data, gas_limit = prepare_transaction_data_for_stake(node_operator.address, validators_file_path, reward_address) - args.data = data - args.receiver = Address.new_from_hex(VALIDATORS_SMART_CONTRACT_ADDRESS_HEX, get_address_hrp()).to_bech32() - - if args.estimate_gas: - args.gas_limit = gas_limit - - -def prepare_transaction_data_for_stake(node_operator_address: Address, validators_file_path: Path, reward_address: Union[Address, None]) -> Tuple[str, int]: - validators_file = ValidatorsFile(validators_file_path) - num_of_nodes = validators_file.get_num_of_nodes() - validators_list = validators_file.get_validators_list() - - call_arguments: List[Any] = [] - call_arguments.append(num_of_nodes) - - for validator in validators_list: - # Get path of "pemFile", make it absolute - validator_pem = Path(validator.get("pemFile")).expanduser() - validator_pem = validator_pem if validator_pem.is_absolute() else validators_file_path.parent / validator_pem - - pem_file = ValidatorPEM.from_file(validator_pem) - - validator_signer = ValidatorSigner(pem_file.secret_key) - signed_message = validator_signer.sign(node_operator_address.pubkey).hex() - - call_arguments.append(f"0x{pem_file.secret_key.generate_public_key().hex()}") - call_arguments.append(f"0x{signed_message}") - - if reward_address: - call_arguments.append(f"0x{reward_address.to_hex()}") - - data = prepare_execute_transaction_data("stake", call_arguments) - gas_limit = estimate_system_sc_call(str(data), MetaChainSystemSCsCost.STAKE, num_of_nodes) - - return str(data), gas_limit - - -def prepare_args_for_top_up(args: Any): - args.data = 'stake' - args.receiver = Address.new_from_hex(VALIDATORS_SMART_CONTRACT_ADDRESS_HEX, get_address_hrp()).to_bech32() - - if args.estimate_gas: - args.gas_limit = estimate_system_sc_call(args.data, MetaChainSystemSCsCost.STAKE, 1) - - -def prepare_args_for_unstake(args: Any): - parsed_keys, num_keys = utils.parse_keys(args.nodes_public_keys) - args.data = 'unStake' + parsed_keys - args.receiver = Address.new_from_hex(VALIDATORS_SMART_CONTRACT_ADDRESS_HEX, get_address_hrp()).to_bech32() - - if args.estimate_gas: - args.gas_limit = estimate_system_sc_call(args.data, MetaChainSystemSCsCost.UNSTAKE, num_keys) - - -def prepare_args_for_unbond(args: Any): - parsed_keys, num_keys = utils.parse_keys(args.nodes_public_keys) - args.data = 'unBond' + parsed_keys - args.receiver = Address.new_from_hex(VALIDATORS_SMART_CONTRACT_ADDRESS_HEX, get_address_hrp()).to_bech32() - - if args.estimate_gas: - args.gas_limit = estimate_system_sc_call(args.data, MetaChainSystemSCsCost.UNBOND, num_keys) - - -def prepare_args_for_unjail(args: Any): - parsed_keys, num_keys = utils.parse_keys(args.nodes_public_keys) - args.data = 'unJail' + parsed_keys - args.receiver = Address.new_from_hex(VALIDATORS_SMART_CONTRACT_ADDRESS_HEX, get_address_hrp()).to_bech32() - - if args.estimate_gas: - args.gas_limit = estimate_system_sc_call(args.data, MetaChainSystemSCsCost.UNJAIL, num_keys) - - -def prepare_args_for_change_reward_address(args: Any): - reward_address = Address.new_from_bech32(args.reward_address) - args.data = 'changeRewardAddress@' + reward_address.hex() - args.receiver = Address.new_from_hex(VALIDATORS_SMART_CONTRACT_ADDRESS_HEX, get_address_hrp()).to_bech32() - - if args.estimate_gas: - args.gas_limit = estimate_system_sc_call(args.data, MetaChainSystemSCsCost.CHANGE_REWARD_ADDRESS) - - -def prepare_args_for_claim(args: Any): - args.data = 'claim' - args.receiver = Address.new_from_hex(VALIDATORS_SMART_CONTRACT_ADDRESS_HEX, get_address_hrp()).to_bech32() - - if args.estimate_gas: - args.gas_limit = estimate_system_sc_call(args.data, MetaChainSystemSCsCost.CLAIM) - - -def prepare_args_for_unstake_nodes(args: Any): - parsed_keys, num_keys = utils.parse_keys(args.nodes_public_keys) - args.data = 'unStakeNodes' + parsed_keys - - args.receiver = Address.new_from_hex(VALIDATORS_SMART_CONTRACT_ADDRESS_HEX, get_address_hrp()).to_bech32() - if args.estimate_gas: - args.gas_limit = estimate_system_sc_call(args.data, MetaChainSystemSCsCost.UNSTAKE, num_keys) - - -def prepare_args_for_unstake_tokens(args: Any): - args.data = 'unStakeTokens' - args.data += '@' + utils.str_int_to_hex_str(str(args.unstake_value)) - - args.receiver = Address.new_from_hex(VALIDATORS_SMART_CONTRACT_ADDRESS_HEX, get_address_hrp()).to_bech32() - if args.estimate_gas: - args.gas_limit = estimate_system_sc_call(args.data, MetaChainSystemSCsCost.UNSTAKE_TOKENS) - - -def prepare_args_for_unbond_nodes(args: Any): - parsed_keys, num_keys = utils.parse_keys(args.nodes_public_keys) - args.data = 'unBondNodes' + parsed_keys - - args.receiver = Address.new_from_hex(VALIDATORS_SMART_CONTRACT_ADDRESS_HEX, get_address_hrp()).to_bech32() - if args.estimate_gas: - args.gas_limit = estimate_system_sc_call(args.data, MetaChainSystemSCsCost.UNBOND, num_keys) - - -def prepare_args_for_unbond_tokens(args: Any): - args.data = 'unBondTokens' - args.data += '@' + utils.str_int_to_hex_str(str(args.unbond_value)) - - args.receiver = Address.new_from_hex(VALIDATORS_SMART_CONTRACT_ADDRESS_HEX, get_address_hrp()).to_bech32() - if args.estimate_gas: - args.gas_limit = estimate_system_sc_call(args.data, MetaChainSystemSCsCost.UNBOND_TOKENS) - - -def prepare_args_for_clean_registered_data(args: Any): - args.data = 'cleanRegisteredData' - - args.receiver = Address.new_from_hex(VALIDATORS_SMART_CONTRACT_ADDRESS_HEX, get_address_hrp()).to_bech32() - if args.estimate_gas: - args.gas_limit = estimate_system_sc_call(args.data, MetaChainSystemSCsCost.STAKE) - - -def prepare_args_for_restake_unstaked_nodes(args: Any): - parsed_keys, num_keys = utils.parse_keys(args.nodes_public_keys) - args.data = 'reStakeUnStakedNodes' + parsed_keys - - args.receiver = Address.new_from_hex(VALIDATORS_SMART_CONTRACT_ADDRESS_HEX, get_address_hrp()).to_bech32() - if args.estimate_gas: - args.gas_limit = estimate_system_sc_call(args.data, MetaChainSystemSCsCost.STAKE, num_keys) - - -def estimate_system_sc_call(transaction_data: str, base_cost: int, factor: int = 1): - num_bytes = len(transaction_data) - gas_limit = MIN_GAS_LIMIT + num_bytes * GAS_PER_DATA_BYTE - gas_limit += factor * base_cost - return gas_limit diff --git a/multiversx_sdk_cli/validators/validators_file.py b/multiversx_sdk_cli/validators/validators_file.py deleted file mode 100644 index ab10535a..00000000 --- a/multiversx_sdk_cli/validators/validators_file.py +++ /dev/null @@ -1,55 +0,0 @@ -import json -from pathlib import Path -from typing import Dict, List - -from multiversx_sdk import ValidatorPEM, ValidatorPublicKey, ValidatorSigner - -from multiversx_sdk_cli import guards -from multiversx_sdk_cli.errors import CannotReadValidatorsData - - -class ValidatorsFile: - def __init__(self, validators_file_path: Path): - self.validators_file_path = validators_file_path - self._validators_data = self._read_json_file_validators() - - def get_num_of_nodes(self) -> int: - return len(self._validators_data.get("validators", [])) - - def get_validators_list(self): - return self._validators_data.get("validators", []) - - def load_signers(self) -> List[ValidatorSigner]: - signers: List[ValidatorSigner] = [] - for validator in self.get_validators_list(): - pem_file = self._load_validator_pem(validator) - validator_signer = ValidatorSigner(pem_file.secret_key) - signers.append(validator_signer) - - return signers - - def load_public_keys(self) -> List[ValidatorPublicKey]: - public_keys: List[ValidatorPublicKey] = [] - - for validator in self.get_validators_list(): - pem_file = self._load_validator_pem(validator) - public_keys.append(pem_file.secret_key.generate_public_key()) - - return public_keys - - def _load_validator_pem(self, validator: Dict[str, str]) -> ValidatorPEM: - # Get path of "pemFile", make it absolute - validator_pem = Path(validator.get("pemFile", "")).expanduser() - validator_pem = validator_pem if validator_pem.is_absolute() else self.validators_file_path.parent / validator_pem - - return ValidatorPEM.from_file(validator_pem) - - def _read_json_file_validators(self): - val_file = self.validators_file_path.expanduser() - guards.is_file(val_file) - with open(self.validators_file_path, "r") as json_file: - try: - data = json.load(json_file) - except Exception: - raise CannotReadValidatorsData() - return data diff --git a/multiversx_sdk_cli/workstation.py b/multiversx_sdk_cli/workstation.py index af1b5142..26c23584 100644 --- a/multiversx_sdk_cli/workstation.py +++ b/multiversx_sdk_cli/workstation.py @@ -30,7 +30,7 @@ def get_platform(): "darwin": "osx", "win32": "windows", "cygwin": "windows", - "msys": "windows" + "msys": "windows", } platform = platforms.get(sys.platform) diff --git a/mxpy-up.py b/mxpy-up.py deleted file mode 100644 index 24177d3a..00000000 --- a/mxpy-up.py +++ /dev/null @@ -1,387 +0,0 @@ -import logging -import os -import os.path -import shutil -import stat -import subprocess -import sys -from argparse import ArgumentParser -from pathlib import Path -from typing import List, Tuple - -logger = logging.getLogger("installer") - -MIN_REQUIRED_PYTHON_VERSION = (3, 8, 0) -sdk_path = Path("~/multiversx-sdk").expanduser().resolve() - - -def main(): - parser = ArgumentParser() - parser.add_argument("--exact-version", help="the exact version of mxpy to install") - parser.add_argument("--from-branch", help="use a branch of multiversx/mx-sdk-py-cli") - parser.add_argument("--not-interactive", action="store_true", default=False) - parser.add_argument("--verbose", action="store_true", default=False) - parser.add_argument("--ignore-deprecation", action="store_true", default=False, help="'mxpy-up.py' is obsolete, install using 'pipx': https://docs.multiversx.com/sdk-and-tools/sdk-py/installing-mxpy/#install-using-pipx") - parser.set_defaults(modify_path=True) - args = parser.parse_args() - - logger.warning("'mxpy-up.py' is deprecated. Check out the documentation on how to install using `pipx`: https://docs.multiversx.com/sdk-and-tools/sdk-py/installing-mxpy/#install-using-pipx.") - - if not args.ignore_deprecation: - raise Exception("'mxpy-up.py' is deprecated, please install using `pipx`: https://docs.multiversx.com/sdk-and-tools/sdk-py/installing-mxpy/#install-using-pipx. If installing using 'mxpy-up` is necessary, provide the `--ignore-deprecation` flag.") - - exact_version = args.exact_version - from_branch = args.from_branch - interactive = not args.not_interactive - verbose = args.verbose - - log_level = logging.DEBUG if verbose else logging.INFO - logging.basicConfig(level=log_level) - - if get_operating_system() == "windows": - print(""" -IMPORTANT NOTE -============== - -Windows support is limited and experimental. -""") - confirm_continuation(interactive) - - guard_non_root_user() - guard_python_version() - migrate_v6(interactive) - - # In case of a fresh install: - sdk_path.mkdir(parents=True, exist_ok=True) - create_venv() - logger.info("Installing the necessary dependencies...") - install_mxpy(exact_version, from_branch, verbose) - - run_post_install_checks() - - if interactive: - guide_system_path_integration() - - logger.warning("Installing `mxpy` using `mxpy-up.py` is deprecated. Check out the documentation on how to install using `pipx`: https://docs.multiversx.com/sdk-and-tools/sdk-py/installing-mxpy/#install-using-pipx") - - -def guard_non_root_user(): - logger.debug("Checking user (should not be root).") - - operating_system = get_operating_system() - - if operating_system == "windows": - return - if os.getuid() == 0: - raise InstallError("You should not install mxpy as root.") - - -def guard_python_version(): - python_version = (sys.version_info.major, sys.version_info.minor, sys.version_info.micro) - - logger.debug("Checking Python version.") - logger.debug(f"Python version: {format_version(python_version)}") - if python_version < MIN_REQUIRED_PYTHON_VERSION: - raise InstallError(f"You need Python {format_version(MIN_REQUIRED_PYTHON_VERSION)} or later.") - - -def format_version(version: Tuple[int, int, int]) -> str: - major, minor, patch = version - return f"{major}.{minor}.{patch}" - - -def get_operating_system(): - aliases = { - "linux": "linux", - "linux1": "linux", - "linux2": "linux", - "darwin": "osx", - "win32": "windows", - "cygwin": "windows", - "msys": "windows" - } - - operating_system = aliases.get(sys.platform) - if operating_system is None: - raise InstallError(f"Unknown platform: {sys.platform}") - - return operating_system - - -def migrate_v6(interactive: bool): - nodejs_folder = sdk_path / "nodejs" - - if nodejs_folder.exists(): - print(f""" -In previous versions of the SDK, the "wasm-opt" tool was installed in the "nodejs" folder. - -This is no longer the case - now, "wasm-opt" is a separate module. - -The following folder will be removed: {nodejs_folder}. - -You may need to reinstall wasm-opt using `mxpy deps install wasm-opt`. -""") - confirm_continuation(interactive) - - shutil.rmtree(nodejs_folder) - - global_testnet_toml = sdk_path / "testnet.toml" - if global_testnet_toml.exists(): - global_testnet_toml.unlink() - - -def create_venv(): - require_python_venv_tools() - venv_folder = get_mxpy_venv_path() - venv_folder.mkdir(parents=True, exist_ok=True) - - logger.debug(f"Creating virtual environment in: {venv_folder}.") - import venv - builder = venv.EnvBuilder(with_pip=True, symlinks=True) - builder.clear_directory(venv_folder) - builder.create(venv_folder) - - logger.debug(f"Virtual environment has been created in: {venv_folder}.") - - -def require_python_venv_tools(): - operating_system = get_operating_system() - - try: - import ensurepip - import venv - logger.debug(f"Packages found: {ensurepip}, {venv}.") - except ModuleNotFoundError: - if operating_system == "linux": - python_venv = f"python{sys.version_info.major}.{sys.version_info.minor}-venv" - raise InstallError(f'Packages [venv] or [ensurepip] not found. Please run "sudo apt install {python_venv}" and then run mxpy-up again.') - else: - raise InstallError("Packages [venv] or [ensurepip] not found, please install them first. See https://docs.python.org/3/tutorial/venv.html.") - - -def get_mxpy_venv_path(): - return sdk_path / "mxpy-venv" - - -def install_mxpy(exact_version: str, from_branch: str, verbose: bool): - logger.debug("Installing mxpy in virtual environment...") - - if from_branch: - package_to_install = f"https://github.com/multiversx/mx-sdk-py-cli/archive/refs/heads/{from_branch}.zip" - else: - package_to_install = "multiversx_sdk_cli" if not exact_version else f"multiversx_sdk_cli=={exact_version}" - - venv_path = get_mxpy_venv_path() - - return_code = run_in_venv(["python3", "-m", "pip", "install", "--upgrade", "pip"], venv_path, verbose) - if return_code != 0: - raise InstallError("Could not upgrade pip.") - return_code = run_in_venv(["pip3", "install", "--no-cache-dir", package_to_install], venv_path, verbose) - if return_code != 0: - raise InstallError("Could not install mxpy.") - - logger.debug("Creating mxpy shortcut...") - - shortcut_path = sdk_path / "mxpy" - - try: - shortcut_path.unlink() - logger.debug(f"Removed existing shortcut: {shortcut_path}") - except FileNotFoundError: - logger.debug(f"Shortcut does not exist yet: {shortcut_path}") - pass - - shortcut_content = get_mxpy_shortcut_content() - shortcut_path.write_text(shortcut_content) - - st = os.stat(shortcut_path) - os.chmod(shortcut_path, st.st_mode | stat.S_IEXEC) - - logger.info("You have successfully installed mxpy.") - - -def get_mxpy_shortcut_content(): - operating_system = get_operating_system() - venv_path = get_mxpy_venv_path() - - if operating_system == "windows": - return f"""#!/bin/sh -. "{venv_path / 'Scripts' / 'activate'}" && python3 -m multiversx_sdk_cli.cli "$@" && deactivate -""" - - return f"""#!/bin/sh -. "{venv_path / 'bin' / 'activate'}" && python3 -m multiversx_sdk_cli.cli "$@" && deactivate -""" - - -def run_in_venv(args: List[str], venv_path: Path, verbose: bool): - env = os.environ.copy() - - if "PYTHONHOME" in env: - del env["PYTHONHOME"] - - env["PATH"] = str(venv_path / "bin") + ":" + env["PATH"] - env["VIRTUAL_ENV"] = str(venv_path) - - if verbose: - process = subprocess.Popen(args, env=env) - else: - process = subprocess.Popen(args, env=env, stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT) - - return process.wait() - - -def run_post_install_checks(): - multiversx_sdk_path = Path("~/multiversx-sdk").expanduser() - - logger.debug("Running post-install checks...") - - if multiversx_sdk_path.exists(): - logger.debug("~/multiversx-sdk exists OK") - else: - logger.warning("~/multiversx-sdk exists NOK") - - if (multiversx_sdk_path / "mxpy").exists(): - logger.debug("~/multiversx-sdk/mxpy shortcut created OK") - else: - logger.warning("~/multiversx-sdk/mxpy shortcut created NOK") - - -def guide_system_path_integration(): - interactive = True - operating_system = get_operating_system() - - if operating_system == "windows": - print(f""" -############################################################################### -On Windows, for the "mxpy" command shortcut to be available, you MUST ADD the directory "{sdk_path}" to the system PATH. - -You can do this by following these steps: - -https://superuser.com/questions/949560/how-do-i-set-system-environment-variables-in-windows-10 - -############################################################################### -Do you UNDERSTAND the above? -############################################################################### -""") - confirm_continuation(interactive) - return - - old_export_directive = f'export PATH="{Path("~/elrondsdk").expanduser()}:$PATH"\t# elrond-sdk' - new_export_directive = 'export PATH="${{HOME}}/multiversx-sdk:$PATH"\t# multiversx-sdk' - - profile_files = get_profile_files() - - if not profile_files: - print(f""" -############################################################################### -No shell profile files have been found. - -The "mxpy" command shortcut will not be available until you add the directory "{sdk_path}" to the system PATH. -############################################################################### -Do you UNDERSTAND the above? -""") - confirm_continuation(interactive) - return - - profile_files_formatted = "\n".join(f" - {file}" for file in profile_files) - profile_files_contents = [profile_file.read_text() for profile_file in profile_files] - any_old_export_directive = any(old_export_directive in content for content in profile_files_contents) - any_new_export_directive = any(new_export_directive in content for content in profile_files_contents) - - if any_old_export_directive: - # We don't perform the removal automatically (a bit risky) - print(f""" -############################################################################### -It seems that the old path "~/elrondsdk" is still configured in shell profile. - -Please MANUALLY remove it from the shell profile (now or after the installer script ends). - -Your shell profile files: -{profile_files_formatted} - -The entry (entries) to remove: - {old_export_directive} -############################################################################### -Make sure you UNDERSTAND the above before proceeding further. -############################################################################### -""") - confirm_continuation(interactive) - - if any_new_export_directive: - # Note: in some (rare) cases, here we'll have false positives (e.g. if the export directive is commented out). - print(f""" -############################################################################### -It seems that the path "~/multiversx-sdk" is already configured in shell profile. - -To confirm this, CHECK the shell profile (now or after the installer script ends). - -Your shell profile files: -{profile_files_formatted} - -The entry to check (it should be present): - {new_export_directive}. -############################################################################### -Make sure you UNDERSTAND the above before proceeding further. -############################################################################### -""") - confirm_continuation(interactive) - return - - print(f""" -############################################################################### -In order to use the "mxpy" command shortcut, you have to manually extend the PATH variable to include "~/multiversx-sdk". - -In order to manually extend the PATH variable, add the following line to your shell profile file upon installation: - - export PATH="${{HOME}}/multiversx-sdk:${{PATH}}" - -Your shell profile files: -{profile_files_formatted} - -Upon editing the shell profile file, you may have to RESTART THE USER SESSION for the changes to take effect. -""") - confirm_continuation(interactive) - - -def get_profile_files() -> List[Path]: - files = [ - Path("~/.profile").expanduser().resolve(), - Path("~/.bashrc").expanduser().resolve(), - Path("~/.bash_profile").expanduser().resolve(), - Path("~/.zshrc").expanduser().resolve() - ] - - return [file for file in files if file.exists()] - - -class InstallError(Exception): - def __init__(self, message: str): - super().__init__(message) - - -def confirm_continuation(interactive: bool): - if not interactive: - return - - answer = input("Continue? (y/n)") - if answer.lower() not in ["y", "yes"]: - print("Confirmation not given. Will stop.") - exit(1) - - -if __name__ == "__main__": - try: - main() - except Exception as err: - logger.fatal(err) - sys.exit(1) - - print(""" -############################################################################### -Installer script finished successfully. -############################################################################### -For more information go to https://docs.multiversx.com. -For support, please contact us at http://discord.gg/MultiversXBuilders. -############################################################################### -""") diff --git a/mypy.ini b/mypy.ini index 5673978b..510006a4 100644 --- a/mypy.ini +++ b/mypy.ini @@ -9,11 +9,5 @@ ignore_missing_imports = True [mypy-ledgercomm.*] ignore_missing_imports = True -[mypy-prettytable.*] -ignore_missing_imports = True - [mypy-semver.*] ignore_missing_imports = True - -[mypy-requests_cache.*] -ignore_missing_imports = True diff --git a/pyproject.toml b/pyproject.toml index d2353387..02b891b5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "multiversx-sdk-cli" -version = "9.12.1" +version = "10.0.0" authors = [ { name="MultiversX" }, ] @@ -22,13 +22,10 @@ classifiers = [ dependencies = [ "toml>=0.10.2", "requests>=2.32.0,<3.0.0", - "prettytable", "ledgercomm[hid]", - "semver", - "requests-cache", "rich==13.3.4", "argcomplete==3.2.2", - "multiversx-sdk==0.19.0" + "multiversx-sdk[ledger]==1.2.0" ] [project.scripts] diff --git a/requirements-dev.txt b/requirements-dev.txt index ce2c47ff..4b9d76b6 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -2,3 +2,6 @@ pytest flake8 autopep8 pytest-mock +pre-commit +black +mypy diff --git a/requirements.txt b/requirements.txt index 0c7334d0..7f295a29 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,12 +2,8 @@ toml>=0.10.2 types-toml requests>=2.32.0,<3.0.0 types-requests -prettytable -types-prettytable ledgercomm[hid] -semver -requests-cache rich==13.3.4 argcomplete==3.2.2 -multiversx-sdk==0.19.0 +multiversx-sdk[ledger]==1.2.0