diff --git a/.github/workflows/build-windows.yml b/.github/workflows/build-windows.yml index e800b693..e7010de2 100644 --- a/.github/workflows/build-windows.yml +++ b/.github/workflows/build-windows.yml @@ -30,7 +30,7 @@ jobs: run: | python3 -m pip install --upgrade pip pip3 install -r requirements.txt - pip3 install pytest + pip3 install -r requirements-dev.txt - name: Set github_api_token shell: bash run: | diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8360272f..c53f9f0a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -29,7 +29,7 @@ jobs: run: | python3 -m pip install --upgrade pip pip3 install -r requirements.txt - pip3 install pytest + pip3 install -r requirements-dev.txt - name: Set github_api_token run: | mkdir ~/multiversx-sdk diff --git a/CLI.md b/CLI.md index e3721cbb..3cf655de 100644 --- a/CLI.md +++ b/CLI.md @@ -23,7 +23,7 @@ See: COMMAND GROUPS: - {contract,tx,validator,account,ledger,wallet,deps,config,localnet,data,staking-provider,dns} + {contract,tx,validator,account,ledger,wallet,deps,config,localnet,data,staking-provider,dns,faucet} TOP-LEVEL OPTIONS: -h, --help show this help message and exit @@ -45,6 +45,7 @@ localnet Set up, start and control localnets data Data manipulation omnitool staking-provider Staking provider omnitool dns Operations related to the Domain Name Service +faucet Get xEGLD on Devnet or Testnet ``` ## Group **Contract** @@ -69,7 +70,7 @@ new Create a new Smart Contract project based on a te templates List the available Smart Contract templates. build Build a Smart Contract project. clean Clean a Smart Contract project. -test Run scenarios (tests). +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). @@ -194,6 +195,7 @@ 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) @@ -219,10 +221,18 @@ options: --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 + --relayer RELAYER the address of the relayer + --inner-transactions INNER_TRANSACTIONS a json file containing the inner transactions; should only be provided + when creating the relayer's transaction + --inner-transactions-outfile INNER_TRANSACTIONS_OUTFILE + where to save the transaction as an inner transaction (default: + stdout) --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 @@ -282,6 +292,7 @@ positional arguments: 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) --pem PEM 🔑 the PEM file, if keyfile not provided --pem-index PEM_INDEX 🔑 the index in the PEM file (default: 0) @@ -303,11 +314,19 @@ options: --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 + --relayer RELAYER the address of the relayer + --inner-transactions INNER_TRANSACTIONS a json file containing the inner transactions; should only be provided + when creating the relayer's transaction + --inner-transactions-outfile INNER_TRANSACTIONS_OUTFILE + where to save the transaction as an inner transaction (default: + stdout) --options OPTIONS the transaction options (default: 0) --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[..] + --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 ...] token transfers for transfer & execute, as [token, amount] E.g. --token-transfers NFT-123456-0a 1 ESDT-987654 100000000 @@ -371,6 +390,7 @@ positional arguments: 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) @@ -397,10 +417,18 @@ options: --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 + --relayer RELAYER the address of the relayer + --inner-transactions INNER_TRANSACTIONS a json file containing the inner transactions; should only be provided + when creating the relayer's transaction + --inner-transactions-outfile INNER_TRANSACTIONS_OUTFILE + where to save the transaction as an inner transaction (default: + stdout) --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 @@ -432,11 +460,14 @@ positional arguments: options: -h, --help show this help message and exit + --abi ABI the ABI 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[..] + --arguments-file ARGUMENTS_FILE a json file containing the arguments. ONLY if abi file is provided. E.g. [{ + 'to': 'erd1...', 'amount': 10000000000 }] ``` ### Contract.Report @@ -549,8 +580,17 @@ options: --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 + --relayer RELAYER the address of the relayer + --inner-transactions INNER_TRANSACTIONS a json file containing the inner transactions; should only be provided + when creating the relayer's transaction + --inner-transactions-outfile INNER_TRANSACTIONS_OUTFILE + where to save the transaction as an inner transaction (default: + stdout) --options OPTIONS the transaction options (default: 0) --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. + --token-transfers NFT-123456-0a 1 ESDT-987654 100000000 --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) @@ -693,6 +733,12 @@ options: --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 + --relayer RELAYER the address of the relayer + --inner-transactions INNER_TRANSACTIONS a json file containing the inner transactions; should only be provided + when creating the relayer's transaction + --inner-transactions-outfile INNER_TRANSACTIONS_OUTFILE + where to save the transaction as an inner transaction (default: + stdout) --options OPTIONS the transaction options (default: 0) --send ✓ whether to broadcast the transaction (default: False) --simulate whether to simulate the transaction (default: False) @@ -743,6 +789,12 @@ options: --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 + --relayer RELAYER the address of the relayer + --inner-transactions INNER_TRANSACTIONS a json file containing the inner transactions; should only be provided + when creating the relayer's transaction + --inner-transactions-outfile INNER_TRANSACTIONS_OUTFILE + where to save the transaction as an inner transaction (default: + stdout) --options OPTIONS the transaction options (default: 0) --send ✓ whether to broadcast the transaction (default: False) --simulate whether to simulate the transaction (default: False) @@ -791,6 +843,12 @@ options: --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 + --relayer RELAYER the address of the relayer + --inner-transactions INNER_TRANSACTIONS a json file containing the inner transactions; should only be provided + when creating the relayer's transaction + --inner-transactions-outfile INNER_TRANSACTIONS_OUTFILE + where to save the transaction as an inner transaction (default: + stdout) --options OPTIONS the transaction options (default: 0) --send ✓ whether to broadcast the transaction (default: False) --simulate whether to simulate the transaction (default: False) @@ -839,6 +897,12 @@ options: --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 + --relayer RELAYER the address of the relayer + --inner-transactions INNER_TRANSACTIONS a json file containing the inner transactions; should only be provided + when creating the relayer's transaction + --inner-transactions-outfile INNER_TRANSACTIONS_OUTFILE + where to save the transaction as an inner transaction (default: + stdout) --options OPTIONS the transaction options (default: 0) --send ✓ whether to broadcast the transaction (default: False) --simulate whether to simulate the transaction (default: False) @@ -887,6 +951,12 @@ options: --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 + --relayer RELAYER the address of the relayer + --inner-transactions INNER_TRANSACTIONS a json file containing the inner transactions; should only be provided + when creating the relayer's transaction + --inner-transactions-outfile INNER_TRANSACTIONS_OUTFILE + where to save the transaction as an inner transaction (default: + stdout) --options OPTIONS the transaction options (default: 0) --send ✓ whether to broadcast the transaction (default: False) --simulate whether to simulate the transaction (default: False) @@ -935,6 +1005,12 @@ options: --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 + --relayer RELAYER the address of the relayer + --inner-transactions INNER_TRANSACTIONS a json file containing the inner transactions; should only be provided + when creating the relayer's transaction + --inner-transactions-outfile INNER_TRANSACTIONS_OUTFILE + where to save the transaction as an inner transaction (default: + stdout) --options OPTIONS the transaction options (default: 0) --send ✓ whether to broadcast the transaction (default: False) --simulate whether to simulate the transaction (default: False) @@ -960,11 +1036,34 @@ usage: mxpy staking-provider COMMAND [-h] ... Staking provider omnitool COMMANDS: - {create-new-delegation-contract,get-contract-address,add-nodes,remove-nodes,stake-nodes,unbond-nodes,unstake-nodes,unjail-nodes,change-service-fee,modify-delegation-cap,automatic-activation,redelegate-cap,set-metadata} + {create-new-delegation-contract,get-contract-address,add-nodes,remove-nodes,stake-nodes,unbond-nodes,unstake-nodes,unjail-nodes,delegate,claim-rewards,redelegate-rewards,undelegate,withdraw,change-service-fee,modify-delegation-cap,automatic-activation,redelegate-cap,set-metadata,make-delegation-contract-from-validator} OPTIONS: -h, --help show this help message and exit +---------------- +COMMANDS summary +---------------- +create-new-delegation-contract Create a new delegation system smart contract, transferred value must be greater than baseIssuingCost + min deposit value +get-contract-address Get create contract address by transaction hash +add-nodes Add new nodes must be called by the contract owner +remove-nodes Remove nodes must be called by the contract owner +stake-nodes Stake nodes must be called by the contract owner +unbond-nodes Unbond nodes must be called by the contract owner +unstake-nodes Unstake nodes must be called by the contract owner +unjail-nodes Unjail nodes must be called by the contract owner +delegate Delegate funds to a delegation contract +claim-rewards Claim the rewards earned for delegating +redelegate-rewards Redelegate the rewards earned for delegating +undelegate Undelegate funds from a delegation contract +withdraw Withdraw funds from a delegation contract +change-service-fee Change service fee must be called by the contract owner +modify-delegation-cap Modify delegation cap must be called by the contract owner +automatic-activation Automatic activation must be called by the contract owner +redelegate-cap Redelegate cap must be called by the contract owner +set-metadata Set metadata must be called by the contract owner +make-delegation-contract-from-validator Create a delegation contract from validator data. Must be called by the node operator + ``` ### StakingProvider.CreateNewDelegationContract @@ -973,7 +1072,7 @@ OPTIONS: $ mxpy staking-provider create-new-delegation-contract --help usage: mxpy staking-provider create-new-delegation-contract [-h] ... -Create a new delegation system smart contract, transferred value must begreater than baseIssuingCost + min deposit value +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 @@ -998,6 +1097,12 @@ options: --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 + --relayer RELAYER the address of the relayer + --inner-transactions INNER_TRANSACTIONS a json file containing the inner transactions; should only be provided + when creating the relayer's transaction + --inner-transactions-outfile INNER_TRANSACTIONS_OUTFILE + where to save the transaction as an inner transaction (default: + stdout) --options OPTIONS the transaction options (default: 0) --send ✓ whether to broadcast the transaction (default: False) --simulate whether to simulate the transaction (default: False) @@ -1044,7 +1149,6 @@ 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 - --using-delegation-manager whether delegation contract was created using the Delegation Manager --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) @@ -1066,6 +1170,12 @@ options: --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 + --relayer RELAYER the address of the relayer + --inner-transactions INNER_TRANSACTIONS a json file containing the inner transactions; should only be provided + when creating the relayer's transaction + --inner-transactions-outfile INNER_TRANSACTIONS_OUTFILE + where to save the transaction as an inner transaction (default: + stdout) --options OPTIONS the transaction options (default: 0) --send ✓ whether to broadcast the transaction (default: False) --simulate whether to simulate the transaction (default: False) @@ -1093,6 +1203,7 @@ 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 @@ -1115,6 +1226,12 @@ options: --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 + --relayer RELAYER the address of the relayer + --inner-transactions INNER_TRANSACTIONS a json file containing the inner transactions; should only be provided + when creating the relayer's transaction + --inner-transactions-outfile INNER_TRANSACTIONS_OUTFILE + where to save the transaction as an inner transaction (default: + stdout) --options OPTIONS the transaction options (default: 0) --send ✓ whether to broadcast the transaction (default: False) --simulate whether to simulate the transaction (default: False) @@ -1142,6 +1259,7 @@ 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 @@ -1164,6 +1282,12 @@ options: --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 + --relayer RELAYER the address of the relayer + --inner-transactions INNER_TRANSACTIONS a json file containing the inner transactions; should only be provided + when creating the relayer's transaction + --inner-transactions-outfile INNER_TRANSACTIONS_OUTFILE + where to save the transaction as an inner transaction (default: + stdout) --options OPTIONS the transaction options (default: 0) --send ✓ whether to broadcast the transaction (default: False) --simulate whether to simulate the transaction (default: False) @@ -1191,6 +1315,7 @@ 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 @@ -1213,6 +1338,12 @@ options: --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 + --relayer RELAYER the address of the relayer + --inner-transactions INNER_TRANSACTIONS a json file containing the inner transactions; should only be provided + when creating the relayer's transaction + --inner-transactions-outfile INNER_TRANSACTIONS_OUTFILE + where to save the transaction as an inner transaction (default: + stdout) --options OPTIONS the transaction options (default: 0) --send ✓ whether to broadcast the transaction (default: False) --simulate whether to simulate the transaction (default: False) @@ -1240,6 +1371,7 @@ 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 @@ -1262,6 +1394,12 @@ options: --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 + --relayer RELAYER the address of the relayer + --inner-transactions INNER_TRANSACTIONS a json file containing the inner transactions; should only be provided + when creating the relayer's transaction + --inner-transactions-outfile INNER_TRANSACTIONS_OUTFILE + where to save the transaction as an inner transaction (default: + stdout) --options OPTIONS the transaction options (default: 0) --send ✓ whether to broadcast the transaction (default: False) --simulate whether to simulate the transaction (default: False) @@ -1289,6 +1427,7 @@ 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 @@ -1311,6 +1450,12 @@ options: --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 + --relayer RELAYER the address of the relayer + --inner-transactions INNER_TRANSACTIONS a json file containing the inner transactions; should only be provided + when creating the relayer's transaction + --inner-transactions-outfile INNER_TRANSACTIONS_OUTFILE + where to save the transaction as an inner transaction (default: + stdout) --options OPTIONS the transaction options (default: 0) --send ✓ whether to broadcast the transaction (default: False) --simulate whether to simulate the transaction (default: False) @@ -1360,6 +1505,12 @@ options: --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 + --relayer RELAYER the address of the relayer + --inner-transactions INNER_TRANSACTIONS a json file containing the inner transactions; should only be provided + when creating the relayer's transaction + --inner-transactions-outfile INNER_TRANSACTIONS_OUTFILE + where to save the transaction as an inner transaction (default: + stdout) --options OPTIONS the transaction options (default: 0) --send ✓ whether to broadcast the transaction (default: False) --simulate whether to simulate the transaction (default: False) @@ -1409,6 +1560,12 @@ options: --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 + --relayer RELAYER the address of the relayer + --inner-transactions INNER_TRANSACTIONS a json file containing the inner transactions; should only be provided + when creating the relayer's transaction + --inner-transactions-outfile INNER_TRANSACTIONS_OUTFILE + where to save the transaction as an inner transaction (default: + stdout) --options OPTIONS the transaction options (default: 0) --send ✓ whether to broadcast the transaction (default: False) --simulate whether to simulate the transaction (default: False) @@ -1459,6 +1616,12 @@ options: --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 + --relayer RELAYER the address of the relayer + --inner-transactions INNER_TRANSACTIONS a json file containing the inner transactions; should only be provided + when creating the relayer's transaction + --inner-transactions-outfile INNER_TRANSACTIONS_OUTFILE + where to save the transaction as an inner transaction (default: + stdout) --options OPTIONS the transaction options (default: 0) --send ✓ whether to broadcast the transaction (default: False) --simulate whether to simulate the transaction (default: False) @@ -1509,6 +1672,12 @@ options: --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 + --relayer RELAYER the address of the relayer + --inner-transactions INNER_TRANSACTIONS a json file containing the inner transactions; should only be provided + when creating the relayer's transaction + --inner-transactions-outfile INNER_TRANSACTIONS_OUTFILE + where to save the transaction as an inner transaction (default: + stdout) --options OPTIONS the transaction options (default: 0) --send ✓ whether to broadcast the transaction (default: False) --simulate whether to simulate the transaction (default: False) @@ -1560,6 +1729,12 @@ options: --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 + --relayer RELAYER the address of the relayer + --inner-transactions INNER_TRANSACTIONS a json file containing the inner transactions; should only be provided + when creating the relayer's transaction + --inner-transactions-outfile INNER_TRANSACTIONS_OUTFILE + where to save the transaction as an inner transaction (default: + stdout) --options OPTIONS the transaction options (default: 0) --send ✓ whether to broadcast the transaction (default: False) --simulate whether to simulate the transaction (default: False) @@ -1657,6 +1832,7 @@ options: (default: None) --address-hrp ADDRESS_HRP the human-readable part of the address, when format is keystore- secret-key or pem (default: erd) + --shard SHARD the shard in which the address will be generated; (default: random) ``` ### Wallet.Convert @@ -1888,11 +2064,11 @@ usage: mxpy deps install [-h] ... Install dependencies or multiversx-sdk modules. positional arguments: - {all,rust,golang,vmtools,testwallets} the dependency to install + {all,rust,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 @@ -1905,10 +2081,10 @@ usage: mxpy deps check [-h] ... Check whether a dependency is installed. positional arguments: - {all,rust,golang,vmtools,testwallets} the dependency to check + {all,rust,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** @@ -2116,3 +2292,46 @@ options: --use-global use the global storage (default: False) ``` +## Group **Faucet** + + +``` +$ mxpy faucet --help +usage: mxpy faucet COMMAND [-h] ... + +Get xEGLD on Devnet or Testnet + +COMMANDS: + {request} + +OPTIONS: + -h, --help show this help message and exit + +---------------- +COMMANDS summary +---------------- +request Request xEGLD. + +``` +### Faucet.Request + + +``` +$ mxpy faucet request --help +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 + +``` diff --git a/CLI.md.sh b/CLI.md.sh index 49790861..8bb90601 100755 --- a/CLI.md.sh +++ b/CLI.md.sh @@ -114,6 +114,9 @@ generate() { command "Data.Dump" "data parse" command "Data.Store" "data store" command "Data.Load" "data load" + + group "Faucet" "faucet" + command "Faucet.Request" "faucet request" } generate diff --git a/multiversx_sdk_cli/cli.py b/multiversx_sdk_cli/cli.py index 8ddd0c02..79eacf20 100644 --- a/multiversx_sdk_cli/cli.py +++ b/multiversx_sdk_cli/cli.py @@ -15,6 +15,7 @@ import multiversx_sdk_cli.cli_delegation import multiversx_sdk_cli.cli_deps import multiversx_sdk_cli.cli_dns +import multiversx_sdk_cli.cli_faucet import multiversx_sdk_cli.cli_ledger import multiversx_sdk_cli.cli_localnet import multiversx_sdk_cli.cli_transactions @@ -100,6 +101,7 @@ def setup_parser(args: List[str]): commands.append(multiversx_sdk_cli.cli_data.setup_parser(subparsers)) commands.append(multiversx_sdk_cli.cli_delegation.setup_parser(args, subparsers)) commands.append(multiversx_sdk_cli.cli_dns.setup_parser(args, subparsers)) + commands.append(multiversx_sdk_cli.cli_faucet.setup_parser(args, subparsers)) parser.epilog = """ ---------------------- diff --git a/multiversx_sdk_cli/cli_delegation.py b/multiversx_sdk_cli/cli_delegation.py index c341a850..aafbaa4b 100644 --- a/multiversx_sdk_cli/cli_delegation.py +++ b/multiversx_sdk_cli/cli_delegation.py @@ -12,7 +12,7 @@ def setup_parser(args: List[str], subparsers: Any) -> Any: # 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" + "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") diff --git a/multiversx_sdk_cli/cli_faucet.py b/multiversx_sdk_cli/cli_faucet.py new file mode 100644 index 00000000..878dc7ad --- /dev/null +++ b/multiversx_sdk_cli/cli_faucet.py @@ -0,0 +1,72 @@ +import logging +import webbrowser +from enum import Enum +from typing import Any, List, Tuple + +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") + + +class WebWalletUrls(Enum): + DEVNET = "https://devnet-wallet.multiversx.com" + TESTNET = "https://testnet-wallet.multiversx.com" + + +class ApiUrls(Enum): + DEVNET = "https://devnet-api.multiversx.com" + TESTNET = "https://testnet-api.multiversx.com" + + +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.set_defaults(func=faucet) + + parser.epilog = cli_shared.build_group_epilog(subparsers) + return subparsers + + +def faucet(args: Any): + account = cli_shared.prepare_account(args) + wallet, api = get_wallet_and_api_urls(args) + + config = NativeAuthClientConfig(origin=wallet, api_url=api) + client = NativeAuthClient(config) + + init_token = client.initialize() + token_for_siginig = f"{account.address.to_bech32()}{init_token}" + signature = account.sign_message(token_for_siginig.encode()) + + access_token = client.get_token( + address=account.address.to_bech32(), + token=init_token, + signature=signature + ) + + logger.info(f"Requesting funds for address: {account.address.to_bech32()}") + call_web_wallet_faucet(wallet_url=wallet, access_token=access_token) + + +def call_web_wallet_faucet(wallet_url: str, access_token: str): + faucet_url = f"{wallet_url}/faucet?accessToken={access_token}" + webbrowser.open_new_tab(faucet_url) + + +def get_wallet_and_api_urls(args: Any) -> Tuple[str, str]: + chain: str = args.chain + + if chain.upper() == "D": + return WebWalletUrls.DEVNET.value, ApiUrls.DEVNET.value + + if chain.upper() == "T": + return WebWalletUrls.TESTNET.value, ApiUrls.TESTNET.value + + raise BadUserInput("Invalid chain id. Choose between 'D' for devnet and 'T' for testnet.") diff --git a/multiversx_sdk_cli/errors.py b/multiversx_sdk_cli/errors.py index 19dae17f..b5cb32b4 100644 --- a/multiversx_sdk_cli/errors.py +++ b/multiversx_sdk_cli/errors.py @@ -177,3 +177,8 @@ def __init__(self, message: str): class QueryContractError(KnownError): def __init__(self, message: str, inner: Any = None): super().__init__(message, str(inner)) + + +class NativeAuthClientError(KnownError): + def __init__(self, message: str): + super().__init__(message) diff --git a/multiversx_sdk_cli/native_auth_client.py b/multiversx_sdk_cli/native_auth_client.py new file mode 100644 index 00000000..f7174b8e --- /dev/null +++ b/multiversx_sdk_cli/native_auth_client.py @@ -0,0 +1,104 @@ +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/tests/test_native_auth_client.py b/multiversx_sdk_cli/tests/test_native_auth_client.py new file mode 100644 index 00000000..92678941 --- /dev/null +++ b/multiversx_sdk_cli/tests/test_native_auth_client.py @@ -0,0 +1,128 @@ +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/requirements-dev.txt b/requirements-dev.txt index 85d17fda..ce2c47ff 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,3 +1,4 @@ pytest flake8 autopep8 +pytest-mock