diff --git a/.github/workflows/snippet_test.yml b/.github/workflows/snippet_test.yml new file mode 100644 index 000000000..1c83c84da --- /dev/null +++ b/.github/workflows/snippet_test.yml @@ -0,0 +1,52 @@ +name: Snippets + +on: + push: + branches: [ master ] + pull_request: + workflow_dispatch: + +env: + POETRY_VERSION: 1.4.2 + +jobs: + snippet-test: + name: Snippet test + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ['3.7', '3.8', '3.9', '3.10', '3.11'] + + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Load cached .local + id: cache-poetry + uses: actions/cache@v3 + with: + path: /home/runner/.local + key: dotlocal-${{ env.POETRY_VERSION }} + + - name: Install poetry + if: steps.cache-poetry.outputs.cache-hit != 'true' + run: | + curl -sSL https://install.python-poetry.org/ | python - --version ${{ env.POETRY_VERSION }} + echo "$HOME/.local/bin" >> $GITHUB_PATH + + - name: Install Python + Retrieve Poetry dependencies from cache + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + cache: 'poetry' + + - name: Display Python version + run: | + python -c "import sys; print(sys.version)" + + - name: Install poetry dependencies + run: poetry install + + + - name: Run Snippets + run: (for i in snippets/*.py; do echo "Running $i" && poetry run python $i || exit 1; done) diff --git a/.github/workflows/unit_test.yml b/.github/workflows/unit_test.yml index 0e8d58797..8f7f88e7e 100644 --- a/.github/workflows/unit_test.yml +++ b/.github/workflows/unit_test.yml @@ -2,7 +2,7 @@ name: Unit test on: push: - branches: [ master ] + branches: [master] pull_request: workflow_dispatch: @@ -38,7 +38,7 @@ jobs: uses: actions/setup-python@v4 with: python-version: ${{ env.PYTHON_VERSION }} - cache: 'poetry' + cache: "poetry" - name: Display Python version run: | @@ -49,18 +49,18 @@ jobs: - name: Lint run: | - poetry run flake8 xrpl tests --darglint-ignore-regex="^_(.*)" + poetry run flake8 xrpl tests snippets --darglint-ignore-regex="^_(.*)" - name: Type-check run: | - poetry run mypy --strict --implicit-reexport xrpl + poetry run mypy --strict --implicit-reexport xrpl snippets unit-test: name: Unit test runs-on: ubuntu-latest strategy: matrix: - python-version: ['3.7', '3.8', '3.9', '3.10', '3.11'] + python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"] steps: - name: Checkout code @@ -83,7 +83,7 @@ jobs: uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} - cache: 'poetry' + cache: "poetry" - name: Display Python version run: | diff --git a/.vscode/settings.json b/.vscode/settings.json index 480f9ce94..7de2a60b8 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,5 +1,5 @@ { - "python.formatting.provider": "black", + "python.formatting.provider": "none", "python.linting.pylintEnabled": false, "python.linting.flake8Enabled": true, "python.linting.enabled": true, @@ -11,14 +11,19 @@ "aiounittest", "altnet", "asyncio", + "autofills", "binarycodec", "isnumeric", "keypair", "keypairs", + "multisign", "nftoken", "rippletest", "ripplex", "xaddress", "xchain" ], + "[python]": { + "editor.defaultFormatter": "ms-python.black-formatter" + } } diff --git a/CHANGELOG.md b/CHANGELOG.md index e28b83c31..f7dc32969 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,19 +6,66 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [[Unreleased]] + +### Fixed +- Replaced alias for `classic_address` with separate property to work around this mypy issue: + https://github.com/python/mypy/issues/6700 + +## [2.0.0] - 2023-07-05 +### Added: +- Wallet support for regular key compatibility +- Added new ways of wallet generation: `from_seed`, `from_secret`, `from_entropy`, `from_secret_numbers` +- Added `address` alias to `Wallet.classic_address` + - Replaced `Wallet.classic_address` with `Wallet.address` to avoid confusion. (`classic_address` is the same as your XRPL account `address`, and is only called classic since it's an older standard than `x-address`) +- Added `network_id` to clients in order to use the `Client` with networks beyond mainnet + +### Changed: +- Updated params for `Wallet` class constructor +- `Wallet.address` is now readonly +- Removed `sequence` from `Wallet` class +- Core keypairs generate seed must take in hexstring instead of bytestring +- Core keypairs formatting for `ED25519` is now padded with zeros if length of keystring is less than 64 +- Removed deprecated request wrappers (the preferred method is to directly do client.request instead) +- `AccountSetFlagInterface` now operates on transaction `tf` flags (as opposed to `asf` flags) +- `sign` is now synchronous instead of async (done by removing the optional `check_fee` param & moving checks up to other functions) +- In order to be internally consistent, all signing/submitting functions will follow the parameter order of `transaction`, `client`, `wallet`, and then other parameters. (This is because `wallet` is optional for `submit_and_wait` and so must come after `client`) +- `XRP.to_amount` now converts from XRP to drops, instead of expecting a drops amount + +### Fixed: +- Added a sort of the account IDs in `multisign`, so that the `multisign` always works. +- Add `ledger_hash` and `ledger_index` to `account_nfts`, `nft_buy_offers`, and `nft_sell_offers` requests. +- Add `nft_page` to `ledger_entry` request. + +### Removed: +- `send_reliable_submission` has been replaced by `submit_and_wait` +- Longer aliases for signing/submitting functions have been removed. Specifically + - `submit_transaction` is now `submit` + - `safe_sign_transaction` is now `sign` + - `safe_sign_and_submit_transaction` is now `sign_and_submit` + - The param order for `sign_and_submit` moves `wallet` after `client` to be consistent with `submit_and_wait` + - `safe_sign_and_autofill_transaction` is now `autofill_and_sign` + - The param order for `autofill_and_sign` moves `wallet` after `client` to be consistent with `submit_and_wait` +- Removed deprecated request functions which were just wrappers around `Client.request()`. Specifically this includes: + - `get_account_info` + - `get_account_transactions` + - `get_account_payment_transactions` + - `get_transaction_from_hash` + +## [1.9.0] - 2023-06-13 ### Added: - Added `submit_and_wait` to sign (if needed), autofill, submit a transaction and wait for its final outcome - `submit` and `send_reliable_submission` now accept an optional boolean param `fail_hard` (if `True` halt the submission if it's not immediately validated) - Support for cross-chain bridge proposal - Support for Automated Market Maker (AMM) transactions and requests as defined in XLS-30. - Added sidechain devnet support to faucet generation +- Added `user_agent` and `usage_context` to `generate_faucet_wallet` ### Changed: - Allowed keypairs.sign to take a hex string in addition to bytes ### Fixed: - Refactored `does_account_exist` and `get_balance` to avoid deprecated methods and use `ledger_index` parameter -- Fixed crashes in the SignerListSet validation +- Fixed crashes in the `SignerListSet` validation - Improved error messages in `send_reliable_submission` - Better error handling in reliable submission diff --git a/README.md b/README.md index 39e173286..844e0c627 100644 --- a/README.md +++ b/README.md @@ -17,10 +17,10 @@ test_wallet = generate_faucet_wallet(client) print(test_wallet) public_key: ED3CC1BBD0952A60088E89FA502921895FC81FBD79CAE9109A8FE2D23659AD5D56 private_key: -HIDDEN- -classic_address: rBtXmAdEYcno9LWRnAGfT9qBxCeDvuVRZo +address: rBtXmAdEYcno9LWRnAGfT9qBxCeDvuVRZo # look up account info -from xrpl.models.requests.account_info import AccountInfo +from xrpl.models import AccountInfo acct_info = AccountInfo( account="rBtXmAdEYcno9LWRnAGfT9qBxCeDvuVRZo", ledger_index="current", @@ -103,18 +103,18 @@ Use the [`xrpl.wallet`](https://xrpl-py.readthedocs.io/en/stable/source/xrpl.wal To create a wallet from a seed (in this case, the value generated using [`xrpl.keypairs`](#xrpl-keypairs)): ```py -wallet_from_seed = xrpl.wallet.Wallet(seed, 0) +wallet_from_seed = xrpl.wallet.Wallet.from_seed(seed) print(wallet_from_seed) # pub_key: ED46949E414A3D6D758D347BAEC9340DC78F7397FEE893132AAF5D56E4D7DE77B0 # priv_key: -HIDDEN- -# classic_address: rG5ZvYsK5BPi9f1Nb8mhFGDTNMJhEhufn6 +# address: rG5ZvYsK5BPi9f1Nb8mhFGDTNMJhEhufn6 ``` To create a wallet from a Testnet faucet: ```py test_wallet = generate_faucet_wallet(client) -test_account = test_wallet.classic_address +test_account = test_wallet.address print("Classic address:", test_account) # Classic address: rEQB2hhp3rg7sHj6L8YyR4GG47Cb7pfcuw ``` @@ -157,34 +157,33 @@ Use the [`xrpl.transaction`](https://xrpl-py.readthedocs.io/en/stable/source/xrp * [`sign`](https://xrpl-py.readthedocs.io/en/stable/source/xrpl.transaction.html#xrpl.transaction.sign) — Signs a transaction locally. This method **does not** submit the transaction to the XRP Ledger. -* [`send_reliable_submission`](https://xrpl-py.readthedocs.io/en/stable/source/xrpl.transaction.html#xrpl.transaction.send_reliable_submission) — An implementation of the [reliable transaction submission guidelines](https://xrpl.org/reliable-transaction-submission.html#reliable-transaction-submission), this method submits a signed transaction to the XRP Ledger and then verifies that it has been included in a validated ledger (or has failed to do so). Use this method to submit transactions for production purposes. +* [`submit_and_wait`](https://xrpl-py.readthedocs.io/en/stable/source/xrpl.transaction.html#xrpl.transaction.submit_and_wait) — An implementation of the [reliable transaction submission guidelines](https://xrpl.org/reliable-transaction-submission.html#reliable-transaction-submission), this method submits a signed transaction to the XRP Ledger and then verifies that it has been included in a validated ledger (or has failed to do so). Use this method to submit transactions for production purposes. ```py from xrpl.models.transactions import Payment -from xrpl.transaction import sign, send_reliable_submission +from xrpl.transaction import sign, submit_and_wait from xrpl.ledger import get_latest_validated_ledger_sequence from xrpl.account import get_next_valid_seq_number current_validated_ledger = get_latest_validated_ledger_sequence(client) -wallet_sequence = get_next_valid_seq_number(test_wallet.classic_address, client) # prepare the transaction # the amount is expressed in drops, not XRP # see https://xrpl.org/basic-data-types.html#specifying-currency-amounts my_tx_payment = Payment( - account=test_wallet.classic_address, + account=test_wallet.address, amount="2200000", destination="rPT1Sjq2YGrBMTttX4GZHjKu9dyfzbpAYe", last_ledger_sequence=current_validated_ledger + 20, - sequence=wallet_sequence, + sequence=get_next_valid_seq_number(test_wallet.address, client), fee="10", ) # sign the transaction my_tx_payment_signed = sign(my_tx_payment,test_wallet) # submit the transaction -tx_response = send_reliable_submission(my_tx_payment_signed, client) +tx_response = submit_and_wait(my_tx_payment_signed, client) ``` #### Get fee from the XRP Ledger @@ -205,19 +204,19 @@ The `xrpl-py` library automatically populates the `fee`, `sequence` and `last_le ```py from xrpl.models.transactions import Payment -from xrpl.transaction import send_reliable_submission, autofill_and_sign +from xrpl.transaction import submit_and_wait, autofill_and_sign # prepare the transaction # the amount is expressed in drops, not XRP # see https://xrpl.org/basic-data-types.html#specifying-currency-amounts my_tx_payment = Payment( - account=test_wallet.classic_address, + account=test_wallet.address, amount="2200000", destination="rPT1Sjq2YGrBMTttX4GZHjKu9dyfzbpAYe" ) # sign the transaction with the autofill method # (this will auto-populate the fee, sequence, and last_ledger_sequence) -my_tx_payment_signed = autofill_and_sign(my_tx_payment, test_wallet, client) +my_tx_payment_signed = autofill_and_sign(my_tx_payment, client, test_wallet) print(my_tx_payment_signed) # Payment( # account='rMPUKmzmDWEX1tQhzQ8oGFNfAEhnWNFwz', @@ -242,7 +241,7 @@ print(my_tx_payment_signed) # ) # submit the transaction -tx_response = send_reliable_submission(my_tx_payment_signed, client) +tx_response = submit_and_wait(my_tx_payment_signed, client) ``` @@ -253,7 +252,7 @@ You can send `subscribe` and `unsubscribe` requests only using the WebSocket net ```py from xrpl.clients import WebsocketClient url = "wss://s.altnet.rippletest.net/" -from xrpl.models.requests import Subscribe, StreamParameter +from xrpl.models import Subscribe, StreamParameter req = Subscribe(streams=[StreamParameter.LEDGER]) # NOTE: this code will run forever without a timeout, until the process is killed with WebsocketClient(url) as client: @@ -276,7 +275,7 @@ This sample code is the asynchronous equivalent of the above section on submitti ```py import asyncio from xrpl.models.transactions import Payment -from xrpl.asyncio.transaction import sign, send_reliable_submission +from xrpl.asyncio.transaction import sign, submit_and_wait from xrpl.asyncio.ledger import get_latest_validated_ledger_sequence from xrpl.asyncio.account import get_next_valid_seq_number from xrpl.asyncio.clients import AsyncJsonRpcClient @@ -285,24 +284,20 @@ async_client = AsyncJsonRpcClient(JSON_RPC_URL) async def submit_sample_transaction(): current_validated_ledger = await get_latest_validated_ledger_sequence(async_client) - wallet_sequence = await get_next_valid_seq_number(test_wallet.classic_address, async_client) # prepare the transaction # the amount is expressed in drops, not XRP # see https://xrpl.org/basic-data-types.html#specifying-currency-amounts my_tx_payment = Payment( - account=test_wallet.classic_address, + account=test_wallet.address, amount="2200000", destination="rPT1Sjq2YGrBMTttX4GZHjKu9dyfzbpAYe", last_ledger_sequence=current_validated_ledger + 20, - sequence=wallet_sequence, + sequence=await get_next_valid_seq_number(test_wallet.address, async_client), fee="10", ) - # sign the transaction - my_tx_payment_signed = await sign(my_tx_payment,test_wallet) - - # submit the transaction - tx_response = await send_reliable_submission(my_tx_payment_signed, async_client) + # sign and submit the transaction + tx_response = await submit_and_wait(my_tx_payment_signed, async_client, test_wallet) asyncio.run(submit_sample_transaction()) ``` diff --git a/poetry.lock b/poetry.lock index f89b6cc25..3790ca286 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,10 +1,9 @@ -# This file is automatically @generated by Poetry and should not be changed by hand. +# This file is automatically @generated by Poetry 1.5.1 and should not be changed by hand. [[package]] name = "aiounittest" version = "1.4.2" description = "Test asyncio code more easily." -category = "dev" optional = false python-versions = "*" files = [ @@ -19,7 +18,6 @@ wrapt = "*" name = "alabaster" version = "0.7.12" description = "A configurable sidebar-enabled Sphinx theme" -category = "dev" optional = false python-versions = "*" files = [ @@ -31,7 +29,6 @@ files = [ name = "anyio" version = "3.6.1" description = "High level compatibility layer for multiple asynchronous event loop implementations" -category = "main" optional = false python-versions = ">=3.6.2" files = [ @@ -53,7 +50,6 @@ trio = ["trio (>=0.16)"] name = "babel" version = "2.10.3" description = "Internationalization utilities" -category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -68,7 +64,6 @@ pytz = ">=2015.7" name = "base58" version = "2.1.1" description = "Base58 and Base58Check implementation." -category = "main" optional = false python-versions = ">=3.5" files = [ @@ -83,7 +78,6 @@ tests = ["PyHamcrest (>=2.0.2)", "mypy", "pytest (>=4.6)", "pytest-benchmark", " name = "black" version = "23.3.0" description = "The uncompromising code formatter." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -134,7 +128,6 @@ uvloop = ["uvloop (>=0.15.2)"] name = "certifi" version = "2022.12.7" description = "Python package for providing Mozilla's CA Bundle." -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -146,7 +139,6 @@ files = [ name = "charset-normalizer" version = "2.1.0" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." -category = "dev" optional = false python-versions = ">=3.6.0" files = [ @@ -161,7 +153,6 @@ unicode-backport = ["unicodedata2"] name = "click" version = "8.1.3" description = "Composable command line interface toolkit" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -177,7 +168,6 @@ importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} name = "colorama" version = "0.4.5" description = "Cross-platform colored terminal text." -category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" files = [ @@ -189,7 +179,6 @@ files = [ name = "coverage" version = "7.2.7" description = "Code coverage measurement for Python" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -262,7 +251,6 @@ toml = ["tomli"] name = "darglint" version = "1.8.1" description = "A utility for ensuring Google-style docstrings stay up to date with the source code." -category = "dev" optional = false python-versions = ">=3.6,<4.0" files = [ @@ -274,7 +262,6 @@ files = [ name = "deprecated" version = "1.2.14" description = "Python @deprecated decorator to deprecate old python classes, functions or methods." -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ @@ -292,7 +279,6 @@ dev = ["PyTest", "PyTest-Cov", "bump2version (<1)", "sphinx (<2)", "tox"] name = "docutils" version = "0.16" description = "Docutils -- Python Documentation Utilities" -category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" files = [ @@ -304,7 +290,6 @@ files = [ name = "ecpy" version = "1.2.5" description = "Pure Pyhton Elliptic Curve Library" -category = "main" optional = false python-versions = "*" files = [ @@ -316,7 +301,6 @@ files = [ name = "flake8" version = "3.9.2" description = "the modular source code checker: pep8 pyflakes and co" -category = "dev" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" files = [ @@ -334,7 +318,6 @@ pyflakes = ">=2.3.0,<2.4.0" name = "flake8-absolute-import" version = "1.0.0.1" description = "flake8 plugin to require absolute imports" -category = "dev" optional = false python-versions = ">=3.4" files = [ @@ -348,7 +331,6 @@ flake8 = ">=3.7" name = "flake8-annotations" version = "2.7.0" description = "Flake8 Type Annotation Checks" -category = "dev" optional = false python-versions = ">=3.6.2,<4.0.0" files = [ @@ -364,7 +346,6 @@ typed-ast = {version = ">=1.4,<2.0", markers = "python_version < \"3.8\""} name = "flake8-black" version = "0.3.6" description = "flake8 plugin to call black as a code style validator" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -384,7 +365,6 @@ develop = ["build", "twine"] name = "flake8-docstrings" version = "1.7.0" description = "Extension for flake8 which uses pydocstyle to check docstrings" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -400,7 +380,6 @@ pydocstyle = ">=2.1" name = "flake8-isort" version = "6.0.0" description = "flake8 plugin that integrates isort ." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -419,7 +398,6 @@ test = ["pytest"] name = "h11" version = "0.12.0" description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -431,7 +409,6 @@ files = [ name = "httpcore" version = "0.15.0" description = "A minimal low-level HTTP client." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -440,20 +417,19 @@ files = [ ] [package.dependencies] -anyio = ">=3.0.0,<4.0.0" +anyio = "==3.*" certifi = "*" h11 = ">=0.11,<0.13" -sniffio = ">=1.0.0,<2.0.0" +sniffio = "==1.*" [package.extras] http2 = ["h2 (>=3,<5)"] -socks = ["socksio (>=1.0.0,<2.0.0)"] +socks = ["socksio (==1.*)"] [[package]] name = "httpx" version = "0.24.1" description = "The next generation HTTP client." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -469,15 +445,14 @@ sniffio = "*" [package.extras] brotli = ["brotli", "brotlicffi"] -cli = ["click (>=8.0.0,<9.0.0)", "pygments (>=2.0.0,<3.0.0)", "rich (>=10,<14)"] +cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"] http2 = ["h2 (>=3,<5)"] -socks = ["socksio (>=1.0.0,<2.0.0)"] +socks = ["socksio (==1.*)"] [[package]] name = "idna" version = "3.3" description = "Internationalized Domain Names in Applications (IDNA)" -category = "main" optional = false python-versions = ">=3.5" files = [ @@ -489,7 +464,6 @@ files = [ name = "imagesize" version = "1.4.1" description = "Getting image size from png/jpeg/jpeg2000/gif file" -category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ @@ -501,7 +475,6 @@ files = [ name = "importlib-metadata" version = "4.12.0" description = "Read metadata from Python packages" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -522,7 +495,6 @@ testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs name = "isort" version = "5.11.5" description = "A Python utility / library to sort Python imports." -category = "dev" optional = false python-versions = ">=3.7.0" files = [ @@ -540,7 +512,6 @@ requirements-deprecated-finder = ["pip-api", "pipreqs"] name = "jinja2" version = "3.1.2" description = "A very fast and expressive template engine." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -556,69 +527,67 @@ i18n = ["Babel (>=2.7)"] [[package]] name = "markupsafe" -version = "2.1.2" +version = "2.1.3" description = "Safely add untrusted strings to HTML/XML markup." -category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "MarkupSafe-2.1.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:665a36ae6f8f20a4676b53224e33d456a6f5a72657d9c83c2aa00765072f31f7"}, - {file = "MarkupSafe-2.1.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:340bea174e9761308703ae988e982005aedf427de816d1afe98147668cc03036"}, - {file = "MarkupSafe-2.1.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22152d00bf4a9c7c83960521fc558f55a1adbc0631fbb00a9471e097b19d72e1"}, - {file = "MarkupSafe-2.1.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28057e985dace2f478e042eaa15606c7efccb700797660629da387eb289b9323"}, - {file = "MarkupSafe-2.1.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca244fa73f50a800cf8c3ebf7fd93149ec37f5cb9596aa8873ae2c1d23498601"}, - {file = "MarkupSafe-2.1.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d9d971ec1e79906046aa3ca266de79eac42f1dbf3612a05dc9368125952bd1a1"}, - {file = "MarkupSafe-2.1.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:7e007132af78ea9df29495dbf7b5824cb71648d7133cf7848a2a5dd00d36f9ff"}, - {file = "MarkupSafe-2.1.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7313ce6a199651c4ed9d7e4cfb4aa56fe923b1adf9af3b420ee14e6d9a73df65"}, - {file = "MarkupSafe-2.1.2-cp310-cp310-win32.whl", hash = "sha256:c4a549890a45f57f1ebf99c067a4ad0cb423a05544accaf2b065246827ed9603"}, - {file = "MarkupSafe-2.1.2-cp310-cp310-win_amd64.whl", hash = "sha256:835fb5e38fd89328e9c81067fd642b3593c33e1e17e2fdbf77f5676abb14a156"}, - {file = "MarkupSafe-2.1.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2ec4f2d48ae59bbb9d1f9d7efb9236ab81429a764dedca114f5fdabbc3788013"}, - {file = "MarkupSafe-2.1.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:608e7073dfa9e38a85d38474c082d4281f4ce276ac0010224eaba11e929dd53a"}, - {file = "MarkupSafe-2.1.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:65608c35bfb8a76763f37036547f7adfd09270fbdbf96608be2bead319728fcd"}, - {file = "MarkupSafe-2.1.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2bfb563d0211ce16b63c7cb9395d2c682a23187f54c3d79bfec33e6705473c6"}, - {file = "MarkupSafe-2.1.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:da25303d91526aac3672ee6d49a2f3db2d9502a4a60b55519feb1a4c7714e07d"}, - {file = "MarkupSafe-2.1.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:9cad97ab29dfc3f0249b483412c85c8ef4766d96cdf9dcf5a1e3caa3f3661cf1"}, - {file = "MarkupSafe-2.1.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:085fd3201e7b12809f9e6e9bc1e5c96a368c8523fad5afb02afe3c051ae4afcc"}, - {file = "MarkupSafe-2.1.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1bea30e9bf331f3fef67e0a3877b2288593c98a21ccb2cf29b74c581a4eb3af0"}, - {file = "MarkupSafe-2.1.2-cp311-cp311-win32.whl", hash = "sha256:7df70907e00c970c60b9ef2938d894a9381f38e6b9db73c5be35e59d92e06625"}, - {file = "MarkupSafe-2.1.2-cp311-cp311-win_amd64.whl", hash = "sha256:e55e40ff0cc8cc5c07996915ad367fa47da6b3fc091fdadca7f5403239c5fec3"}, - {file = "MarkupSafe-2.1.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a6e40afa7f45939ca356f348c8e23048e02cb109ced1eb8420961b2f40fb373a"}, - {file = "MarkupSafe-2.1.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf877ab4ed6e302ec1d04952ca358b381a882fbd9d1b07cccbfd61783561f98a"}, - {file = "MarkupSafe-2.1.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63ba06c9941e46fa389d389644e2d8225e0e3e5ebcc4ff1ea8506dce646f8c8a"}, - {file = "MarkupSafe-2.1.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f1cd098434e83e656abf198f103a8207a8187c0fc110306691a2e94a78d0abb2"}, - {file = "MarkupSafe-2.1.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:55f44b440d491028addb3b88f72207d71eeebfb7b5dbf0643f7c023ae1fba619"}, - {file = "MarkupSafe-2.1.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:a6f2fcca746e8d5910e18782f976489939d54a91f9411c32051b4aab2bd7c513"}, - {file = "MarkupSafe-2.1.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:0b462104ba25f1ac006fdab8b6a01ebbfbce9ed37fd37fd4acd70c67c973e460"}, - {file = "MarkupSafe-2.1.2-cp37-cp37m-win32.whl", hash = "sha256:7668b52e102d0ed87cb082380a7e2e1e78737ddecdde129acadb0eccc5423859"}, - {file = "MarkupSafe-2.1.2-cp37-cp37m-win_amd64.whl", hash = "sha256:6d6607f98fcf17e534162f0709aaad3ab7a96032723d8ac8750ffe17ae5a0666"}, - {file = "MarkupSafe-2.1.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:a806db027852538d2ad7555b203300173dd1b77ba116de92da9afbc3a3be3eed"}, - {file = "MarkupSafe-2.1.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:a4abaec6ca3ad8660690236d11bfe28dfd707778e2442b45addd2f086d6ef094"}, - {file = "MarkupSafe-2.1.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f03a532d7dee1bed20bc4884194a16160a2de9ffc6354b3878ec9682bb623c54"}, - {file = "MarkupSafe-2.1.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4cf06cdc1dda95223e9d2d3c58d3b178aa5dacb35ee7e3bbac10e4e1faacb419"}, - {file = "MarkupSafe-2.1.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:22731d79ed2eb25059ae3df1dfc9cb1546691cc41f4e3130fe6bfbc3ecbbecfa"}, - {file = "MarkupSafe-2.1.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:f8ffb705ffcf5ddd0e80b65ddf7bed7ee4f5a441ea7d3419e861a12eaf41af58"}, - {file = "MarkupSafe-2.1.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:8db032bf0ce9022a8e41a22598eefc802314e81b879ae093f36ce9ddf39ab1ba"}, - {file = "MarkupSafe-2.1.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2298c859cfc5463f1b64bd55cb3e602528db6fa0f3cfd568d3605c50678f8f03"}, - {file = "MarkupSafe-2.1.2-cp38-cp38-win32.whl", hash = "sha256:50c42830a633fa0cf9e7d27664637532791bfc31c731a87b202d2d8ac40c3ea2"}, - {file = "MarkupSafe-2.1.2-cp38-cp38-win_amd64.whl", hash = "sha256:bb06feb762bade6bf3c8b844462274db0c76acc95c52abe8dbed28ae3d44a147"}, - {file = "MarkupSafe-2.1.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:99625a92da8229df6d44335e6fcc558a5037dd0a760e11d84be2260e6f37002f"}, - {file = "MarkupSafe-2.1.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8bca7e26c1dd751236cfb0c6c72d4ad61d986e9a41bbf76cb445f69488b2a2bd"}, - {file = "MarkupSafe-2.1.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40627dcf047dadb22cd25ea7ecfe9cbf3bbbad0482ee5920b582f3809c97654f"}, - {file = "MarkupSafe-2.1.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40dfd3fefbef579ee058f139733ac336312663c6706d1163b82b3003fb1925c4"}, - {file = "MarkupSafe-2.1.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:090376d812fb6ac5f171e5938e82e7f2d7adc2b629101cec0db8b267815c85e2"}, - {file = "MarkupSafe-2.1.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:2e7821bffe00aa6bd07a23913b7f4e01328c3d5cc0b40b36c0bd81d362faeb65"}, - {file = "MarkupSafe-2.1.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:c0a33bc9f02c2b17c3ea382f91b4db0e6cde90b63b296422a939886a7a80de1c"}, - {file = "MarkupSafe-2.1.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:b8526c6d437855442cdd3d87eede9c425c4445ea011ca38d937db299382e6fa3"}, - {file = "MarkupSafe-2.1.2-cp39-cp39-win32.whl", hash = "sha256:137678c63c977754abe9086a3ec011e8fd985ab90631145dfb9294ad09c102a7"}, - {file = "MarkupSafe-2.1.2-cp39-cp39-win_amd64.whl", hash = "sha256:0576fe974b40a400449768941d5d0858cc624e3249dfd1e0c33674e5c7ca7aed"}, - {file = "MarkupSafe-2.1.2.tar.gz", hash = "sha256:abcabc8c2b26036d62d4c746381a6f7cf60aafcc653198ad678306986b09450d"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cd0f502fe016460680cd20aaa5a76d241d6f35a1c3350c474bac1273803893fa"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e09031c87a1e51556fdcb46e5bd4f59dfb743061cf93c4d6831bf894f125eb57"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68e78619a61ecf91e76aa3e6e8e33fc4894a2bebe93410754bd28fce0a8a4f9f"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65c1a9bcdadc6c28eecee2c119465aebff8f7a584dd719facdd9e825ec61ab52"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:525808b8019e36eb524b8c68acdd63a37e75714eac50e988180b169d64480a00"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:962f82a3086483f5e5f64dbad880d31038b698494799b097bc59c2edf392fce6"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:aa7bd130efab1c280bed0f45501b7c8795f9fdbeb02e965371bbef3523627779"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c9c804664ebe8f83a211cace637506669e7890fec1b4195b505c214e50dd4eb7"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-win32.whl", hash = "sha256:10bbfe99883db80bdbaff2dcf681dfc6533a614f700da1287707e8a5d78a8431"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-win_amd64.whl", hash = "sha256:1577735524cdad32f9f694208aa75e422adba74f1baee7551620e43a3141f559"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ad9e82fb8f09ade1c3e1b996a6337afac2b8b9e365f926f5a61aacc71adc5b3c"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3c0fae6c3be832a0a0473ac912810b2877c8cb9d76ca48de1ed31e1c68386575"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b076b6226fb84157e3f7c971a47ff3a679d837cf338547532ab866c57930dbee"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bfce63a9e7834b12b87c64d6b155fdd9b3b96191b6bd334bf37db7ff1fe457f2"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:338ae27d6b8745585f87218a3f23f1512dbf52c26c28e322dbe54bcede54ccb9"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e4dd52d80b8c83fdce44e12478ad2e85c64ea965e75d66dbeafb0a3e77308fcc"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:df0be2b576a7abbf737b1575f048c23fb1d769f267ec4358296f31c2479db8f9"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca379055a47383d02a5400cb0d110cef0a776fc644cda797db0c5696cfd7e18e"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:b7ff0f54cb4ff66dd38bebd335a38e2c22c41a8ee45aa608efc890ac3e3931bc"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c011a4149cfbcf9f03994ec2edffcb8b1dc2d2aede7ca243746df97a5d41ce48"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:56d9f2ecac662ca1611d183feb03a3fa4406469dafe241673d521dd5ae92a155"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-win32.whl", hash = "sha256:8758846a7e80910096950b67071243da3e5a20ed2546e6392603c096778d48e0"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-win_amd64.whl", hash = "sha256:787003c0ddb00500e49a10f2844fac87aa6ce977b90b0feaaf9de23c22508b24"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:2ef12179d3a291be237280175b542c07a36e7f60718296278d8593d21ca937d4"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2c1b19b3aaacc6e57b7e25710ff571c24d6c3613a45e905b1fde04d691b98ee0"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8afafd99945ead6e075b973fefa56379c5b5c53fd8937dad92c662da5d8fd5ee"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c41976a29d078bb235fea9b2ecd3da465df42a562910f9022f1a03107bd02be"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d080e0a5eb2529460b30190fcfcc4199bd7f827663f858a226a81bc27beaa97e"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:69c0f17e9f5a7afdf2cc9fb2d1ce6aabdb3bafb7f38017c0b77862bcec2bbad8"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:504b320cd4b7eff6f968eddf81127112db685e81f7e36e75f9f84f0df46041c3"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:42de32b22b6b804f42c5d98be4f7e5e977ecdd9ee9b660fda1a3edf03b11792d"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-win32.whl", hash = "sha256:ceb01949af7121f9fc39f7d27f91be8546f3fb112c608bc4029aef0bab86a2a5"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-win_amd64.whl", hash = "sha256:1b40069d487e7edb2676d3fbdb2b0829ffa2cd63a2ec26c4938b2d34391b4ecc"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8023faf4e01efadfa183e863fefde0046de576c6f14659e8782065bcece22198"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6b2b56950d93e41f33b4223ead100ea0fe11f8e6ee5f641eb753ce4b77a7042b"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9dcdfd0eaf283af041973bff14a2e143b8bd64e069f4c383416ecd79a81aab58"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:05fb21170423db021895e1ea1e1f3ab3adb85d1c2333cbc2310f2a26bc77272e"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:282c2cb35b5b673bbcadb33a585408104df04f14b2d9b01d4c345a3b92861c2c"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ab4a0df41e7c16a1392727727e7998a467472d0ad65f3ad5e6e765015df08636"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7ef3cb2ebbf91e330e3bb937efada0edd9003683db6b57bb108c4001f37a02ea"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0a4e4a1aff6c7ac4cd55792abf96c915634c2b97e3cc1c7129578aa68ebd754e"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-win32.whl", hash = "sha256:fec21693218efe39aa7f8599346e90c705afa52c5b31ae019b2e57e8f6542bb2"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-win_amd64.whl", hash = "sha256:3fd4abcb888d15a94f32b75d8fd18ee162ca0c064f35b11134be77050296d6ba"}, + {file = "MarkupSafe-2.1.3.tar.gz", hash = "sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad"}, ] [[package]] name = "mccabe" version = "0.6.1" description = "McCabe checker, plugin for flake8" -category = "dev" optional = false python-versions = "*" files = [ @@ -628,45 +597,44 @@ files = [ [[package]] name = "mypy" -version = "1.3.0" +version = "1.4.1" description = "Optional static typing for Python" -category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "mypy-1.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c1eb485cea53f4f5284e5baf92902cd0088b24984f4209e25981cc359d64448d"}, - {file = "mypy-1.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4c99c3ecf223cf2952638da9cd82793d8f3c0c5fa8b6ae2b2d9ed1e1ff51ba85"}, - {file = "mypy-1.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:550a8b3a19bb6589679a7c3c31f64312e7ff482a816c96e0cecec9ad3a7564dd"}, - {file = "mypy-1.3.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:cbc07246253b9e3d7d74c9ff948cd0fd7a71afcc2b77c7f0a59c26e9395cb152"}, - {file = "mypy-1.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:a22435632710a4fcf8acf86cbd0d69f68ac389a3892cb23fbad176d1cddaf228"}, - {file = "mypy-1.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6e33bb8b2613614a33dff70565f4c803f889ebd2f859466e42b46e1df76018dd"}, - {file = "mypy-1.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7d23370d2a6b7a71dc65d1266f9a34e4cde9e8e21511322415db4b26f46f6b8c"}, - {file = "mypy-1.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:658fe7b674769a0770d4b26cb4d6f005e88a442fe82446f020be8e5f5efb2fae"}, - {file = "mypy-1.3.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:6e42d29e324cdda61daaec2336c42512e59c7c375340bd202efa1fe0f7b8f8ca"}, - {file = "mypy-1.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:d0b6c62206e04061e27009481cb0ec966f7d6172b5b936f3ead3d74f29fe3dcf"}, - {file = "mypy-1.3.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:76ec771e2342f1b558c36d49900dfe81d140361dd0d2df6cd71b3db1be155409"}, - {file = "mypy-1.3.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ebc95f8386314272bbc817026f8ce8f4f0d2ef7ae44f947c4664efac9adec929"}, - {file = "mypy-1.3.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:faff86aa10c1aa4a10e1a301de160f3d8fc8703b88c7e98de46b531ff1276a9a"}, - {file = "mypy-1.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:8c5979d0deb27e0f4479bee18ea0f83732a893e81b78e62e2dda3e7e518c92ee"}, - {file = "mypy-1.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c5d2cc54175bab47011b09688b418db71403aefad07cbcd62d44010543fc143f"}, - {file = "mypy-1.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:87df44954c31d86df96c8bd6e80dfcd773473e877ac6176a8e29898bfb3501cb"}, - {file = "mypy-1.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:473117e310febe632ddf10e745a355714e771ffe534f06db40702775056614c4"}, - {file = "mypy-1.3.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:74bc9b6e0e79808bf8678d7678b2ae3736ea72d56eede3820bd3849823e7f305"}, - {file = "mypy-1.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:44797d031a41516fcf5cbfa652265bb994e53e51994c1bd649ffcd0c3a7eccbf"}, - {file = "mypy-1.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ddae0f39ca146972ff6bb4399f3b2943884a774b8771ea0a8f50e971f5ea5ba8"}, - {file = "mypy-1.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1c4c42c60a8103ead4c1c060ac3cdd3ff01e18fddce6f1016e08939647a0e703"}, - {file = "mypy-1.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e86c2c6852f62f8f2b24cb7a613ebe8e0c7dc1402c61d36a609174f63e0ff017"}, - {file = "mypy-1.3.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:f9dca1e257d4cc129517779226753dbefb4f2266c4eaad610fc15c6a7e14283e"}, - {file = "mypy-1.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:95d8d31a7713510685b05fbb18d6ac287a56c8f6554d88c19e73f724a445448a"}, - {file = "mypy-1.3.0-py3-none-any.whl", hash = "sha256:a8763e72d5d9574d45ce5881962bc8e9046bf7b375b0abf031f3e6811732a897"}, - {file = "mypy-1.3.0.tar.gz", hash = "sha256:e1f4d16e296f5135624b34e8fb741eb0eadedca90862405b1f1fde2040b9bd11"}, + {file = "mypy-1.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:566e72b0cd6598503e48ea610e0052d1b8168e60a46e0bfd34b3acf2d57f96a8"}, + {file = "mypy-1.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ca637024ca67ab24a7fd6f65d280572c3794665eaf5edcc7e90a866544076878"}, + {file = "mypy-1.4.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0dde1d180cd84f0624c5dcaaa89c89775550a675aff96b5848de78fb11adabcd"}, + {file = "mypy-1.4.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8c4d8e89aa7de683e2056a581ce63c46a0c41e31bd2b6d34144e2c80f5ea53dc"}, + {file = "mypy-1.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:bfdca17c36ae01a21274a3c387a63aa1aafe72bff976522886869ef131b937f1"}, + {file = "mypy-1.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7549fbf655e5825d787bbc9ecf6028731973f78088fbca3a1f4145c39ef09462"}, + {file = "mypy-1.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:98324ec3ecf12296e6422939e54763faedbfcc502ea4a4c38502082711867258"}, + {file = "mypy-1.4.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:141dedfdbfe8a04142881ff30ce6e6653c9685b354876b12e4fe6c78598b45e2"}, + {file = "mypy-1.4.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8207b7105829eca6f3d774f64a904190bb2231de91b8b186d21ffd98005f14a7"}, + {file = "mypy-1.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:16f0db5b641ba159eff72cff08edc3875f2b62b2fa2bc24f68c1e7a4e8232d01"}, + {file = "mypy-1.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:470c969bb3f9a9efcedbadcd19a74ffb34a25f8e6b0e02dae7c0e71f8372f97b"}, + {file = "mypy-1.4.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e5952d2d18b79f7dc25e62e014fe5a23eb1a3d2bc66318df8988a01b1a037c5b"}, + {file = "mypy-1.4.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:190b6bab0302cec4e9e6767d3eb66085aef2a1cc98fe04936d8a42ed2ba77bb7"}, + {file = "mypy-1.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:9d40652cc4fe33871ad3338581dca3297ff5f2213d0df345bcfbde5162abf0c9"}, + {file = "mypy-1.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:01fd2e9f85622d981fd9063bfaef1aed6e336eaacca00892cd2d82801ab7c042"}, + {file = "mypy-1.4.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2460a58faeea905aeb1b9b36f5065f2dc9a9c6e4c992a6499a2360c6c74ceca3"}, + {file = "mypy-1.4.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a2746d69a8196698146a3dbe29104f9eb6a2a4d8a27878d92169a6c0b74435b6"}, + {file = "mypy-1.4.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:ae704dcfaa180ff7c4cfbad23e74321a2b774f92ca77fd94ce1049175a21c97f"}, + {file = "mypy-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:43d24f6437925ce50139a310a64b2ab048cb2d3694c84c71c3f2a1626d8101dc"}, + {file = "mypy-1.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c482e1246726616088532b5e964e39765b6d1520791348e6c9dc3af25b233828"}, + {file = "mypy-1.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:43b592511672017f5b1a483527fd2684347fdffc041c9ef53428c8dc530f79a3"}, + {file = "mypy-1.4.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:34a9239d5b3502c17f07fd7c0b2ae6b7dd7d7f6af35fbb5072c6208e76295816"}, + {file = "mypy-1.4.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5703097c4936bbb9e9bce41478c8d08edd2865e177dc4c52be759f81ee4dd26c"}, + {file = "mypy-1.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:e02d700ec8d9b1859790c0475df4e4092c7bf3272a4fd2c9f33d87fac4427b8f"}, + {file = "mypy-1.4.1-py3-none-any.whl", hash = "sha256:45d32cec14e7b97af848bddd97d85ea4f0db4d5a149ed9676caa4eb2f7402bb4"}, + {file = "mypy-1.4.1.tar.gz", hash = "sha256:9bbcd9ab8ea1f2e1c8031c21445b511442cc45c89951e49bbf852cbb70755b1b"}, ] [package.dependencies] mypy-extensions = ">=1.0.0" tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} typed-ast = {version = ">=1.4.0,<2", markers = "python_version < \"3.8\""} -typing-extensions = ">=3.10" +typing-extensions = ">=4.1.0" [package.extras] dmypy = ["psutil (>=4.0)"] @@ -678,7 +646,6 @@ reports = ["lxml"] name = "mypy-extensions" version = "1.0.0" description = "Type system extensions for programs checked with the mypy type checker." -category = "dev" optional = false python-versions = ">=3.5" files = [ @@ -690,7 +657,6 @@ files = [ name = "packaging" version = "23.1" description = "Core utilities for Python packages" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -702,7 +668,6 @@ files = [ name = "pastel" version = "0.2.1" description = "Bring colors to your terminal." -category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ @@ -714,7 +679,6 @@ files = [ name = "pathspec" version = "0.9.0" description = "Utility library for gitignore style pattern matching of file paths." -category = "dev" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" files = [ @@ -726,7 +690,6 @@ files = [ name = "platformdirs" version = "2.5.2" description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -742,7 +705,6 @@ test = ["appdirs (==1.4.4)", "pytest (>=6)", "pytest-cov (>=2.7)", "pytest-mock name = "poethepoet" version = "0.19.0" description = "A task runner that works well with poetry." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -761,7 +723,6 @@ poetry-plugin = ["poetry (>=1.0,<2.0)"] name = "pycodestyle" version = "2.7.0" description = "Python style guide checker" -category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ @@ -771,52 +732,49 @@ files = [ [[package]] name = "pycryptodome" -version = "3.17" +version = "3.18.0" description = "Cryptographic library for Python" -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" files = [ - {file = "pycryptodome-3.17-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:2c5631204ebcc7ae33d11c43037b2dafe25e2ab9c1de6448eb6502ac69c19a56"}, - {file = "pycryptodome-3.17-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:04779cc588ad8f13c80a060b0b1c9d1c203d051d8a43879117fe6b8aaf1cd3fa"}, - {file = "pycryptodome-3.17-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:f812d58c5af06d939b2baccdda614a3ffd80531a26e5faca2c9f8b1770b2b7af"}, - {file = "pycryptodome-3.17-cp27-cp27m-manylinux2014_aarch64.whl", hash = "sha256:9453b4e21e752df8737fdffac619e93c9f0ec55ead9a45df782055eb95ef37d9"}, - {file = "pycryptodome-3.17-cp27-cp27m-musllinux_1_1_aarch64.whl", hash = "sha256:121d61663267f73692e8bde5ec0d23c9146465a0d75cad75c34f75c752527b01"}, - {file = "pycryptodome-3.17-cp27-cp27m-win32.whl", hash = "sha256:ba2d4fcb844c6ba5df4bbfee9352ad5352c5ae939ac450e06cdceff653280450"}, - {file = "pycryptodome-3.17-cp27-cp27m-win_amd64.whl", hash = "sha256:87e2ca3aa557781447428c4b6c8c937f10ff215202ab40ece5c13a82555c10d6"}, - {file = "pycryptodome-3.17-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:f44c0d28716d950135ff21505f2c764498eda9d8806b7c78764165848aa419bc"}, - {file = "pycryptodome-3.17-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:5a790bc045003d89d42e3b9cb3cc938c8561a57a88aaa5691512e8540d1ae79c"}, - {file = "pycryptodome-3.17-cp27-cp27mu-manylinux2014_aarch64.whl", hash = "sha256:d086d46774e27b280e4cece8ab3d87299cf0d39063f00f1e9290d096adc5662a"}, - {file = "pycryptodome-3.17-cp27-cp27mu-musllinux_1_1_aarch64.whl", hash = "sha256:5587803d5b66dfd99e7caa31ed91fba0fdee3661c5d93684028ad6653fce725f"}, - {file = "pycryptodome-3.17-cp35-abi3-macosx_10_9_universal2.whl", hash = "sha256:e7debd9c439e7b84f53be3cf4ba8b75b3d0b6e6015212355d6daf44ac672e210"}, - {file = "pycryptodome-3.17-cp35-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ca1ceb6303be1282148f04ac21cebeebdb4152590842159877778f9cf1634f09"}, - {file = "pycryptodome-3.17-cp35-abi3-manylinux2014_aarch64.whl", hash = "sha256:dc22cc00f804485a3c2a7e2010d9f14a705555f67020eb083e833cabd5bd82e4"}, - {file = "pycryptodome-3.17-cp35-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80ea8333b6a5f2d9e856ff2293dba2e3e661197f90bf0f4d5a82a0a6bc83a626"}, - {file = "pycryptodome-3.17-cp35-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c133f6721fba313722a018392a91e3c69d3706ae723484841752559e71d69dc6"}, - {file = "pycryptodome-3.17-cp35-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:333306eaea01fde50a73c4619e25631e56c4c61bd0fb0a2346479e67e3d3a820"}, - {file = "pycryptodome-3.17-cp35-abi3-musllinux_1_1_i686.whl", hash = "sha256:1a30f51b990994491cec2d7d237924e5b6bd0d445da9337d77de384ad7f254f9"}, - {file = "pycryptodome-3.17-cp35-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:909e36a43fe4a8a3163e9c7fc103867825d14a2ecb852a63d3905250b308a4e5"}, - {file = "pycryptodome-3.17-cp35-abi3-win32.whl", hash = "sha256:a3228728a3808bc9f18c1797ec1179a0efb5068c817b2ffcf6bcd012494dffb2"}, - {file = "pycryptodome-3.17-cp35-abi3-win_amd64.whl", hash = "sha256:9ec565e89a6b400eca814f28d78a9ef3f15aea1df74d95b28b7720739b28f37f"}, - {file = "pycryptodome-3.17-pp27-pypy_73-macosx_10_9_x86_64.whl", hash = "sha256:e1819b67bcf6ca48341e9b03c2e45b1c891fa8eb1a8458482d14c2805c9616f2"}, - {file = "pycryptodome-3.17-pp27-pypy_73-manylinux2010_x86_64.whl", hash = "sha256:f8e550caf52472ae9126953415e4fc554ab53049a5691c45b8816895c632e4d7"}, - {file = "pycryptodome-3.17-pp27-pypy_73-win32.whl", hash = "sha256:afbcdb0eda20a0e1d44e3a1ad6d4ec3c959210f4b48cabc0e387a282f4c7deb8"}, - {file = "pycryptodome-3.17-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a74f45aee8c5cc4d533e585e0e596e9f78521e1543a302870a27b0ae2106381e"}, - {file = "pycryptodome-3.17-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:38bbd6717eac084408b4094174c0805bdbaba1f57fc250fd0309ae5ec9ed7e09"}, - {file = "pycryptodome-3.17-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f68d6c8ea2974a571cacb7014dbaada21063a0375318d88ac1f9300bc81e93c3"}, - {file = "pycryptodome-3.17-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:8198f2b04c39d817b206ebe0db25a6653bb5f463c2319d6f6d9a80d012ac1e37"}, - {file = "pycryptodome-3.17-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:3a232474cd89d3f51e4295abe248a8b95d0332d153bf46444e415409070aae1e"}, - {file = "pycryptodome-3.17-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4992ec965606054e8326e83db1c8654f0549cdb26fce1898dc1a20bc7684ec1c"}, - {file = "pycryptodome-3.17-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:53068e33c74f3b93a8158dacaa5d0f82d254a81b1002e0cd342be89fcb3433eb"}, - {file = "pycryptodome-3.17-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:74794a2e2896cd0cf56fdc9db61ef755fa812b4a4900fa46c49045663a92b8d0"}, - {file = "pycryptodome-3.17.tar.gz", hash = "sha256:bce2e2d8e82fcf972005652371a3e8731956a0c1fbb719cc897943b3695ad91b"}, + {file = "pycryptodome-3.18.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:d1497a8cd4728db0e0da3c304856cb37c0c4e3d0b36fcbabcc1600f18504fc54"}, + {file = "pycryptodome-3.18.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:928078c530da78ff08e10eb6cada6e0dff386bf3d9fa9871b4bbc9fbc1efe024"}, + {file = "pycryptodome-3.18.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:157c9b5ba5e21b375f052ca78152dd309a09ed04703fd3721dce3ff8ecced148"}, + {file = "pycryptodome-3.18.0-cp27-cp27m-manylinux2014_aarch64.whl", hash = "sha256:d20082bdac9218649f6abe0b885927be25a917e29ae0502eaf2b53f1233ce0c2"}, + {file = "pycryptodome-3.18.0-cp27-cp27m-musllinux_1_1_aarch64.whl", hash = "sha256:e8ad74044e5f5d2456c11ed4cfd3e34b8d4898c0cb201c4038fe41458a82ea27"}, + {file = "pycryptodome-3.18.0-cp27-cp27m-win32.whl", hash = "sha256:62a1e8847fabb5213ccde38915563140a5b338f0d0a0d363f996b51e4a6165cf"}, + {file = "pycryptodome-3.18.0-cp27-cp27m-win_amd64.whl", hash = "sha256:16bfd98dbe472c263ed2821284118d899c76968db1a6665ade0c46805e6b29a4"}, + {file = "pycryptodome-3.18.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:7a3d22c8ee63de22336679e021c7f2386f7fc465477d59675caa0e5706387944"}, + {file = "pycryptodome-3.18.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:78d863476e6bad2a592645072cc489bb90320972115d8995bcfbee2f8b209918"}, + {file = "pycryptodome-3.18.0-cp27-cp27mu-manylinux2014_aarch64.whl", hash = "sha256:b6a610f8bfe67eab980d6236fdc73bfcdae23c9ed5548192bb2d530e8a92780e"}, + {file = "pycryptodome-3.18.0-cp27-cp27mu-musllinux_1_1_aarch64.whl", hash = "sha256:422c89fd8df8a3bee09fb8d52aaa1e996120eafa565437392b781abec2a56e14"}, + {file = "pycryptodome-3.18.0-cp35-abi3-macosx_10_9_universal2.whl", hash = "sha256:9ad6f09f670c466aac94a40798e0e8d1ef2aa04589c29faa5b9b97566611d1d1"}, + {file = "pycryptodome-3.18.0-cp35-abi3-macosx_10_9_x86_64.whl", hash = "sha256:53aee6be8b9b6da25ccd9028caf17dcdce3604f2c7862f5167777b707fbfb6cb"}, + {file = "pycryptodome-3.18.0-cp35-abi3-manylinux2014_aarch64.whl", hash = "sha256:10da29526a2a927c7d64b8f34592f461d92ae55fc97981aab5bbcde8cb465bb6"}, + {file = "pycryptodome-3.18.0-cp35-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f21efb8438971aa16924790e1c3dba3a33164eb4000106a55baaed522c261acf"}, + {file = "pycryptodome-3.18.0-cp35-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4944defabe2ace4803f99543445c27dd1edbe86d7d4edb87b256476a91e9ffa4"}, + {file = "pycryptodome-3.18.0-cp35-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:51eae079ddb9c5f10376b4131be9589a6554f6fd84f7f655180937f611cd99a2"}, + {file = "pycryptodome-3.18.0-cp35-abi3-musllinux_1_1_i686.whl", hash = "sha256:83c75952dcf4a4cebaa850fa257d7a860644c70a7cd54262c237c9f2be26f76e"}, + {file = "pycryptodome-3.18.0-cp35-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:957b221d062d5752716923d14e0926f47670e95fead9d240fa4d4862214b9b2f"}, + {file = "pycryptodome-3.18.0-cp35-abi3-win32.whl", hash = "sha256:795bd1e4258a2c689c0b1f13ce9684fa0dd4c0e08680dcf597cf9516ed6bc0f3"}, + {file = "pycryptodome-3.18.0-cp35-abi3-win_amd64.whl", hash = "sha256:b1d9701d10303eec8d0bd33fa54d44e67b8be74ab449052a8372f12a66f93fb9"}, + {file = "pycryptodome-3.18.0-pp27-pypy_73-manylinux2010_x86_64.whl", hash = "sha256:cb1be4d5af7f355e7d41d36d8eec156ef1382a88638e8032215c215b82a4b8ec"}, + {file = "pycryptodome-3.18.0-pp27-pypy_73-win32.whl", hash = "sha256:fc0a73f4db1e31d4a6d71b672a48f3af458f548059aa05e83022d5f61aac9c08"}, + {file = "pycryptodome-3.18.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:f022a4fd2a5263a5c483a2bb165f9cb27f2be06f2f477113783efe3fe2ad887b"}, + {file = "pycryptodome-3.18.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:363dd6f21f848301c2dcdeb3c8ae5f0dee2286a5e952a0f04954b82076f23825"}, + {file = "pycryptodome-3.18.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:12600268763e6fec3cefe4c2dcdf79bde08d0b6dc1813887e789e495cb9f3403"}, + {file = "pycryptodome-3.18.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:4604816adebd4faf8810782f137f8426bf45fee97d8427fa8e1e49ea78a52e2c"}, + {file = "pycryptodome-3.18.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:01489bbdf709d993f3058e2996f8f40fee3f0ea4d995002e5968965fa2fe89fb"}, + {file = "pycryptodome-3.18.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3811e31e1ac3069988f7a1c9ee7331b942e605dfc0f27330a9ea5997e965efb2"}, + {file = "pycryptodome-3.18.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6f4b967bb11baea9128ec88c3d02f55a3e338361f5e4934f5240afcb667fdaec"}, + {file = "pycryptodome-3.18.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:9c8eda4f260072f7dbe42f473906c659dcbadd5ae6159dfb49af4da1293ae380"}, + {file = "pycryptodome-3.18.0.tar.gz", hash = "sha256:c9adee653fc882d98956e33ca2c1fb582e23a8af7ac82fee75bd6113c55a0413"}, ] [[package]] name = "pydocstyle" version = "6.1.1" description = "Python docstring style checker" -category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -834,7 +792,6 @@ toml = ["toml"] name = "pyflakes" version = "2.3.1" description = "passive checker of Python programs" -category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ @@ -846,7 +803,6 @@ files = [ name = "pygments" version = "2.12.0" description = "Pygments is a syntax highlighting package written in Python." -category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -858,7 +814,6 @@ files = [ name = "pytz" version = "2022.1" description = "World timezone definitions, modern and historical" -category = "dev" optional = false python-versions = "*" files = [ @@ -870,7 +825,6 @@ files = [ name = "requests" version = "2.31.0" description = "Python HTTP for Humans." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -892,7 +846,6 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] name = "sniffio" version = "1.2.0" description = "Sniff out which async library your code is running under" -category = "main" optional = false python-versions = ">=3.5" files = [ @@ -904,7 +857,6 @@ files = [ name = "snowballstemmer" version = "2.2.0" description = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms." -category = "dev" optional = false python-versions = "*" files = [ @@ -916,7 +868,6 @@ files = [ name = "sphinx" version = "5.3.0" description = "Python documentation generator" -category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -950,20 +901,19 @@ test = ["cython", "html5lib", "pytest (>=4.6)", "typed_ast"] [[package]] name = "sphinx-rtd-theme" -version = "1.2.1" +version = "1.2.2" description = "Read the Docs theme for Sphinx" -category = "dev" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" files = [ - {file = "sphinx_rtd_theme-1.2.1-py2.py3-none-any.whl", hash = "sha256:2cc9351176cbf91944ce44cefd4fab6c3b76ac53aa9e15d6db45a3229ad7f866"}, - {file = "sphinx_rtd_theme-1.2.1.tar.gz", hash = "sha256:cf9a7dc0352cf179c538891cb28d6fad6391117d4e21c891776ab41dd6c8ff70"}, + {file = "sphinx_rtd_theme-1.2.2-py2.py3-none-any.whl", hash = "sha256:6a7e7d8af34eb8fc57d52a09c6b6b9c46ff44aea5951bc831eeb9245378f3689"}, + {file = "sphinx_rtd_theme-1.2.2.tar.gz", hash = "sha256:01c5c5a72e2d025bd23d1f06c59a4831b06e6ce6c01fdd5ebfe9986c0a880fc7"}, ] [package.dependencies] docutils = "<0.19" sphinx = ">=1.6,<7" -sphinxcontrib-jquery = {version = ">=2.0.0,<3.0.0 || >3.0.0", markers = "python_version > \"3\""} +sphinxcontrib-jquery = ">=4,<5" [package.extras] dev = ["bump2version", "sphinxcontrib-httpdomain", "transifex-client", "wheel"] @@ -972,7 +922,6 @@ dev = ["bump2version", "sphinxcontrib-httpdomain", "transifex-client", "wheel"] name = "sphinxcontrib-applehelp" version = "1.0.2" description = "sphinxcontrib-applehelp is a sphinx extension which outputs Apple help books" -category = "dev" optional = false python-versions = ">=3.5" files = [ @@ -988,7 +937,6 @@ test = ["pytest"] name = "sphinxcontrib-devhelp" version = "1.0.2" description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp document." -category = "dev" optional = false python-versions = ">=3.5" files = [ @@ -1004,7 +952,6 @@ test = ["pytest"] name = "sphinxcontrib-htmlhelp" version = "2.0.0" description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files" -category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -1020,7 +967,6 @@ test = ["html5lib", "pytest"] name = "sphinxcontrib-jquery" version = "4.1" description = "Extension to include jQuery on newer Sphinx releases" -category = "dev" optional = false python-versions = ">=2.7" files = [ @@ -1035,7 +981,6 @@ Sphinx = ">=1.8" name = "sphinxcontrib-jsmath" version = "1.0.1" description = "A sphinx extension which renders display math in HTML via JavaScript" -category = "dev" optional = false python-versions = ">=3.5" files = [ @@ -1050,7 +995,6 @@ test = ["flake8", "mypy", "pytest"] name = "sphinxcontrib-qthelp" version = "1.0.3" description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp document." -category = "dev" optional = false python-versions = ">=3.5" files = [ @@ -1066,7 +1010,6 @@ test = ["pytest"] name = "sphinxcontrib-serializinghtml" version = "1.1.5" description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)." -category = "dev" optional = false python-versions = ">=3.5" files = [ @@ -1082,7 +1025,6 @@ test = ["pytest"] name = "tomli" version = "2.0.1" description = "A lil' TOML parser" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1094,7 +1036,6 @@ files = [ name = "typed-ast" version = "1.5.4" description = "a fork of Python 2 and 3 ast modules with type comment support" -category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -1128,7 +1069,6 @@ files = [ name = "types-deprecated" version = "1.2.9.2" description = "Typing stubs for Deprecated" -category = "main" optional = false python-versions = "*" files = [ @@ -1138,21 +1078,19 @@ files = [ [[package]] name = "typing-extensions" -version = "4.6.2" +version = "4.7.1" description = "Backported and Experimental Type Hints for Python 3.7+" -category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "typing_extensions-4.6.2-py3-none-any.whl", hash = "sha256:3a8b36f13dd5fdc5d1b16fe317f5668545de77fa0b8e02006381fd49d731ab98"}, - {file = "typing_extensions-4.6.2.tar.gz", hash = "sha256:06006244c70ac8ee83fa8282cb188f697b8db25bc8b4df07be1873c43897060c"}, + {file = "typing_extensions-4.7.1-py3-none-any.whl", hash = "sha256:440d5dd3af93b060174bf433bccd69b0babc3b15b1a8dca43789fd7f61514b36"}, + {file = "typing_extensions-4.7.1.tar.gz", hash = "sha256:b75ddc264f0ba5615db7ba217daeb99701ad295353c45f9e95963337ceeeffb2"}, ] [[package]] name = "urllib3" version = "1.26.10" description = "HTTP library with thread-safe connection pooling, file post, and more." -category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, <4" files = [ @@ -1169,7 +1107,6 @@ socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] name = "websockets" version = "10.3" description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1227,7 +1164,6 @@ files = [ name = "wrapt" version = "1.14.1" description = "Module for decorators, wrappers and monkey patching." -category = "main" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" files = [ @@ -1301,7 +1237,6 @@ files = [ name = "zipp" version = "3.8.0" description = "Backport of pathlib-compatible object wrapper for zip files" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1316,4 +1251,4 @@ testing = ["func-timeout", "jaraco.itertools", "pytest (>=6)", "pytest-black (>= [metadata] lock-version = "2.0" python-versions = "^3.7" -content-hash = "657adab97a2dd340a128d222ae04da2b96bb41646b25fbd6d54d691ab4f9d27e" +content-hash = "6290ab443802e830fc1b75ef6333778b42fefc36c9aaa0da2931d0b28b9994ab" diff --git a/pyproject.toml b/pyproject.toml index d29cc2fc1..5a50c549f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -54,11 +54,11 @@ flake8-isort = "^6.0.0" flake8-annotations = "2.7.0" flake8-absolute-import = "^1.0" darglint = "^1.5.8" -sphinx-rtd-theme = "^1.2.1" +sphinx-rtd-theme = "^1.2.2" aiounittest = "^1.4.0" coverage = "^7.2.7" Jinja2 = "^3.1.2" -MarkupSafe = "2.1.2" +MarkupSafe = "2.1.3" Sphinx = "^5.3.0" poethepoet = "^0.19.0" diff --git a/snippets/get_transaction.py b/snippets/get_transaction.py index 59f9d6c4f..f22b15c8c 100644 --- a/snippets/get_transaction.py +++ b/snippets/get_transaction.py @@ -1,6 +1,6 @@ """Example of how we can see a transaction that was validated on the ledger""" from xrpl.clients import JsonRpcClient -from xrpl.models.requests import Ledger, Tx +from xrpl.models import Ledger, Tx # References # - https://xrpl.org/look-up-transaction-results.html diff --git a/snippets/multisign.py b/snippets/multisign.py index 84deb9cf2..11f217e86 100644 --- a/snippets/multisign.py +++ b/snippets/multisign.py @@ -1,24 +1,24 @@ """Example of how we can multisign a transaction""" from xrpl.clients import JsonRpcClient -from xrpl.models.transactions import AccountSet, SignerEntry, SignerListSet +from xrpl.models import AccountSet, SignerEntry, SignerListSet from xrpl.transaction import autofill, multisign, sign, submit_and_wait from xrpl.utils import str_to_hex -from xrpl.wallet import generate_faucet_wallet +from xrpl.wallet import Wallet, generate_faucet_wallet client = JsonRpcClient("https://s.altnet.rippletest.net:51234") # Create a wallets to use for multisigning # Prints debug info as it creates the wallet master_wallet = generate_faucet_wallet(client, debug=True) -signer_wallet_1 = generate_faucet_wallet(client, debug=True) -signer_wallet_2 = generate_faucet_wallet(client, debug=True) +signer_wallet_1 = Wallet.create() +signer_wallet_2 = Wallet.create() signer_entries = [ - SignerEntry(account=signer_wallet_1.classic_address, signer_weight=1), - SignerEntry(account=signer_wallet_2.classic_address, signer_weight=1), + SignerEntry(account=signer_wallet_1.address, signer_weight=1), + SignerEntry(account=signer_wallet_2.address, signer_weight=1), ] signer_list_set_tx = SignerListSet( - account=master_wallet.classic_address, + account=master_wallet.address, signer_quorum=2, signer_entries=signer_entries, ) @@ -34,7 +34,7 @@ # Now that we've set up multisigning, let's try using it to submit an AccountSet # transaction. account_set_tx = AccountSet( - account=master_wallet.classic_address, domain=str_to_hex("example.com") + account=master_wallet.address, domain=str_to_hex("example.com") ) autofilled_account_set_tx = autofill(account_set_tx, client, len(signer_entries)) print("AccountSet transaction is ready to be multisigned") @@ -48,7 +48,7 @@ multisigned_tx = multisign(autofilled_account_set_tx, [tx_1, tx_2]) print("Successfully multisigned the transaction") -print(multisigned_tx) +print(multisigned_tx.to_xrpl()) multisigned_tx_response = submit_and_wait(multisigned_tx, client) diff --git a/snippets/partial_payment.py b/snippets/partial_payment.py index ca5039852..02ff0fd84 100644 --- a/snippets/partial_payment.py +++ b/snippets/partial_payment.py @@ -1,8 +1,12 @@ """Example of how to handle partial payments""" from xrpl.clients import JsonRpcClient -from xrpl.models.amounts import IssuedCurrencyAmount -from xrpl.models.requests import AccountLines -from xrpl.models.transactions import Payment, PaymentFlag, TrustSet +from xrpl.models import ( + AccountLines, + IssuedCurrencyAmount, + Payment, + PaymentFlag, + TrustSet, +) from xrpl.transaction import submit_and_wait from xrpl.wallet import generate_faucet_wallet @@ -21,11 +25,11 @@ # Create a TrustSet to issue an IOU `FOO` and set limit on it trust_set_tx = TrustSet( - account=wallet2.classic_address, + account=wallet2.address, limit_amount=IssuedCurrencyAmount( currency="FOO", value="10000000000", - issuer=wallet1.classic_address, + issuer=wallet1.address, ), ) @@ -34,19 +38,19 @@ # Both balances should be zero since nothing has been sent yet print("Balances after trustline is claimed:") -print((client.request(AccountLines(account=wallet1.classic_address))).result["lines"]) -print((client.request(AccountLines(account=wallet2.classic_address))).result["lines"]) +print((client.request(AccountLines(account=wallet1.address))).result["lines"]) +print((client.request(AccountLines(account=wallet2.address))).result["lines"]) # Create a Payment to send 3840 FOO from wallet1 (issuer) to destination (wallet2) issue_quantity = "3840" payment_tx = Payment( - account=wallet1.classic_address, + account=wallet1.address, amount=IssuedCurrencyAmount( currency="FOO", value=issue_quantity, - issuer=wallet1.classic_address, + issuer=wallet1.address, ), - destination=wallet2.classic_address, + destination=wallet2.address, ) # Sign and autofill, then send transaction to the ledger @@ -55,8 +59,8 @@ # Issuer (wallet1) should have -3840 FOO and destination (wallet2) should have 3840 FOO print("Balances after wallet1 sends 3840 FOO to wallet2:") -print((client.request(AccountLines(account=wallet1.classic_address))).result["lines"]) -print((client.request(AccountLines(account=wallet2.classic_address))).result["lines"]) +print((client.request(AccountLines(account=wallet1.address))).result["lines"]) +print((client.request(AccountLines(account=wallet2.address))).result["lines"]) # Send money less than the amount specified on 2 conditions: # 1. Sender has less money than the amount specified in the payment Tx. @@ -68,18 +72,18 @@ # Create Payment to send 4000 (of 3840) FOO from wallet2 to wallet1 partial_payment_tx = Payment( - account=wallet2.classic_address, + account=wallet2.address, amount=IssuedCurrencyAmount( currency="FOO", value="4000", - issuer=wallet1.classic_address, + issuer=wallet1.address, ), - destination=wallet1.classic_address, + destination=wallet1.address, flags=[PaymentFlag.TF_PARTIAL_PAYMENT], send_max=IssuedCurrencyAmount( currency="FOO", value="1000000", - issuer=wallet1.classic_address, + issuer=wallet1.address, ), ) @@ -89,5 +93,5 @@ # Tried sending 4000 of 3840 FOO -> wallet1 and wallet2 should have 0 FOO print("Balances after Partial Payment, when wallet2 tried to send 4000 FOOs") -print((client.request(AccountLines(account=wallet1.classic_address))).result["lines"]) -print((client.request(AccountLines(account=wallet2.classic_address))).result["lines"]) +print((client.request(AccountLines(account=wallet1.address))).result["lines"]) +print((client.request(AccountLines(account=wallet2.address))).result["lines"]) diff --git a/snippets/paths.py b/snippets/paths.py index 487122273..b26581d6d 100644 --- a/snippets/paths.py +++ b/snippets/paths.py @@ -1,9 +1,6 @@ """Example of how to find the best path to trade with""" from xrpl.clients import JsonRpcClient -from xrpl.models.amounts import IssuedCurrencyAmount -from xrpl.models.currencies.xrp import XRP -from xrpl.models.requests import RipplePathFind -from xrpl.models.transactions import Payment +from xrpl.models import XRP, IssuedCurrencyAmount, Payment, RipplePathFind from xrpl.transaction import autofill_and_sign from xrpl.wallet import generate_faucet_wallet @@ -27,7 +24,7 @@ # Create a RipplePathFind request and have the client call it path_request = RipplePathFind( - source_account=wallet.classic_address, + source_account=wallet.address, source_currencies=[XRP()], destination_account=destination_account, destination_amount=destination_amount, @@ -41,10 +38,10 @@ # # Create a Payment to send money from wallet to destination_account using path payment_tx = Payment( - account=wallet.classic_address, + account=wallet.address, amount=destination_amount, destination=destination_account, paths=paths, ) -print("signed: ", autofill_and_sign(payment_tx, wallet, client)) +print("signed: ", autofill_and_sign(payment_tx, client, wallet)) diff --git a/snippets/send_escrow.py b/snippets/send_escrow.py index 8477c0c05..d502f749b 100644 --- a/snippets/send_escrow.py +++ b/snippets/send_escrow.py @@ -1,10 +1,10 @@ """Example of how we can set up an escrow""" from datetime import datetime +from time import sleep from xrpl.account import get_balance from xrpl.clients import JsonRpcClient -from xrpl.models.requests import AccountObjects -from xrpl.models.transactions import EscrowCreate, EscrowFinish +from xrpl.models import AccountObjects, EscrowCreate, EscrowFinish from xrpl.transaction.reliable_submission import submit_and_wait from xrpl.utils import datetime_to_ripple_time from xrpl.wallet import generate_faucet_wallet @@ -23,34 +23,37 @@ # Both balances should be zero since nothing has been sent yet print("Balances of wallets before Escrow tx was created:") -print(get_balance(wallet1.classic_address, client)) -print(get_balance(wallet2.classic_address, client)) +print(get_balance(wallet1.address, client)) +print(get_balance(wallet2.address, client)) -# Create a finish time (2 seconds from current time) -finish_after = datetime_to_ripple_time(datetime.now()) + 2 +# Create a finish time (8 seconds from last ledger close) +finish_after = datetime_to_ripple_time(datetime.now()) + 8 # Create an EscrowCreate transaction, then sign, autofill, and send it create_tx = EscrowCreate( - account=wallet1.classic_address, - destination=wallet2.classic_address, + account=wallet1.address, + destination=wallet2.address, amount="1000000", finish_after=finish_after, ) -create_escrow_response = submit_and_wait(create_tx, wallet1, client) +create_escrow_response = submit_and_wait(create_tx, client, wallet1) print(create_escrow_response) # Create an AccountObjects request and have the client call it to see if escrow exists -account_objects_request = AccountObjects(account=wallet1.classic_address) +account_objects_request = AccountObjects(account=wallet1.address) account_objects = (client.request(account_objects_request)).result["account_objects"] print("Escrow object exists in wallet1's account:") print(account_objects) +print("Waiting for the escrow finish time to pass...") +sleep(6) + # Create an EscrowFinish transaction, then sign, autofill, and send it finish_tx = EscrowFinish( - account=wallet1.classic_address, - owner=wallet1.classic_address, + account=wallet1.address, + owner=wallet1.address, offer_sequence=create_escrow_response.result["Sequence"], ) @@ -58,5 +61,5 @@ # If escrow went through successfully, 1000000 exchanged print("Balances of wallets after Escrow was sent:") -print(get_balance(wallet1.classic_address, client)) -print(get_balance(wallet2.classic_address, client)) +print(get_balance(wallet1.address, client)) +print(get_balance(wallet2.address, client)) diff --git a/snippets/set_regular_key.py b/snippets/set_regular_key.py index 520f031c5..39ad5f207 100644 --- a/snippets/set_regular_key.py +++ b/snippets/set_regular_key.py @@ -1,7 +1,7 @@ """Example of how we can setting a regular key""" from xrpl.account import get_balance from xrpl.clients import JsonRpcClient -from xrpl.models.transactions import Payment, SetRegularKey +from xrpl.models import Payment, SetRegularKey from xrpl.transaction import submit_and_wait from xrpl.wallet import generate_faucet_wallet @@ -20,15 +20,13 @@ # Both balances should be zero since nothing has been sent yet print("Balances before payment:") -print(get_balance(wallet1.classic_address, client)) -print(get_balance(wallet2.classic_address, client)) +print(get_balance(wallet1.address, client)) +print(get_balance(wallet2.address, client)) # Assign key pair (regular_key_wallet) to wallet1 using SetRegularKey transaction -tx = SetRegularKey( - account=wallet1.classic_address, regular_key=regular_key_wallet.classic_address -) +tx = SetRegularKey(account=wallet1.address, regular_key=regular_key_wallet.address) -set_regular_key_response = submit_and_wait(tx, wallet1, client) +set_regular_key_response = submit_and_wait(tx, client, wallet1) print("Response for successful SetRegularKey tx:") print(set_regular_key_response) @@ -36,8 +34,8 @@ # Since regular_key_wallet is linked to wallet1, # walet1 can send payment to wallet2 and have regular_key_wallet sign it payment = Payment( - account=wallet1.classic_address, - destination=wallet2.classic_address, + account=wallet1.address, + destination=wallet2.address, amount="1000", ) @@ -48,5 +46,5 @@ # Balance after sending 1000 from wallet1 to wallet2 print("Balances after payment:") -print(get_balance(wallet1.classic_address, client)) -print(get_balance(wallet2.classic_address, client)) +print(get_balance(wallet1.address, client)) +print(get_balance(wallet2.address, client)) diff --git a/snippets/reliable_transaction_submission.py b/snippets/submit_payment.py similarity index 77% rename from snippets/reliable_transaction_submission.py rename to snippets/submit_payment.py index f9d70c8c2..8dbbb4f25 100644 --- a/snippets/reliable_transaction_submission.py +++ b/snippets/submit_payment.py @@ -1,8 +1,7 @@ """Example of how to send a transaction and see its validation response""" from xrpl.account import get_balance from xrpl.clients import JsonRpcClient -from xrpl.models.requests import Tx -from xrpl.models.transactions import Payment +from xrpl.models import Payment, Tx from xrpl.transaction import submit_and_wait from xrpl.wallet import generate_faucet_wallet @@ -20,17 +19,18 @@ # Both balances should be zero since nothing has been sent yet print("Balances of wallets before Payment tx") -print(get_balance(wallet1.classic_address, client)) -print(get_balance(wallet2.classic_address, client)) +print(get_balance(wallet1.address, client)) +print(get_balance(wallet2.address, client)) # Create a Payment transaction payment_tx = Payment( - account=wallet1.classic_address, + account=wallet1.address, amount="1000", - destination=wallet2.classic_address, + destination=wallet2.address, ) -# Signs, autofills, and submits transaction and waits for response (validated or rejected) +# Signs, autofills, and submits transaction and waits for response +# (validated or rejected) payment_response = submit_and_wait(payment_tx, client, wallet1) print("Transaction was submitted") @@ -42,5 +42,5 @@ # Check balances after 1000 was sent from wallet1 to wallet2 print("Balances of wallets after Payment tx:") -print(get_balance(wallet1.classic_address, client)) -print(get_balance(wallet2.classic_address, client)) +print(get_balance(wallet1.address, client)) +print(get_balance(wallet2.address, client)) diff --git a/tests/integration/it_utils.py b/tests/integration/it_utils.py index 138aa95b4..2f2c38cc3 100644 --- a/tests/integration/it_utils.py +++ b/tests/integration/it_utils.py @@ -9,17 +9,14 @@ import xrpl # noqa: F401 - needed for sync tests from xrpl.asyncio.clients import AsyncJsonRpcClient, AsyncWebsocketClient from xrpl.asyncio.clients.async_client import AsyncClient -from xrpl.asyncio.transaction import ( - safe_sign_and_submit_transaction as sign_and_submit_async, -) +from xrpl.asyncio.transaction import sign_and_submit as sign_and_submit_async from xrpl.clients import Client, JsonRpcClient, WebsocketClient from xrpl.clients.sync_client import SyncClient +from xrpl.constants import CryptoAlgorithm from xrpl.models import GenericRequest, Payment, Request, Response, Transaction +from xrpl.transaction import sign_and_submit # noqa: F401 - needed for sync tests from xrpl.transaction import ( # noqa: F401 - needed for sync tests - safe_sign_and_submit_transaction, -) -from xrpl.transaction import ( # noqa: F401 - needed for sync tests - submit_transaction as submit_transaction_alias, + submit as submit_transaction_alias, ) from xrpl.wallet import Wallet @@ -55,7 +52,7 @@ MASTER_ACCOUNT = "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh" MASTER_SECRET = "snoPBrXtMeMyMHUVTgbuqAfg1SUTb" -MASTER_WALLET = Wallet(MASTER_SECRET, 0) +MASTER_WALLET = Wallet.from_seed(MASTER_SECRET, algorithm=CryptoAlgorithm.SECP256K1) FUNDING_AMOUNT = "2000000000" LEDGER_ACCEPT_REQUEST = GenericRequest(method="ledger_accept") @@ -100,10 +97,10 @@ def fund_wallet_sync(wallet: Wallet) -> None: client = JSON_RPC_CLIENT payment = Payment( account=MASTER_ACCOUNT, - destination=wallet.classic_address, + destination=wallet.address, amount=FUNDING_AMOUNT, ) - safe_sign_and_submit_transaction(payment, MASTER_WALLET, client, check_fee=True) + sign_and_submit(payment, client, MASTER_WALLET, check_fee=True) client.request(LEDGER_ACCEPT_REQUEST) @@ -112,10 +109,10 @@ async def fund_wallet( ) -> None: payment = Payment( account=MASTER_ACCOUNT, - destination=wallet.classic_address, + destination=wallet.address, amount=FUNDING_AMOUNT, ) - await sign_and_submit_async(payment, MASTER_WALLET, client, check_fee=True) + await sign_and_submit_async(payment, client, MASTER_WALLET, check_fee=True) await client.request(LEDGER_ACCEPT_REQUEST) @@ -127,9 +124,7 @@ def submit_transaction( check_fee: bool = True, ) -> Response: """Signs and submits a transaction to the XRPL.""" - return safe_sign_and_submit_transaction( - transaction, wallet, client, check_fee=check_fee - ) + return sign_and_submit(transaction, client, wallet, check_fee=check_fee) # just submits a transaction to the ledger, asynchronously @@ -139,7 +134,7 @@ async def submit_transaction_async( client: AsyncClient, check_fee: bool = True, ) -> Response: - return await sign_and_submit_async(transaction, wallet, client, check_fee=check_fee) + return await sign_and_submit_async(transaction, client, wallet, check_fee=check_fee) # submits a transaction to the ledger and closes a ledger, synchronously diff --git a/tests/integration/reqs/test_account_info.py b/tests/integration/reqs/test_account_info.py index aaa4804a6..28a87533a 100644 --- a/tests/integration/reqs/test_account_info.py +++ b/tests/integration/reqs/test_account_info.py @@ -9,7 +9,7 @@ class TestAccountInfo(IntegrationTestCase): async def test_basic_functionality(self, client): response = await client.request( AccountInfo( - account=WALLET.classic_address, + account=WALLET.address, ) ) self.assertTrue(response.is_successful()) diff --git a/tests/integration/reqs/test_account_objects.py b/tests/integration/reqs/test_account_objects.py index 099c992fc..4606c5ed5 100644 --- a/tests/integration/reqs/test_account_objects.py +++ b/tests/integration/reqs/test_account_objects.py @@ -9,7 +9,7 @@ class TestAccountObjects(IntegrationTestCase): async def test_basic_functionality(self, client): response = await client.request( AccountObjects( - account=WALLET.classic_address, + account=WALLET.address, ) ) self.assertTrue(response.is_successful()) diff --git a/tests/integration/reqs/test_account_tx.py b/tests/integration/reqs/test_account_tx.py index 4066de4fd..d8cacff93 100644 --- a/tests/integration/reqs/test_account_tx.py +++ b/tests/integration/reqs/test_account_tx.py @@ -9,7 +9,7 @@ class TestAccountTx(IntegrationTestCase): async def test_basic_functionality(self, client): response = await client.request( AccountTx( - account=WALLET.classic_address, + account=WALLET.address, ) ) self.assertTrue(response.is_successful()) diff --git a/tests/integration/reqs/test_book_offers.py b/tests/integration/reqs/test_book_offers.py index c5e69818d..bd97a8c26 100644 --- a/tests/integration/reqs/test_book_offers.py +++ b/tests/integration/reqs/test_book_offers.py @@ -10,7 +10,7 @@ class TestBookOffers(IntegrationTestCase): async def test_basic_functionality(self, client): response = await client.request( BookOffers( - taker=WALLET.classic_address, + taker=WALLET.address, taker_gets=XRP(), taker_pays=IssuedCurrency( currency="USD", diff --git a/tests/integration/reqs/test_no_ripple_check.py b/tests/integration/reqs/test_no_ripple_check.py index 808d322b6..7812ca708 100644 --- a/tests/integration/reqs/test_no_ripple_check.py +++ b/tests/integration/reqs/test_no_ripple_check.py @@ -9,7 +9,7 @@ class TestNoRippleCheck(IntegrationTestCase): async def test_basic_functionality(self, client): response = await client.request( NoRippleCheck( - account=WALLET.classic_address, + account=WALLET.address, role=NoRippleCheckRole.USER, ) ) diff --git a/tests/integration/reqs/test_ripple_path_find.py b/tests/integration/reqs/test_ripple_path_find.py index dc5831481..8bbd4f61c 100644 --- a/tests/integration/reqs/test_ripple_path_find.py +++ b/tests/integration/reqs/test_ripple_path_find.py @@ -9,8 +9,8 @@ class TestRipplePathFind(IntegrationTestCase): async def test_basic_functionality(self, client): response = await client.request( RipplePathFind( - source_account=WALLET.classic_address, - destination_account=DESTINATION.classic_address, + source_account=WALLET.address, + destination_account=DESTINATION.address, destination_amount="100", ), ) diff --git a/tests/integration/reqs/test_submit_multisigned.py b/tests/integration/reqs/test_submit_multisigned.py index cfdcd525f..e589e42c8 100644 --- a/tests/integration/reqs/test_submit_multisigned.py +++ b/tests/integration/reqs/test_submit_multisigned.py @@ -8,21 +8,21 @@ from xrpl.utils.str_conversions import str_to_hex from xrpl.wallet import Wallet -FIRST_SIGNER = Wallet("sEdTLQkHAWpdS7FDk7EvuS7Mz8aSMRh", 0) -SECOND_SIGNER = Wallet("sEd7DXaHkGQD8mz8xcRLDxfMLqCurif", 0) +FIRST_SIGNER = Wallet.from_seed("sEdTLQkHAWpdS7FDk7EvuS7Mz8aSMRh") +SECOND_SIGNER = Wallet.from_seed("sEd7DXaHkGQD8mz8xcRLDxfMLqCurif") SIGNER_ENTRIES = [ SignerEntry( - account=FIRST_SIGNER.classic_address, + account=FIRST_SIGNER.address, signer_weight=1, ), SignerEntry( - account=SECOND_SIGNER.classic_address, + account=SECOND_SIGNER.address, signer_weight=1, ), ] LIST_SET_TX = sign_and_reliable_submission( SignerListSet( - account=WALLET.classic_address, + account=WALLET.address, signer_quorum=2, signer_entries=SIGNER_ENTRIES, ), @@ -41,12 +41,12 @@ class TestSubmitMultisigned(IntegrationTestCase): ], ) async def test_basic_functionality(self, client): - tx = AccountSet(account=WALLET.classic_address, domain=EXAMPLE_DOMAIN) + tx = AccountSet(account=WALLET.address, domain=EXAMPLE_DOMAIN) autofilled_tx = await autofill(tx, client, len(SIGNER_ENTRIES)) - tx_1 = await sign(autofilled_tx, FIRST_SIGNER, multisign=True) - tx_2 = await sign(autofilled_tx, SECOND_SIGNER, multisign=True) + tx_1 = sign(autofilled_tx, FIRST_SIGNER, multisign=True) + tx_2 = sign(autofilled_tx, SECOND_SIGNER, multisign=True) multisigned_tx = multisign(autofilled_tx, [tx_1, tx_2]) diff --git a/tests/integration/reqs/test_submit_only.py b/tests/integration/reqs/test_submit_only.py index 250d65569..e82091f53 100644 --- a/tests/integration/reqs/test_submit_only.py +++ b/tests/integration/reqs/test_submit_only.py @@ -8,11 +8,11 @@ from xrpl.models.transactions import OfferCreate TX = OfferCreate( - account=WALLET.classic_address, + account=WALLET.address, taker_gets="13100000", taker_pays=IssuedCurrencyAmount( currency="USD", - issuer=WALLET.classic_address, + issuer=WALLET.address, value="10", ), ) @@ -21,7 +21,8 @@ class TestSubmitOnly(IntegrationTestCase): @test_async_and_sync(globals(), ["xrpl.transaction.autofill_and_sign"]) async def test_basic_functionality(self, client): - transaction = await autofill_and_sign(TX, WALLET, client) + transaction = await autofill_and_sign(TX, client, WALLET) + tx_json = transaction.to_xrpl() tx_blob = encode(tx_json) response = await client.request( diff --git a/tests/integration/reqs/test_subscribe.py b/tests/integration/reqs/test_subscribe.py index a52d3948b..e1dd22e94 100644 --- a/tests/integration/reqs/test_subscribe.py +++ b/tests/integration/reqs/test_subscribe.py @@ -76,9 +76,9 @@ async def test_transactions_subscription(self, client): await client.send(Subscribe(streams=[StreamParameter.TRANSACTIONS])) payment_transaction = Payment( - account=WALLET.classic_address, + account=WALLET.address, amount="100", - destination=DESTINATION.classic_address, + destination=DESTINATION.address, ) count = 0 @@ -105,9 +105,9 @@ async def test_transactions_proposed_subscription(self, client): await client.send(Subscribe(streams=[StreamParameter.TRANSACTIONS_PROPOSED])) payment_transaction = Payment( - account=WALLET.classic_address, + account=WALLET.address, amount="100", - destination=DESTINATION.classic_address, + destination=DESTINATION.address, ) count = 0 diff --git a/tests/integration/reusable_values.py b/tests/integration/reusable_values.py index 315b620bf..33979aeda 100644 --- a/tests/integration/reusable_values.py +++ b/tests/integration/reusable_values.py @@ -1,8 +1,7 @@ import asyncio from tests.integration.it_utils import fund_wallet, sign_and_reliable_submission_async -from xrpl.models.amounts import IssuedCurrencyAmount -from xrpl.models.transactions import OfferCreate, PaymentChannelCreate +from xrpl.models import IssuedCurrencyAmount, OfferCreate, PaymentChannelCreate from xrpl.wallet import Wallet @@ -17,11 +16,11 @@ async def _set_up_reusable_values(): OFFER = await sign_and_reliable_submission_async( OfferCreate( - account=WALLET.classic_address, + account=WALLET.address, taker_gets="13100000", taker_pays=IssuedCurrencyAmount( currency="USD", - issuer=WALLET.classic_address, + issuer=WALLET.address, value="10", ), ), @@ -30,9 +29,9 @@ async def _set_up_reusable_values(): PAYMENT_CHANNEL = await sign_and_reliable_submission_async( PaymentChannelCreate( - account=WALLET.classic_address, + account=WALLET.address, amount="1", - destination=DESTINATION.classic_address, + destination=DESTINATION.address, settle_delay=86400, public_key=WALLET.public_key, ), diff --git a/tests/integration/sugar/test_account.py b/tests/integration/sugar/test_account.py index bed129100..7cd8b4287 100644 --- a/tests/integration/sugar/test_account.py +++ b/tests/integration/sugar/test_account.py @@ -7,6 +7,7 @@ ) from tests.integration.reusable_values import DESTINATION, WALLET from xrpl.asyncio.account import does_account_exist, get_balance, get_latest_transaction +from xrpl.asyncio.clients.exceptions import XRPLRequestFailureException from xrpl.core.addresscodec import classic_address_to_xaddress from xrpl.models.transactions import Payment from xrpl.wallet import Wallet @@ -19,22 +20,28 @@ class TestAccount(IntegrationTestCase): @test_async_and_sync(globals(), ["xrpl.account.does_account_exist"]) async def test_does_account_exist_true(self, client): - self.assertTrue(await does_account_exist(WALLET.classic_address, client)) + self.assertTrue(await does_account_exist(WALLET.address, client)) @test_async_and_sync(globals(), ["xrpl.account.does_account_exist"]) async def test_does_account_exist_false(self, client): - address = "rG1QQv2nh2gr7RCZ1P8YYcBUcCCN633jCn" + address = Wallet.create().classic_address self.assertFalse(await does_account_exist(address, client)) + @test_async_and_sync(globals(), ["xrpl.account.does_account_exist"]) + async def test_does_account_exist_throws_for_invalid_account(self, client): + address = "a" + with self.assertRaises(XRPLRequestFailureException): + await does_account_exist(address, client) + @test_async_and_sync(globals(), ["xrpl.account.does_account_exist"]) async def test_does_account_exist_xaddress(self, client): - xaddress = classic_address_to_xaddress(WALLET.classic_address, None, True) + xaddress = classic_address_to_xaddress(WALLET.address, None, True) self.assertTrue(await does_account_exist(xaddress, client)) @test_async_and_sync(globals(), ["xrpl.account.get_balance"]) async def test_get_balance(self, client): self.assertEqual( - await get_balance(NEW_WALLET.classic_address, client), + await get_balance(NEW_WALLET.address, client), int(FUNDING_AMOUNT), ) @@ -43,15 +50,15 @@ async def test_get_latest_transaction(self, client): # NOTE: this test may take a long time to run amount = "21000000" payment = Payment( - account=WALLET.classic_address, - destination=DESTINATION.classic_address, + account=WALLET.address, + destination=DESTINATION.address, amount=amount, ) await sign_and_reliable_submission_async(payment, WALLET, client) - response = await get_latest_transaction(WALLET.classic_address, client) + response = await get_latest_transaction(WALLET.address, client) self.assertEqual(len(response.result["transactions"]), 1) transaction = response.result["transactions"][0]["tx"] self.assertEqual(transaction["TransactionType"], "Payment") self.assertEqual(transaction["Amount"], amount) - self.assertEqual(transaction["Account"], WALLET.classic_address) + self.assertEqual(transaction["Account"], WALLET.address) diff --git a/tests/integration/sugar/test_network_id.py b/tests/integration/sugar/test_network_id.py new file mode 100644 index 000000000..26e84c65d --- /dev/null +++ b/tests/integration/sugar/test_network_id.py @@ -0,0 +1,69 @@ +from unittest import TestCase + +from xrpl.asyncio.transaction.main import _RESTRICTED_NETWORKS +from xrpl.clients import JsonRpcClient, WebsocketClient +from xrpl.models.transactions import AccountSet +from xrpl.transaction import autofill +from xrpl.wallet.wallet_generation import generate_faucet_wallet + +_FEE = "0.00001" + + +# TODO: move to test_transaction and use the standard integration test setup +class TestNetworkID(TestCase): + # Autofill should override tx networkID for network with ID > 1024 + # and build_version from 1.11.0 or later. + def test_networkid_override(self): + client = JsonRpcClient("https://sidechain-net1.devnet.rippletest.net:51234") + wallet = generate_faucet_wallet(client, debug=True) + # Override client build_version since 1.11.0 is not released yet. + client.build_version = "1.11.0" + tx = AccountSet( + account=wallet.classic_address, + fee=_FEE, + domain="www.example.com", + ) + tx_autofilled = autofill(tx, client) + self.assertGreaterEqual(client.network_id, _RESTRICTED_NETWORKS) + self.assertEqual(tx_autofilled.network_id, client.network_id) + + # Autofill should ignore tx network_id for build version earlier than 1.11.0. + def test_networkid_ignore_early_version(self): + client = JsonRpcClient("https://sidechain-net1.devnet.rippletest.net:51234") + wallet = generate_faucet_wallet(client, debug=True) + # Override client build_version since 1.11.0 is not released yet. + client.build_version = "1.10.0" + tx = AccountSet( + account=wallet.classic_address, + fee=_FEE, + domain="www.example.com", + ) + tx_autofilled = autofill(tx, client) + self.assertEqual(tx_autofilled.network_id, None) + + # Autofill should ignore tx network_id for networks with ID <= 1024. + def test_networkid_ignore_restricted_networks(self): + client = JsonRpcClient("https://s.altnet.rippletest.net:51234") + wallet = generate_faucet_wallet(client, debug=True) + # Override client build_version since 1.11.0 is not released yet. + client.build_version = "1.11.0" + tx = AccountSet( + account=wallet.classic_address, + fee=_FEE, + domain="www.example.com", + ) + tx_autofilled = autofill(tx, client) + self.assertLessEqual(client.network_id, _RESTRICTED_NETWORKS) + self.assertEqual(tx_autofilled.network_id, None) + + # Autofill should override tx networkID for hooks-testnet. + def test_networkid_override_hooks_testnet(self): + with WebsocketClient("wss://hooks-testnet-v3.xrpl-labs.com") as client: + wallet = generate_faucet_wallet(client, debug=True) + tx = AccountSet( + account=wallet.classic_address, + fee=_FEE, + domain="www.example.com", + ) + tx_autofilled = autofill(tx, client) + self.assertEqual(tx_autofilled.network_id, client.network_id) diff --git a/tests/integration/sugar/test_transaction.py b/tests/integration/sugar/test_transaction.py index 4d82cf3ad..adce2c99d 100644 --- a/tests/integration/sugar/test_transaction.py +++ b/tests/integration/sugar/test_transaction.py @@ -11,13 +11,11 @@ XRPLReliableSubmissionException, autofill, autofill_and_sign, - send_reliable_submission, sign, - submit_and_wait, -) -from xrpl.asyncio.transaction import ( - submit_transaction as submit_transaction_alias_async, ) +from xrpl.asyncio.transaction import submit as submit_transaction_alias_async +from xrpl.asyncio.transaction import submit_and_wait +from xrpl.asyncio.transaction.main import sign_and_submit from xrpl.clients import XRPLRequestFailureException from xrpl.core.addresscodec import classic_address_to_xaddress from xrpl.core.binarycodec.main import encode @@ -26,8 +24,8 @@ from xrpl.models.transactions import AccountDelete, AccountSet, EscrowFinish, Payment from xrpl.utils import xrp_to_drops -ACCOUNT = WALLET.classic_address -DESTINATION = DESTINATION_WALLET.classic_address +ACCOUNT = WALLET.address +DESTINATION = DESTINATION_WALLET.address CLEAR_FLAG = 3 DOMAIN = "6578616D706C652E636F6D".lower() @@ -53,7 +51,7 @@ class TestTransaction(IntegrationTestCase): async def test_none_as_destination_tag(self, client): # GIVEN a new transaction (payment) payment_transaction = Payment( - account=WALLET.classic_address, + account=WALLET.address, amount="100", destination=classic_address_to_xaddress(DESTINATION, None, False), ) @@ -117,7 +115,7 @@ async def test_payment_high_fee_authorized(self, client): # GIVEN a new Payment transaction response = await sign_and_reliable_submission_async( Payment( - account=WALLET.classic_address, + account=WALLET.address, amount="1", # WITH the fee higher than 2 XRP fee=FEE, @@ -135,19 +133,19 @@ async def test_payment_high_fee_authorized(self, client): globals(), [ "xrpl.transaction.autofill_and_sign", - "xrpl.transaction.submit_transaction", + "xrpl.transaction.submit", ], ) async def test_payment_high_fee_authorized_with_submit_alias(self, client): signed_and_autofilled = await autofill_and_sign( Payment( - account=WALLET.classic_address, + account=WALLET.address, amount="1", fee=FEE, destination=DESTINATION, ), - WALLET, client, + WALLET, check_fee=False, ) @@ -206,7 +204,7 @@ async def test_calculate_escrow_finish_fee(self, client): async def test_calculate_payment_fee(self, client): # GIVEN a new Payment transaction payment = Payment( - account=WALLET.classic_address, + account=WALLET.address, amount="100", destination=DESTINATION, ) @@ -219,128 +217,6 @@ async def test_calculate_payment_fee(self, client): self.assertEqual(payment_autofilled.fee, expected_fee) -class TestReliableSubmission(IntegrationTestCase): - @test_async_and_sync( - globals(), - [ - "xrpl.transaction.autofill_and_sign", - "xrpl.transaction.send_reliable_submission", - "xrpl.account.get_next_valid_seq_number", - "xrpl.ledger.get_fee", - ], - ) - async def test_reliable_submission_simple(self, client): - account_set = AccountSet( - account=ACCOUNT, - set_flag=SET_FLAG, - ) - signed_account_set = await autofill_and_sign(account_set, WALLET, client) - await accept_ledger_async() - response = await send_reliable_submission(signed_account_set, client) - self.assertTrue(response.result["validated"]) - self.assertEqual(response.result["meta"]["TransactionResult"], "tesSUCCESS") - self.assertTrue(response.is_successful()) - self.assertEqual(response.result["Fee"], await get_fee(client)) - - @test_async_and_sync( - globals(), - [ - "xrpl.transaction.autofill_and_sign", - "xrpl.transaction.send_reliable_submission", - "xrpl.account.get_next_valid_seq_number", - "xrpl.ledger.get_fee", - ], - ) - async def test_reliable_submission_payment(self, client): - payment_dict = { - "account": ACCOUNT, - "amount": "10", - "destination": DESTINATION, - } - payment_transaction = Payment.from_dict(payment_dict) - signed_payment_transaction = await autofill_and_sign( - payment_transaction, WALLET, client - ) - await accept_ledger_async() - response = await send_reliable_submission(signed_payment_transaction, client) - self.assertTrue(response.result["validated"]) - self.assertEqual(response.result["meta"]["TransactionResult"], "tesSUCCESS") - self.assertTrue(response.is_successful()) - self.assertEqual(response.result["Fee"], await get_fee(client)) - - @test_async_and_sync( - globals(), - [ - "xrpl.transaction.autofill_and_sign", - "xrpl.transaction.send_reliable_submission", - "xrpl.account.get_next_valid_seq_number", - "xrpl.ledger.get_latest_validated_ledger_sequence", - ], - ) - async def test_reliable_submission_last_ledger_expiration(self, client): - payment_dict = { - "account": ACCOUNT, - "last_ledger_sequence": await get_latest_validated_ledger_sequence(client), - "fee": "10", - "amount": "100", - "destination": DESTINATION, - } - payment_transaction = Payment.from_dict(payment_dict) - signed_payment_transaction = await autofill_and_sign( - payment_transaction, WALLET, client - ) - - await accept_ledger_async() - - with self.assertRaises(XRPLReliableSubmissionException): - await send_reliable_submission(signed_payment_transaction, client) - - @test_async_and_sync( - globals(), - [ - "xrpl.transaction.sign", - "xrpl.transaction.send_reliable_submission", - "xrpl.account.get_next_valid_seq_number", - "xrpl.ledger.get_fee", - "xrpl.ledger.get_latest_validated_ledger_sequence", - ], - ) - async def test_reliable_submission_bad_transaction(self, client): - payment_dict = { - "account": ACCOUNT, - "last_ledger_sequence": await get_latest_validated_ledger_sequence(client) - + 20, - "fee": "10", - "amount": "100", - "destination": DESTINATION, - } - payment_transaction = Payment.from_dict(payment_dict) - signed_payment_transaction = await sign(payment_transaction, WALLET) - with self.assertRaises(XRPLRequestFailureException): - await send_reliable_submission(signed_payment_transaction, client) - - @test_async_and_sync( - globals(), - [ - "xrpl.transaction.sign", - "xrpl.transaction.send_reliable_submission", - "xrpl.account.get_next_valid_seq_number", - "xrpl.ledger.get_fee", - ], - ) - async def test_reliable_submission_no_last_ledger_sequence(self, client): - payment_dict = { - "account": ACCOUNT, - "fee": "10", - "amount": "100", - "destination": DESTINATION, - } - payment_transaction = Payment.from_dict(payment_dict) - signed_payment_transaction = await sign(payment_transaction, WALLET) - with self.assertRaises(XRPLReliableSubmissionException): - await send_reliable_submission(signed_payment_transaction, client) - - class TestSubmitAndWait(IntegrationTestCase): @test_async_and_sync( globals(), @@ -360,7 +236,6 @@ async def test_submit_and_wait_simple(self, client): self.assertEqual(response.result["meta"]["TransactionResult"], "tesSUCCESS") self.assertTrue(response.is_successful()) self.assertEqual(response.result["Fee"], await get_fee(client)) - WALLET.sequence += 1 @test_async_and_sync( globals(), @@ -381,7 +256,6 @@ async def test_submit_and_wait_payment(self, client): self.assertEqual(response.result["meta"]["TransactionResult"], "tesSUCCESS") self.assertTrue(response.is_successful()) self.assertEqual(response.result["Fee"], await get_fee(client)) - WALLET.sequence += 1 @test_async_and_sync( globals(), @@ -398,7 +272,7 @@ async def test_submit_and_wait_signed(self, client): destination=DESTINATION, ) payment_transaction_signed = await autofill_and_sign( - payment_transaction, WALLET, client + payment_transaction, client, WALLET ) await accept_ledger_async(delay=1) response = await submit_and_wait(payment_transaction_signed, client) @@ -406,7 +280,6 @@ async def test_submit_and_wait_signed(self, client): self.assertEqual(response.result["meta"]["TransactionResult"], "tesSUCCESS") self.assertTrue(response.is_successful()) self.assertEqual(response.result["Fee"], await get_fee(client)) - WALLET.sequence += 1 @test_async_and_sync( globals(), @@ -423,7 +296,7 @@ async def test_submit_and_wait_blob(self, client): destination=DESTINATION, ) payment_transaction_signed = await autofill_and_sign( - payment_transaction, WALLET, client + payment_transaction, client, WALLET ) await accept_ledger_async(delay=1) payment_transaction_signed_blob = encode(payment_transaction_signed.to_xrpl()) @@ -432,7 +305,6 @@ async def test_submit_and_wait_blob(self, client): self.assertEqual(response.result["meta"]["TransactionResult"], "tesSUCCESS") self.assertTrue(response.is_successful()) self.assertEqual(response.result["Fee"], await get_fee(client)) - WALLET.sequence += 1 @test_async_and_sync( globals(), @@ -467,3 +339,62 @@ async def test_submit_and_wait_tec_error(self, client): await accept_ledger_async(delay=1) with self.assertRaises(XRPLReliableSubmissionException): await submit_and_wait(payment_transaction, client, WALLET) + + @test_async_and_sync( + globals(), + [ + "xrpl.transaction.sign", + "xrpl.transaction.submit_and_wait", + "xrpl.ledger.get_latest_validated_ledger_sequence", + ], + ) + async def test_submit_and_wait_bad_transaction(self, client): + payment_dict = { + "account": ACCOUNT, + "fee": "10", + "last_ledger_sequence": await get_latest_validated_ledger_sequence(client) + + 20, + "amount": "100", + "destination": DESTINATION, + } + payment_transaction = Payment.from_dict(payment_dict) + signed_payment_transaction = sign(payment_transaction, WALLET) + with self.assertRaises(XRPLRequestFailureException): + await submit_and_wait(signed_payment_transaction, client) + + @test_async_and_sync( + globals(), + [ + "xrpl.transaction.sign", + "xrpl.transaction.submit_and_wait", + "xrpl.account.get_next_valid_seq_number", + ], + ) + async def test_reliable_submission_no_last_ledger_sequence(self, client): + payment_dict = { + "account": ACCOUNT, + "fee": "10", + "amount": "100", + "destination": DESTINATION, + } + payment_transaction = Payment.from_dict(payment_dict) + signed_payment_transaction = sign(payment_transaction, WALLET) + with self.assertRaises(XRPLReliableSubmissionException): + await submit_and_wait(signed_payment_transaction, client) + + @test_async_and_sync( + globals(), + [ + "xrpl.transaction.sign_and_submit", + ], + ) + async def test_sign_and_submit(self, client): + payment_dict = { + "account": ACCOUNT, + "fee": "10", + "amount": "100", + "destination": DESTINATION, + } + payment_transaction = Payment.from_dict(payment_dict) + response = await sign_and_submit(payment_transaction, client, WALLET) + self.assertTrue(response.is_successful()) diff --git a/tests/integration/sugar/test_wallet.py b/tests/integration/sugar/test_wallet.py index 0cfcb308b..5ba4ef519 100644 --- a/tests/integration/sugar/test_wallet.py +++ b/tests/integration/sugar/test_wallet.py @@ -7,6 +7,7 @@ from xrpl.asyncio.clients import AsyncJsonRpcClient, AsyncWebsocketClient from xrpl.asyncio.wallet import generate_faucet_wallet from xrpl.clients import JsonRpcClient, WebsocketClient +from xrpl.core.addresscodec.main import classic_address_to_xaddress from xrpl.models.requests import AccountInfo from xrpl.models.transactions import Payment from xrpl.wallet import generate_faucet_wallet as sync_generate_faucet_wallet @@ -15,40 +16,52 @@ time_of_last_hooks_faucet_call = 0 -def sync_generate_faucet_wallet_and_fund_again(self, client, faucet_host=None): - wallet = sync_generate_faucet_wallet(client, faucet_host=faucet_host) +def sync_generate_faucet_wallet_and_fund_again( + self, client, faucet_host=None, usage_context="integration_test" +): + wallet = sync_generate_faucet_wallet( + client, faucet_host=faucet_host, usage_context=usage_context + ) result = client.request( AccountInfo( - account=wallet.classic_address, + account=wallet.address, ), ) balance = int(result.result["account_data"]["Balance"]) self.assertTrue(balance > 0) - new_wallet = sync_generate_faucet_wallet(client, wallet, faucet_host=faucet_host) + new_wallet = sync_generate_faucet_wallet( + client, wallet, faucet_host=faucet_host, usage_context="integration_test" + ) new_result = client.request( AccountInfo( - account=new_wallet.classic_address, + account=new_wallet.address, ), ) new_balance = int(new_result.result["account_data"]["Balance"]) self.assertTrue(new_balance > balance) -async def generate_faucet_wallet_and_fund_again(self, client, faucet_host=None): - wallet = await generate_faucet_wallet(client, faucet_host=faucet_host) +async def generate_faucet_wallet_and_fund_again( + self, client, faucet_host=None, usage_context="integration_test" +): + wallet = await generate_faucet_wallet( + client, faucet_host=faucet_host, usage_context=usage_context + ) result = await client.request( AccountInfo( - account=wallet.classic_address, + account=wallet.address, ), ) balance = int(result.result["account_data"]["Balance"]) self.assertTrue(balance > 0) - new_wallet = await generate_faucet_wallet(client, wallet, faucet_host=faucet_host) + new_wallet = await generate_faucet_wallet( + client, wallet, faucet_host=faucet_host, usage_context=usage_context + ) new_result = await client.request( AccountInfo( - account=new_wallet.classic_address, + account=new_wallet.address, ), ) new_balance = int(new_result.result["account_data"]["Balance"]) @@ -89,10 +102,10 @@ async def _parallel_test_generate_faucet_wallet_rel_sub(self): # TODO: refactor so this actually waits for validation response = await submit_transaction_async( Payment( - account=wallet.classic_address, + account=wallet.address, fee="10", amount="1", - destination=destination.classic_address, + destination=destination.address, ), wallet, client=client, @@ -106,13 +119,19 @@ async def _parallel_test_generate_faucet_wallet_custom_host_async_websockets(sel "wss://s.devnet.rippletest.net:51233" ) as client: await generate_faucet_wallet_and_fund_again( - self, client, "faucet.devnet.rippletest.net" + self, + client, + "faucet.devnet.rippletest.net", + usage_context="integration_test", ) async def _parallel_test_generate_faucet_wallet_custom_host_async_json_rpc(self): client = AsyncJsonRpcClient("https://s.devnet.rippletest.net:51234") await generate_faucet_wallet_and_fund_again( - self, client, "faucet.devnet.rippletest.net" + self, + client, + "faucet.devnet.rippletest.net", + usage_context="integration_test", ) def _parallel_test_generate_faucet_wallet_custom_host_sync_websockets(self): @@ -167,12 +186,14 @@ async def _parallel_test_generate_faucet_wallet_hooks_v3_testnet_async_websocket if time_since_last_hooks_call < 10: time.sleep(11 - time_since_last_hooks_call) - wallet = await generate_faucet_wallet(client) + wallet = await generate_faucet_wallet( + client, usage_context="integration_test" + ) time_of_last_hooks_faucet_call = time.time() result = await client.request( AccountInfo( - account=wallet.classic_address, + account=wallet.address, ), ) balance = int(result.result["account_data"]["Balance"]) @@ -194,7 +215,7 @@ async def test_fund_given_wallet_hooks_v3_testnet_async_websockets(self): await generate_faucet_wallet(client, wallet) result = await client.request( AccountInfo( - account=wallet.classic_address, + account=wallet.address, ), ) balance = int(result.result["account_data"]["Balance"]) @@ -205,11 +226,18 @@ async def test_fund_given_wallet_hooks_v3_testnet_async_websockets(self): time.sleep(11 - time_since_last_hooks_call) time_of_last_hooks_faucet_call = time.time() - new_wallet = await generate_faucet_wallet(client, wallet) + new_wallet = await generate_faucet_wallet( + client, wallet, usage_context="integration_test" + ) new_result = await client.request( AccountInfo( - account=new_wallet.classic_address, + account=new_wallet.address, ), ) new_balance = int(new_result.result["account_data"]["Balance"]) self.assertTrue(new_balance > balance) + + def test_wallet_get_xaddress(self): + wallet = Wallet.create() + expected = classic_address_to_xaddress(wallet.address, None, False) + self.assertEqual(wallet.get_xaddress(), expected) diff --git a/tests/integration/transactions/test_account_delete.py b/tests/integration/transactions/test_account_delete.py index 86488ab75..63dff7bcf 100644 --- a/tests/integration/transactions/test_account_delete.py +++ b/tests/integration/transactions/test_account_delete.py @@ -10,7 +10,7 @@ # We can re-use the shared wallet bc this test should fail to actually delete # the associated account. -ACCOUNT = WALLET.classic_address +ACCOUNT = WALLET.address # AccountDelete transactions have a special fee. # See https://xrpl.org/accountdelete.html#special-transaction-cost. @@ -24,7 +24,7 @@ async def test_all_fields(self, client): account_delete = AccountDelete( account=ACCOUNT, fee=FEE, - destination=DESTINATION.classic_address, + destination=DESTINATION.address, destination_tag=DESTINATION_TAG, ) response = await sign_and_reliable_submission_async( diff --git a/tests/integration/transactions/test_account_set.py b/tests/integration/transactions/test_account_set.py index f77006b27..2d1b67cd8 100644 --- a/tests/integration/transactions/test_account_set.py +++ b/tests/integration/transactions/test_account_set.py @@ -7,7 +7,7 @@ from xrpl.models.response import ResponseStatus from xrpl.models.transactions import AccountSet -ACCOUNT = WALLET.classic_address +ACCOUNT = WALLET.address CLEAR_FLAG = 3 DOMAIN = "6578616D706C652E636F6D".lower() diff --git a/tests/integration/transactions/test_check_cancel.py b/tests/integration/transactions/test_check_cancel.py index 879549cc3..c0f8c131a 100644 --- a/tests/integration/transactions/test_check_cancel.py +++ b/tests/integration/transactions/test_check_cancel.py @@ -7,7 +7,7 @@ from xrpl.models.response import ResponseStatus from xrpl.models.transactions import CheckCancel -ACCOUNT = WALLET.classic_address +ACCOUNT = WALLET.address CHECK_ID = "49647F0D748DC3FE26BDACBC57F251AADEFFF391403EC9BF87C97F67E9977FB0" diff --git a/tests/integration/transactions/test_check_cash.py b/tests/integration/transactions/test_check_cash.py index bd6b9c1ed..0d1c03b96 100644 --- a/tests/integration/transactions/test_check_cash.py +++ b/tests/integration/transactions/test_check_cash.py @@ -7,7 +7,7 @@ from xrpl.models.response import ResponseStatus from xrpl.models.transactions import CheckCash -ACCOUNT = WALLET.classic_address +ACCOUNT = WALLET.address CHECK_ID = "838766BA2B995C00744175F69A1B11E32C3DBC40E64801A4056FCBD657F57334" AMOUNT = "100000000" DELIVER_MIN = "100000000" diff --git a/tests/integration/transactions/test_check_create.py b/tests/integration/transactions/test_check_create.py index 83485bcc1..9ac11284f 100644 --- a/tests/integration/transactions/test_check_create.py +++ b/tests/integration/transactions/test_check_create.py @@ -7,7 +7,7 @@ from xrpl.models.response import ResponseStatus from xrpl.models.transactions import CheckCreate -ACCOUNT = WALLET.classic_address +ACCOUNT = WALLET.address DESTINATION_TAG = 1 SENDMAX = "100000000" EXPIRATION = 970113521 @@ -19,7 +19,7 @@ class TestCheckCreate(IntegrationTestCase): async def test_all_fields(self, client): check_create = CheckCreate( account=ACCOUNT, - destination=DESTINATION.classic_address, + destination=DESTINATION.address, destination_tag=DESTINATION_TAG, send_max=SENDMAX, expiration=EXPIRATION, diff --git a/tests/integration/transactions/test_deposit_preauth.py b/tests/integration/transactions/test_deposit_preauth.py index 83fbb1025..3f6aa3118 100644 --- a/tests/integration/transactions/test_deposit_preauth.py +++ b/tests/integration/transactions/test_deposit_preauth.py @@ -7,7 +7,7 @@ from xrpl.models.response import ResponseStatus from xrpl.models.transactions import DepositPreauth -ACCOUNT = WALLET.classic_address +ACCOUNT = WALLET.address ADDRESS = "rEhxGqkqPPSxQ3P25J66ft5TwpzV14k2de" diff --git a/tests/integration/transactions/test_escrow_cancel.py b/tests/integration/transactions/test_escrow_cancel.py index e37ae0f7f..42cae81d6 100644 --- a/tests/integration/transactions/test_escrow_cancel.py +++ b/tests/integration/transactions/test_escrow_cancel.py @@ -7,7 +7,7 @@ from xrpl.models.response import ResponseStatus from xrpl.models.transactions import EscrowCancel -ACCOUNT = WALLET.classic_address +ACCOUNT = WALLET.address OWNER = "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn" OFFER_SEQUENCE = 7 diff --git a/tests/integration/transactions/test_escrow_create.py b/tests/integration/transactions/test_escrow_create.py index 8dbe1e2f2..da92f5bd6 100644 --- a/tests/integration/transactions/test_escrow_create.py +++ b/tests/integration/transactions/test_escrow_create.py @@ -7,7 +7,7 @@ from xrpl.models.response import ResponseStatus from xrpl.models.transactions import EscrowCreate -ACCOUNT = WALLET.classic_address +ACCOUNT = WALLET.address AMOUNT = "10000" DESTINATION = "rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW" diff --git a/tests/integration/transactions/test_escrow_finish.py b/tests/integration/transactions/test_escrow_finish.py index cb6f5ffeb..a415cb3fa 100644 --- a/tests/integration/transactions/test_escrow_finish.py +++ b/tests/integration/transactions/test_escrow_finish.py @@ -11,7 +11,7 @@ # See note here: https://xrpl.org/escrowfinish.html FEE = "600000000" -ACCOUNT = WALLET.classic_address +ACCOUNT = WALLET.address OWNER = "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn" OFFER_SEQUENCE = 7 CONDITION = ( diff --git a/tests/integration/transactions/test_offer_cancel.py b/tests/integration/transactions/test_offer_cancel.py index e1ac21cfa..1b4f017c8 100644 --- a/tests/integration/transactions/test_offer_cancel.py +++ b/tests/integration/transactions/test_offer_cancel.py @@ -12,7 +12,7 @@ class TestOfferCancel(IntegrationTestCase): async def test_all_fields(self, client): response = await sign_and_reliable_submission_async( OfferCancel( - account=WALLET.classic_address, + account=WALLET.address, offer_sequence=OFFER.result["tx_json"]["Sequence"], ), WALLET, diff --git a/tests/integration/transactions/test_offer_create.py b/tests/integration/transactions/test_offer_create.py index 55fc798fd..56eb24802 100644 --- a/tests/integration/transactions/test_offer_create.py +++ b/tests/integration/transactions/test_offer_create.py @@ -13,11 +13,11 @@ class TestOfferCreate(IntegrationTestCase): async def test_basic_functionality(self, client): offer = await sign_and_reliable_submission_async( OfferCreate( - account=WALLET.classic_address, + account=WALLET.address, taker_gets="13100000", taker_pays=IssuedCurrencyAmount( currency="USD", - issuer=WALLET.classic_address, + issuer=WALLET.address, value="10", ), ), diff --git a/tests/integration/transactions/test_payment.py b/tests/integration/transactions/test_payment.py index a74f70e5a..7e308debb 100644 --- a/tests/integration/transactions/test_payment.py +++ b/tests/integration/transactions/test_payment.py @@ -12,9 +12,9 @@ class TestPayment(IntegrationTestCase): async def test_basic_functionality(self, client): response = await sign_and_reliable_submission_async( Payment( - account=WALLET.classic_address, + account=WALLET.address, amount="1", - destination=DESTINATION.classic_address, + destination=DESTINATION.address, ), WALLET, client, diff --git a/tests/integration/transactions/test_payment_channel_claim.py b/tests/integration/transactions/test_payment_channel_claim.py index 87efc6a18..6a76e829a 100644 --- a/tests/integration/transactions/test_payment_channel_claim.py +++ b/tests/integration/transactions/test_payment_channel_claim.py @@ -12,7 +12,7 @@ class TestPaymentChannelClaim(IntegrationTestCase): async def test_receiver_claim(self, client): response = await sign_and_reliable_submission_async( PaymentChannelClaim( - account=WALLET.classic_address, + account=WALLET.address, channel=PAYMENT_CHANNEL.result["tx_json"]["hash"], ), WALLET, diff --git a/tests/integration/transactions/test_payment_channel_create.py b/tests/integration/transactions/test_payment_channel_create.py index 68a757833..954f86d7f 100644 --- a/tests/integration/transactions/test_payment_channel_create.py +++ b/tests/integration/transactions/test_payment_channel_create.py @@ -12,9 +12,9 @@ class TestPaymentChannelCreate(IntegrationTestCase): async def test_basic_functionality(self, client): payment_channel = await sign_and_reliable_submission_async( PaymentChannelCreate( - account=WALLET.classic_address, + account=WALLET.address, amount="1", - destination=DESTINATION.classic_address, + destination=DESTINATION.address, settle_delay=86400, public_key=WALLET.public_key, ), diff --git a/tests/integration/transactions/test_payment_channel_fund.py b/tests/integration/transactions/test_payment_channel_fund.py index e22cd366b..b87f43fbb 100644 --- a/tests/integration/transactions/test_payment_channel_fund.py +++ b/tests/integration/transactions/test_payment_channel_fund.py @@ -12,7 +12,7 @@ class TestPaymentChannelFund(IntegrationTestCase): async def test_basic_functionality(self, client): response = await sign_and_reliable_submission_async( PaymentChannelFund( - account=WALLET.classic_address, + account=WALLET.address, channel=PAYMENT_CHANNEL.result["tx_json"]["hash"], amount="1", ), diff --git a/tests/integration/transactions/test_set_regular_key.py b/tests/integration/transactions/test_set_regular_key.py index beb212609..492ba234f 100644 --- a/tests/integration/transactions/test_set_regular_key.py +++ b/tests/integration/transactions/test_set_regular_key.py @@ -11,10 +11,10 @@ class TestSetRegularKey(IntegrationTestCase): @test_async_and_sync(globals()) async def test_all_fields(self, client): - regular_key = Wallet.create().classic_address + regular_key = Wallet.create().address response = await sign_and_reliable_submission_async( SetRegularKey( - account=WALLET.classic_address, + account=WALLET.address, regular_key=regular_key, ), WALLET, diff --git a/tests/integration/transactions/test_signer_list_set.py b/tests/integration/transactions/test_signer_list_set.py index 4a226484e..3f7a83688 100644 --- a/tests/integration/transactions/test_signer_list_set.py +++ b/tests/integration/transactions/test_signer_list_set.py @@ -15,11 +15,11 @@ async def test_add_signer(self, client): other_signer = Wallet.create() response = await sign_and_reliable_submission_async( SignerListSet( - account=WALLET.classic_address, + account=WALLET.address, signer_quorum=1, signer_entries=[ SignerEntry( - account=other_signer.classic_address, + account=other_signer.address, signer_weight=1, ), ], diff --git a/tests/integration/transactions/test_ticket_create.py b/tests/integration/transactions/test_ticket_create.py index 2855d6365..0cb372bc1 100644 --- a/tests/integration/transactions/test_ticket_create.py +++ b/tests/integration/transactions/test_ticket_create.py @@ -12,7 +12,7 @@ class TestTicketCreate(IntegrationTestCase): async def test_basic_functionality(self, client): response = await sign_and_reliable_submission_async( TicketCreate( - account=WALLET.classic_address, + account=WALLET.address, ticket_count=2, ), WALLET, diff --git a/tests/integration/transactions/test_trust_set.py b/tests/integration/transactions/test_trust_set.py index d30dd7496..e53b5e54d 100644 --- a/tests/integration/transactions/test_trust_set.py +++ b/tests/integration/transactions/test_trust_set.py @@ -15,10 +15,10 @@ async def test_basic_functionality(self, client): issuer_wallet = Wallet.create() response = await sign_and_reliable_submission_async( TrustSet( - account=WALLET.classic_address, + account=WALLET.address, flags=TrustSetFlag.TF_SET_NO_RIPPLE, limit_amount=IssuedCurrencyAmount( - issuer=issuer_wallet.classic_address, + issuer=issuer_wallet.address, currency="USD", value="100", ), diff --git a/tests/unit/asyn/wallet/test_main.py b/tests/unit/asyn/wallet/test_main.py new file mode 100644 index 000000000..ba7d3858e --- /dev/null +++ b/tests/unit/asyn/wallet/test_main.py @@ -0,0 +1,405 @@ +from unittest import TestCase + +from xrpl.constants import CryptoAlgorithm, XRPLException +from xrpl.wallet.main import Wallet + +constants = { + "regular_key_pair": { + "master_address": "rUAi7pipxGpYfPNg3LtPcf2ApiS8aw9A93", + "seed": "sh8i92YRnEjJy3fpFkL8txQSCVo79", + "ed25519": { + "public_key": "ED848AB5972C2D7885DBF188EAF1DC24C3EE8064E41C13AAFF3B731494B9C81990", # noqa:E501 + "private_key": "ED14CAA44B431CEE77F13139BFDE59F63CBEBCFF37C6F0F4AC6F05620A5EDBE33C", # noqa:E501 + }, + "secp256k1": { + "public_key": "03AEEFE1E8ED4BBC009DE996AC03A8C6B5713B1554794056C66E5B8D1753C7DD0E", # noqa:E501 + "private_key": "004265A28F3E18340A490421D47B2EB8DBC2C0BF2C24CEFEA971B61CED2CABD233", # noqa:E501 + }, + }, + "seed": { + "seed": "ssL9dv2W5RK8L3tuzQxYY6EaZhSxW", + "ed25519": { + "public_key": "ED16FD02F7A7E52B6ACB35F8A4D7013DC94755951629DB6611483590AC0E9FC6D5", # noqa:E501 + "private_key": "ED713A8C3171A3D8F69FE9526D9243834B3A30BF893B1EBC1824B1D96F18B44DCF", # noqa:E501 + "address": "rPxLJ2GumwfLk3z9njmmpo8nAX7sfZYptV", + }, + "secp256k1": { + "public_key": "030E58CDD076E798C84755590AAF6237CA8FAE821070A59F648B517A30DC6F589D", # noqa:E501 + "private_key": "00141BA006D3363D2FB2785E8DF4E44D3A49908780CB4FB51F6D217C08C021429F", # noqa:E501 + "address": "rhvh5SrgBL5V8oeV9EpDuVszeJSSCEkbPc", + }, + }, + "entropy": { + "entropy": "00000000000000000000000000000000", + "ed25519": { + "seed": "sEdSJHS4oiAdz7w2X2ni1gFiqtbJHqE", + "public_key": "ED1A7C082846CFF58FF9A892BA4BA2593151CCF1DBA59F37714CC9ED39824AF85F", # noqa:E501 + "private_key": "ED0B6CBAC838DFE7F47EA1BD0DF00EC282FDF45510C92161072CCFB84035390C4D", # noqa:E501 + "address": "r9zRhGr7b6xPekLvT6wP4qNdWMryaumZS7", + }, + "secp256k1": { + "seed": "sp6JS7f14BuwFY8Mw6bTtLKWauoUs", + "public_key": "0390A196799EE412284A5D80BF78C3E84CBB80E1437A0AECD9ADF94D7FEAAFA284", # noqa:E501 + "private_key": "002512BBDFDBB77510883B7DCCBEF270B86DEAC8B64AC762873D75A1BEE6298665", # noqa:E501 + "address": "rGCkuB7PBr5tNy68tPEABEtcdno4hE6Y7f", + }, + }, + "secret_numbers": { + "string": "399150 474506 009147 088773 432160 282843 253738 605430", + "array": [ + "399150", + "474506", + "009147", + "088773", + "432160", + "282843", + "253738", + "605430", + ], + "ed25519": { + "seed": "sEd77GiNwRkBYwqzZiTrmh21oovzSAC", + "public_key": "ED8079E575450E256C496578480020A33E19B579D58A2DB8FF13FC6B05B9229DE3", # noqa:E501 + "private_key": "EDD2AF6288A903DED9860FC62E778600A985BDF804E40BD8266505553E3222C3DA", # noqa:E501 + "address": "rHnnXF4oYodLonx7P7MV4WaqPUvBWzskEw", + }, + "secp256k1": { + "seed": "sh1HiK7SwjS1VxFdXi7qeMHRedrYX", + "public_key": "03BFC2F7AE242C3493187FA0B72BE97B2DF71194FB772E507FF9DEA0AD13CA1625", # noqa:E501 + "private_key": "00B6FE8507D977E46E988A8A94DB3B8B35E404B60F8B11AC5213FA8B5ABC8A8D19", # noqa:E501 + "address": "rQKQsPeE3iTRyfUypLhuq74gZdcRdwWqDp", + }, + }, + "prefix": { + "address": "r", + "ed25519_key": "ED", + "secp256k1_private_key": "00", + }, + "xaddress": { + "tag": 1337, + "mainnet_xaddress": "X7gJ5YK8abHf2eTPWPFHAAot8Knck11QGqmQ7a6a3Z8PJvk", + "testnet_xaddress": "T7bq3e7kxYq9pwDz8UZhqAZoEkcRGTXSNr5immvcj3DYRaV", + "wallet": Wallet( + "030E58CDD076E798C84755590AAF6237CA8FAE821070A59F648B517A30DC6F589D", + "00141BA006D3363D2FB2785E8DF4E44D3A49908780CB4FB51F6D217C08C021429F", + ), + }, +} + + +def _test_wallet_values( + self, wallet, method, algorithm, *, is_entropy_with_regular_key_pair=False +): + method_constants = constants[method] + algorithm_constants = method_constants[algorithm] + + self.assertEqual(wallet.public_key, algorithm_constants["public_key"]) + self.assertEqual(wallet.private_key, algorithm_constants["private_key"]) + + if method == "entropy" or method == "secret_numbers": + self.assertEqual(wallet.seed, algorithm_constants["seed"]) + else: + self.assertEqual(wallet.seed, method_constants["seed"]) + + if method == "regular_key_pair" or is_entropy_with_regular_key_pair: + self.assertEqual( + wallet.address, constants["regular_key_pair"]["master_address"] + ) + else: + self.assertEqual(wallet.address, algorithm_constants["address"]) + + +def _test_wallet_types(self, wallet, algorithm): + self.assertIsInstance(wallet.public_key, str) + self.assertIsInstance(wallet.private_key, str) + self.assertIsInstance(wallet.address, str) + self.assertIsInstance(wallet.address, str) + self.assertIsInstance(wallet.seed, str) + self.assertTrue(wallet.address.startswith(constants["prefix"]["address"])) + + if algorithm == "ed25519": + self.assertTrue( + wallet.public_key.startswith(constants["prefix"]["ed25519_key"]) + ) + self.assertTrue( + wallet.private_key.startswith(constants["prefix"]["ed25519_key"]) + ) + else: + self.assertTrue( + wallet.private_key.startswith(constants["prefix"]["secp256k1_private_key"]) + ) + + +class TestWalletMain(TestCase): + def test_constructor(self): + wallet = Wallet( + constants["seed"]["secp256k1"]["public_key"], + constants["seed"]["secp256k1"]["private_key"], + seed=constants["seed"]["seed"], + ) + + _test_wallet_values(self, wallet, "seed", "secp256k1") + + def test_constructor_using_regular_key_pair(self): + wallet = Wallet( + constants["regular_key_pair"]["secp256k1"]["public_key"], + constants["regular_key_pair"]["secp256k1"]["private_key"], + master_address=constants["regular_key_pair"]["master_address"], + seed=constants["regular_key_pair"]["seed"], + ) + + _test_wallet_values(self, wallet, "regular_key_pair", "secp256k1") + + def test_create_using_default_algorithm(self): + wallet = Wallet.create() + + _test_wallet_types(self, wallet, "ed25519") + + def test_create_using_algorithm_ed25519(self): + wallet = Wallet.create(CryptoAlgorithm.ED25519) + + _test_wallet_types(self, wallet, "ed25519") + + def test_create_using_algorithm_ecdsa_secp256k1(self): + wallet = Wallet.create(CryptoAlgorithm.SECP256K1) + + _test_wallet_types(self, wallet, "secp256k1") + + def test_from_seed_using_default_algorithm(self): + wallet = Wallet.from_seed(constants["seed"]["seed"]) + + _test_wallet_values(self, wallet, "seed", "ed25519") + + def test_from_seed_using_algorithm_ecdsa_secp256k1(self): + wallet = Wallet.from_seed( + constants["seed"]["seed"], algorithm=CryptoAlgorithm.SECP256K1 + ) + + _test_wallet_values(self, wallet, "seed", "secp256k1") + + def test_from_seed_using_algorithm_ed25519(self): + wallet = Wallet.from_seed( + constants["seed"]["seed"], algorithm=CryptoAlgorithm.ED25519 + ) + + _test_wallet_values(self, wallet, "seed", "ed25519") + + def test_from_seed_using_regular_key_pair_using_default_algorithm(self): + wallet = Wallet.from_seed( + constants["regular_key_pair"]["seed"], + master_address=constants["regular_key_pair"]["master_address"], + ) + + _test_wallet_values(self, wallet, "regular_key_pair", "ed25519") + + def test_from_seed_using_regular_key_pair_using_algorithm_ed25519(self): + wallet = Wallet.from_seed( + constants["regular_key_pair"]["seed"], + master_address=constants["regular_key_pair"]["master_address"], + algorithm=CryptoAlgorithm.ED25519, + ) + + _test_wallet_values(self, wallet, "regular_key_pair", "ed25519") + + def test_from_seed_using_regular_key_pair_using_algorithm_edcsa_secp256k1(self): + wallet = Wallet.from_seed( + constants["regular_key_pair"]["seed"], + master_address=constants["regular_key_pair"]["master_address"], + algorithm=CryptoAlgorithm.SECP256K1, + ) + + _test_wallet_values(self, wallet, "regular_key_pair", "secp256k1") + + def test_from_secret_using_default_algorithm(self): + wallet = Wallet.from_secret(constants["seed"]["seed"]) + + _test_wallet_values(self, wallet, "seed", "ed25519") + + def test_from_secret_using_algorithm_ecdsa_secp256k1(self): + wallet = Wallet.from_secret( + constants["seed"]["seed"], algorithm=CryptoAlgorithm.SECP256K1 + ) + + _test_wallet_values(self, wallet, "seed", "secp256k1") + + def test_from_secret_using_algorithm_ed25519(self): + wallet = Wallet.from_secret( + constants["seed"]["seed"], algorithm=CryptoAlgorithm.ED25519 + ) + + _test_wallet_values(self, wallet, "seed", "ed25519") + + def test_from_secret_using_regular_key_pair_using_default_algorithm(self): + wallet = Wallet.from_secret( + constants["regular_key_pair"]["seed"], + master_address=constants["regular_key_pair"]["master_address"], + ) + + _test_wallet_values(self, wallet, "regular_key_pair", "ed25519") + + def test_from_secret_using_regular_key_pair_using_algorithm_ed25519(self): + wallet = Wallet.from_secret( + constants["regular_key_pair"]["seed"], + master_address=constants["regular_key_pair"]["master_address"], + algorithm=CryptoAlgorithm.ED25519, + ) + + _test_wallet_values(self, wallet, "regular_key_pair", "ed25519") + + def test_from_secret_using_regular_key_pair_using_algorithm_edcsa_secp256k1(self): + wallet = Wallet.from_secret( + constants["regular_key_pair"]["seed"], + master_address=constants["regular_key_pair"]["master_address"], + algorithm=CryptoAlgorithm.SECP256K1, + ) + + _test_wallet_values(self, wallet, "regular_key_pair", "secp256k1") + + def test_from_entropy_using_default_algorithm(self): + wallet = Wallet.from_entropy(constants["entropy"]["entropy"]) + + _test_wallet_values(self, wallet, "entropy", "ed25519") + + def test_from_entropy_using_algorithm_ecdsa_secp256k1(self): + wallet = Wallet.from_entropy( + constants["entropy"]["entropy"], algorithm=CryptoAlgorithm.SECP256K1 + ) + + _test_wallet_values(self, wallet, "entropy", "secp256k1") + + def test_from_entropy_using_algorithm_ed25519(self): + wallet = Wallet.from_entropy( + constants["entropy"]["entropy"], algorithm=CryptoAlgorithm.ED25519 + ) + + _test_wallet_values(self, wallet, "entropy", "ed25519") + + def test_from_entropy_using_regular_key_pair_using_default_algorithm(self): + wallet = Wallet.from_entropy( + constants["entropy"]["entropy"], + master_address=constants["regular_key_pair"]["master_address"], + ) + + _test_wallet_values( + self, wallet, "entropy", "ed25519", is_entropy_with_regular_key_pair=True + ) + + def test_from_entropy_using_regular_key_pair_using_algorithm_ecdsa_secp256k1(self): + wallet = Wallet.from_entropy( + constants["entropy"]["entropy"], + master_address=constants["regular_key_pair"]["master_address"], + algorithm=CryptoAlgorithm.SECP256K1, + ) + + _test_wallet_values( + self, wallet, "entropy", "secp256k1", is_entropy_with_regular_key_pair=True + ) + + def test_from_entropy_using_regular_key_pair_using_algorithm_ed25519(self): + wallet = Wallet.from_entropy( + constants["entropy"]["entropy"], + master_address=constants["regular_key_pair"]["master_address"], + algorithm=CryptoAlgorithm.ED25519, + ) + + _test_wallet_values( + self, wallet, "entropy", "ed25519", is_entropy_with_regular_key_pair=True + ) + + def test_from_secret_numbers_string_using_default_algorithm(self): + wallet = Wallet.from_secret_numbers(constants["secret_numbers"]["string"]) + + _test_wallet_values(self, wallet, "secret_numbers", "secp256k1") + + def test_from_secret_numbers_array_using_default_algorithm(self): + wallet = Wallet.from_secret_numbers(constants["secret_numbers"]["array"]) + + _test_wallet_values(self, wallet, "secret_numbers", "secp256k1") + + def test_from_secret_numbers_string_using_algorithm_ecdsa_secp256k1(self): + wallet = Wallet.from_secret_numbers( + constants["secret_numbers"]["string"], + algorithm=CryptoAlgorithm.SECP256K1, + ) + + _test_wallet_values(self, wallet, "secret_numbers", "secp256k1") + + def test_from_secret_numbers_array_using_algorithm_ecdsa_secp256k1(self): + wallet = Wallet.from_secret_numbers( + constants["secret_numbers"]["array"], + algorithm=CryptoAlgorithm.SECP256K1, + ) + + _test_wallet_values(self, wallet, "secret_numbers", "secp256k1") + + def test_from_secret_numbers_string_using_algorithm_ed25519(self): + wallet = Wallet.from_secret_numbers( + constants["secret_numbers"]["string"], + algorithm=CryptoAlgorithm.ED25519, + ) + + _test_wallet_values(self, wallet, "secret_numbers", "ed25519") + + def test_from_secret_numbers_array_using_algorithm_ed25519(self): + wallet = Wallet.from_secret_numbers( + constants["secret_numbers"]["array"], + algorithm=CryptoAlgorithm.ED25519, + ) + + _test_wallet_values(self, wallet, "secret_numbers", "ed25519") + + def test_from_secret_numbers_failure_nine_numbers(self): + invalid_array = constants["secret_numbers"]["array"].copy() + invalid_array.append("605430") + + with self.assertRaises(XRPLException): + Wallet.from_secret_numbers( + invalid_array, + algorithm=CryptoAlgorithm.ED25519, + ) + + def test_from_secret_numbers_failure_seven_digit_number(self): + invalid_array = constants["secret_numbers"]["array"].copy() + invalid_array[0] += "1" + + with self.assertRaises(XRPLException): + Wallet.from_secret_numbers( + invalid_array, + algorithm=CryptoAlgorithm.ED25519, + ) + + def test_get_xaddress_when_test_is_true(self): + self.assertEqual( + constants["xaddress"]["wallet"].get_xaddress( + tag=constants["xaddress"]["tag"], is_test=True + ), + constants["xaddress"]["testnet_xaddress"], + ) + + def test_get_xaddress_when_test_is_false(self): + self.assertEqual( + constants["xaddress"]["wallet"].get_xaddress( + tag=constants["xaddress"]["tag"], is_test=False + ), + constants["xaddress"]["mainnet_xaddress"], + ) + + def test_get_xaddress_when_test_is_not_provided(self): + self.assertEqual( + constants["xaddress"]["wallet"].get_xaddress( + tag=constants["xaddress"]["tag"] + ), + constants["xaddress"]["mainnet_xaddress"], + ) + + def test_address_alias(self): + wallet = Wallet.create() + self.assertEqual(wallet.address, wallet.address) + + def test_get_only_address(self): + wallet = Wallet.create() + + with self.assertRaises(AttributeError): + wallet.address = "rhvh5SrgBL5V8oeV9EpDuVszeJSSCEkbPc" + + with self.assertRaises(AttributeError): + wallet.address = "rhvh5SrgBL5V8oeV9EpDuVszeJSSCEkbPc" diff --git a/tests/unit/core/addresscodec/test_main.py b/tests/unit/core/addresscodec/test_main.py index 77856c433..f1cbb4a22 100644 --- a/tests/unit/core/addresscodec/test_main.py +++ b/tests/unit/core/addresscodec/test_main.py @@ -1,8 +1,12 @@ +import asyncio from unittest import TestCase from tests.unit.core.addresscodec.test_main_test_cases import test_cases +from xrpl.asyncio.transaction.main import _calculate_fee_per_transaction_type from xrpl.core import addresscodec from xrpl.core.addresscodec.main import MAX_32_BIT_UNSIGNED_INT +from xrpl.models.amounts.issued_currency_amount import IssuedCurrencyAmount +from xrpl.models.transactions.payment import Payment class TestMain(TestCase): @@ -91,6 +95,42 @@ def test_classic_address_to_xaddress_bad_classic_address(self): False, ) + def test_ensure_classic_address(self): + for test_case in test_cases: + ( + expected_classic_address, + tag, + main_xaddress, + test_xaddress, + ) = test_case + + # classic address + classic_address = addresscodec.ensure_classic_address( + expected_classic_address + ) + self.assertEqual(classic_address, expected_classic_address) + + # tagged xaddress + if tag is not None: + self.assertRaises( + addresscodec.XRPLAddressCodecException, + addresscodec.ensure_classic_address, + main_xaddress, + ) + self.assertRaises( + addresscodec.XRPLAddressCodecException, + addresscodec.ensure_classic_address, + test_xaddress, + ) + else: + # main xaddress + classic_address = addresscodec.ensure_classic_address(main_xaddress) + self.assertEqual(classic_address, expected_classic_address) + + # test xaddress + classic_address = addresscodec.ensure_classic_address(test_xaddress) + self.assertEqual(classic_address, expected_classic_address) + def test_is_valid_classic_address_secp256k1(self): classic_address = "rU6K7V3Po4snVhBBaU29sesqs2qTQJWDw1" @@ -132,3 +172,24 @@ def test_is_valid_xaddress_empty(self): result = addresscodec.is_valid_xaddress(xaddress) self.assertFalse(result) + + def test_basic_calculate_fee_per_transaction_type_offline(self): + fee = asyncio.run( + _calculate_fee_per_transaction_type( + Payment( + account="rweYz56rfmQ98cAdRaeTxQS9wVMGnrdsFp", + amount=IssuedCurrencyAmount( + currency="USD", + issuer="rweYz56rfmQ98cAdRaeTxQS9wVMGnrdsFp", + value="0.0001", + ), + destination="rweYz56rfmQ98cAdRaeTxQS9wVMGnrdsFp", + send_max=IssuedCurrencyAmount( + currency="BTC", + issuer="rweYz56rfmQ98cAdRaeTxQS9wVMGnrdsFp", + value="0.0000002831214446", + ), + ) + ) + ) + self.assertEqual(fee, "10") diff --git a/tests/unit/core/binarycodec/types/test_amount.py b/tests/unit/core/binarycodec/types/test_amount.py index 370ceb0da..dd5099035 100644 --- a/tests/unit/core/binarycodec/types/test_amount.py +++ b/tests/unit/core/binarycodec/types/test_amount.py @@ -5,6 +5,8 @@ ) from xrpl.core.binarycodec.binary_wrappers import BinaryParser from xrpl.core.binarycodec.exceptions import XRPLBinaryCodecException +from xrpl.models.amounts.amount import is_issued_currency +from xrpl.models.amounts.issued_currency_amount import IssuedCurrencyAmount # [IOU dict, expected serialized hex] IOU_CASES = [ @@ -161,3 +163,11 @@ def test_to_json_xrp(self): def test_fixtures(self): for fixture in data_driven_fixtures_for_type("Amount"): self.fixture_test(fixture) + + def test_is_issued_currency(self): + issued_currency = IssuedCurrencyAmount( + currency="USD", issuer="rDgZZ3wyprx4ZqrGQUkquE9Fs2Xs8XBcdw", value=10 + ) + self.assertTrue(is_issued_currency(issued_currency)) + xrp_amount = "10" + self.assertFalse(is_issued_currency(xrp_amount)) diff --git a/tests/unit/core/keypairs/test_main.py b/tests/unit/core/keypairs/test_main.py index 3efb11fe3..14893e316 100644 --- a/tests/unit/core/keypairs/test_main.py +++ b/tests/unit/core/keypairs/test_main.py @@ -3,37 +3,46 @@ from xrpl.constants import CryptoAlgorithm from xrpl.core import keypairs +from xrpl.core.addresscodec.exceptions import XRPLAddressCodecException from xrpl.core.keypairs.exceptions import XRPLKeypairsException -_DUMMY_BYTES = b"\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r\x0e\x0f\x10" +_DUMMY_HEX = "0102030405060708090a0b0c0d0e0f10" +_DUMMY_HEX_TOO_SHORT = "0102030405060708090a0b0c0d0e" +_DUMMY_HEX_TOO_LONG = "0102030405060708090a0b0c0d0e0f101112" class TestMain(TestCase): # unfortunately, this patching is very brittle; it depends on the syntax # used to import secrets within the calling module. @patch( - "xrpl.core.keypairs.main.token_bytes", autospec=True, return_value=_DUMMY_BYTES + "xrpl.core.keypairs.main.token_bytes", + autospec=True, + return_value=bytes.fromhex(_DUMMY_HEX), ) def test_generate_seed_no_params(self, _randbytes): output = keypairs.generate_seed() self.assertEqual(output, "sEdSKaCy2JT7JaM7v95H9SxkhP9wS2r") def test_generate_seed_entropy_provided(self): - output = keypairs.generate_seed(_DUMMY_BYTES.decode("UTF-8")) + output = keypairs.generate_seed(_DUMMY_HEX) self.assertEqual(output, "sEdSKaCy2JT7JaM7v95H9SxkhP9wS2r") def test_generate_seed_ed25519(self): - output = keypairs.generate_seed( - _DUMMY_BYTES.decode("UTF-8"), CryptoAlgorithm.ED25519 - ) + output = keypairs.generate_seed(_DUMMY_HEX, CryptoAlgorithm.ED25519) self.assertEqual(output, "sEdSKaCy2JT7JaM7v95H9SxkhP9wS2r") def test_generate_seed_secp256k1(self): - output = keypairs.generate_seed( - _DUMMY_BYTES.decode("UTF-8"), CryptoAlgorithm.SECP256K1 - ) + output = keypairs.generate_seed(_DUMMY_HEX, CryptoAlgorithm.SECP256K1) self.assertEqual(output, "sp5fghtJtpUorTwvof1NpDXAzNwf5") + def test_generate_seed_entropy_provided_too_short(self): + with self.assertRaises(XRPLAddressCodecException): + keypairs.generate_seed(_DUMMY_HEX_TOO_SHORT) + + def test_generate_seed_entropy_provided_too_long(self): + with self.assertRaises(XRPLAddressCodecException): + keypairs.generate_seed(_DUMMY_HEX_TOO_LONG) + def test_derive_keypair_ed25519(self): public, private = keypairs.derive_keypair("sEdSKaCy2JT7JaM7v95H9SxkhP9wS2r") self.assertEqual( diff --git a/tests/unit/models/currencies/test_xrp.py b/tests/unit/models/currencies/test_xrp.py index c823f5ee1..473cd0969 100644 --- a/tests/unit/models/currencies/test_xrp.py +++ b/tests/unit/models/currencies/test_xrp.py @@ -1,6 +1,7 @@ from unittest import TestCase from xrpl.models.currencies import XRP +from xrpl.utils import xrp_to_drops class TestXRP(TestCase): @@ -8,7 +9,8 @@ def test_to_dict(self): self.assertEqual(XRP().to_dict()["currency"], "XRP") def test_to_amount(self): - amount = "12" - issued_currency_amount = XRP().to_amount(amount) + amount = 12 + expected = xrp_to_drops(amount) + result = XRP().to_amount(amount) - self.assertEqual(issued_currency_amount, amount) + self.assertEqual(result, expected) diff --git a/tests/unit/models/transactions/test_account_set.py b/tests/unit/models/transactions/test_account_set.py index f1faa6c1b..623a32e91 100644 --- a/tests/unit/models/transactions/test_account_set.py +++ b/tests/unit/models/transactions/test_account_set.py @@ -1,7 +1,7 @@ from unittest import TestCase from xrpl.models.exceptions import XRPLModelException -from xrpl.models.transactions import AccountSet, AccountSetFlag +from xrpl.models.transactions import AccountSet, AccountSetAsfFlag _ACCOUNT = "r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ" _ANOTHER_ACCOUNT = "rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW" @@ -98,7 +98,7 @@ def test_nftoken_minter_not_set_with_minter_flag(self): AccountSet( account=_ACCOUNT, fee=_FEE, - set_flag=AccountSetFlag.ASF_AUTHORIZED_NFTOKEN_MINTER, + set_flag=AccountSetAsfFlag.ASF_AUTHORIZED_NFTOKEN_MINTER, ) def test_nftoken_minter_set_with_clear_minter_flag(self): @@ -106,7 +106,7 @@ def test_nftoken_minter_set_with_clear_minter_flag(self): AccountSet( account=_ACCOUNT, fee=_FEE, - clear_flag=AccountSetFlag.ASF_AUTHORIZED_NFTOKEN_MINTER, + clear_flag=AccountSetAsfFlag.ASF_AUTHORIZED_NFTOKEN_MINTER, nftoken_minter=_ANOTHER_ACCOUNT, ) @@ -114,7 +114,7 @@ def test_nftoken_minter_set_with_minter_flag(self): tx = AccountSet( account=_ACCOUNT, fee=_FEE, - set_flag=AccountSetFlag.ASF_AUTHORIZED_NFTOKEN_MINTER, + set_flag=AccountSetAsfFlag.ASF_AUTHORIZED_NFTOKEN_MINTER, nftoken_minter=_ANOTHER_ACCOUNT, ) self.assertTrue(tx.is_valid()) @@ -123,6 +123,6 @@ def test_nftoken_minter_not_set_with_clear_minter_flag(self): tx = AccountSet( account=_ACCOUNT, fee=_FEE, - clear_flag=AccountSetFlag.ASF_AUTHORIZED_NFTOKEN_MINTER, + clear_flag=AccountSetAsfFlag.ASF_AUTHORIZED_NFTOKEN_MINTER, ) self.assertTrue(tx.is_valid()) diff --git a/tests/unit/models/transactions/test_better_transaction_flags.py b/tests/unit/models/transactions/test_better_transaction_flags.py index d0a90c39d..4518744ee 100644 --- a/tests/unit/models/transactions/test_better_transaction_flags.py +++ b/tests/unit/models/transactions/test_better_transaction_flags.py @@ -10,7 +10,7 @@ ACCOUNT = "rQUUhraHao4wCqS4MJyzzQP79QE6T9FdeL" SEED = "snHG27JeogwML83AwTRyvTXxCWteF" -WALLET = Wallet(seed=SEED, sequence=0) +WALLET = Wallet.from_seed(SEED) class TestBetterTransactionFlags(TestCase): @@ -18,23 +18,15 @@ def test_account_set_flags(self): actual = models.AccountSet( account=ACCOUNT, flags=models.AccountSetFlagInterface( - asf_account_tx_id=True, - asf_authorized_nftoken_minter=True, - asf_default_ripple=True, - asf_deposit_auth=True, - asf_disable_master=True, - asf_disallow_xrp=True, - asf_global_freeze=True, - asf_no_freeze=True, - asf_require_auth=True, - asf_require_dest=True, - asf_disable_incoming_nftoken_offer=True, - asf_disable_incoming_check=True, - asf_disable_incoming_paychan=True, - asf_disable_incoming_trustline=True, + tf_require_dest_tag=True, + tf_optional_dest_tag=True, + tf_require_auth=True, + tf_optional_auth=True, + tf_disallow_xrp=True, + tf_allow_xrp=True, ), ) - self.assertTrue(actual.has_flag(flag=0x00000005)) + self.assertTrue(actual.has_flag(flag=0x00010000)) self.assertTrue(actual.is_valid()) flags = models.AccountSetFlag expected = models.AccountSet( diff --git a/tests/unit/models/transactions/test_transaction.py b/tests/unit/models/transactions/test_transaction.py index 81cc1a602..1ff971561 100644 --- a/tests/unit/models/transactions/test_transaction.py +++ b/tests/unit/models/transactions/test_transaction.py @@ -1,4 +1,3 @@ -import asyncio from unittest import TestCase from xrpl.asyncio.transaction.main import sign @@ -133,18 +132,18 @@ def test_throws_when_ticket_sequence_and_account_tx_in_both_included(self): ) def test_is_signed_for_signed_transaction(self): - tx = AccountSet(account=_WALLET.classic_address, domain=EXAMPLE_DOMAIN) - signed_tx = asyncio.run(sign(tx, _WALLET)) + tx = AccountSet(account=_WALLET.address, domain=EXAMPLE_DOMAIN) + signed_tx = sign(tx, _WALLET) self.assertTrue(signed_tx.is_signed()) def test_is_signed_for_unsigned_transaction(self): - tx = AccountSet(account=_WALLET.classic_address, domain=EXAMPLE_DOMAIN) + tx = AccountSet(account=_WALLET.address, domain=EXAMPLE_DOMAIN) self.assertFalse(tx.is_signed()) def test_is_signed_for_multisigned_transaction(self): - tx = AccountSet(account=_WALLET.classic_address, domain=EXAMPLE_DOMAIN) - tx_1 = asyncio.run(sign(tx, _FIRST_SIGNER, multisign=True)) - tx_2 = asyncio.run(sign(tx, _SECOND_SIGNER, multisign=True)) + tx = AccountSet(account=_WALLET.address, domain=EXAMPLE_DOMAIN) + tx_1 = sign(tx, _FIRST_SIGNER, multisign=True) + tx_2 = sign(tx, _SECOND_SIGNER, multisign=True) multisigned_tx = multisign(tx, [tx_1, tx_2]) self.assertTrue(multisigned_tx.is_signed()) diff --git a/tests/unit/wallet/test_wallet.py b/tests/unit/wallet/test_wallet.py index 17fc9ba7c..aa52cdc55 100644 --- a/tests/unit/wallet/test_wallet.py +++ b/tests/unit/wallet/test_wallet.py @@ -19,35 +19,35 @@ def test_create_basic(self): self.assertTrue(wallet.seed.startswith("sEd")) def test_create_secp(self): - wallet = Wallet.create(crypto_algorithm=CryptoAlgorithm.SECP256K1) + wallet = Wallet.create(algorithm=CryptoAlgorithm.SECP256K1) self.assertEqual(wallet.algorithm, CryptoAlgorithm.SECP256K1) self.assertFalse(wallet.seed.startswith("sEd")) self.assertTrue(wallet.seed.startswith("s")) - def test_init_auto_with_s_seed(self): - wallet = Wallet(SEED, 0) - self.assertEqual(wallet.seed, SEED) - self.assertEqual(wallet.classic_address, SECP_ADDRESS) - self.assertEqual(wallet.algorithm, CryptoAlgorithm.SECP256K1) + def test_init_auto_with_default_algorithm(self): + wallet = Wallet.from_seed(SED_SEED) + self.assertEqual(wallet.seed, SED_SEED) + self.assertEqual(wallet.address, SED_ADDRESS) + self.assertEqual(wallet.algorithm, CryptoAlgorithm.ED25519) def test_init_auto_with_sEd_seed(self): - wallet = Wallet(SED_SEED, 0) + wallet = Wallet.from_seed(SED_SEED) self.assertEqual(wallet.seed, SED_SEED) - self.assertEqual(wallet.classic_address, SED_ADDRESS) + self.assertEqual(wallet.address, SED_ADDRESS) self.assertEqual(wallet.algorithm, CryptoAlgorithm.ED25519) def test_init_secp256k1_with_s_seed(self): - wallet = Wallet(SEED, 0, algorithm=CryptoAlgorithm.SECP256K1) + wallet = Wallet.from_seed(SEED, algorithm=CryptoAlgorithm.SECP256K1) self.assertEqual(wallet.seed, SEED) - self.assertEqual(wallet.classic_address, SECP_ADDRESS) + self.assertEqual(wallet.address, SECP_ADDRESS) self.assertEqual(wallet.algorithm, CryptoAlgorithm.SECP256K1) def test_init_ed25519_with_s_seed(self): - wallet = Wallet(SEED, 0, algorithm=CryptoAlgorithm.ED25519) + wallet = Wallet.from_seed(SEED, algorithm=CryptoAlgorithm.ED25519) self.assertEqual(wallet.seed, SEED) - self.assertEqual(wallet.classic_address, ED_ADDRESS) + self.assertEqual(wallet.address, ED_ADDRESS) self.assertEqual(wallet.algorithm, CryptoAlgorithm.ED25519) def test_init_secp256k1_with_sEd_seed_fail(self): with self.assertRaises(XRPLAddressCodecException): - Wallet(SED_SEED, 0, algorithm=CryptoAlgorithm.SECP256K1) + Wallet.from_seed(SED_SEED, algorithm=CryptoAlgorithm.SECP256K1) diff --git a/xrpl/account/__init__.py b/xrpl/account/__init__.py index 5043dc1a3..5f575c43b 100644 --- a/xrpl/account/__init__.py +++ b/xrpl/account/__init__.py @@ -1,24 +1,16 @@ """Methods for interacting with XRPL accounts.""" from xrpl.account.main import ( does_account_exist, - get_account_info, get_account_root, get_balance, get_next_valid_seq_number, ) -from xrpl.account.transaction_history import ( - get_account_payment_transactions, - get_account_transactions, - get_latest_transaction, -) +from xrpl.account.transaction_history import get_latest_transaction __all__ = [ "get_next_valid_seq_number", "get_balance", "get_account_root", - "get_account_info", - "get_account_payment_transactions", - "get_account_transactions", "does_account_exist", "get_latest_transaction", ] diff --git a/xrpl/account/main.py b/xrpl/account/main.py index 4472054cc..028468f15 100644 --- a/xrpl/account/main.py +++ b/xrpl/account/main.py @@ -3,11 +3,8 @@ import asyncio from typing import Dict, Union -from deprecated.sphinx import deprecated - from xrpl.asyncio.account import main from xrpl.clients.sync_client import SyncClient -from xrpl.models.response import Response def does_account_exist( @@ -91,30 +88,3 @@ def get_account_root( The AccountRoot dictionary for the address. """ return asyncio.run(main.get_account_root(address, client, ledger_index)) - - -@deprecated( - reason="Sending an AccountInfo request directly is just as easy to use.", - version="1.8.0", -) -def get_account_info( - address: str, client: SyncClient, ledger_index: Union[str, int] = "validated" -) -> Response: # pragma: no cover - """ - Query the ledger for account info of given address. - - Args: - address: the account to query. - client: the network client used to make network calls. - ledger_index: The ledger index to use for the request. Must be an integer - ledger value or "current" (the current working version), "closed" (for the - closed-and-proposed version), or "validated" (the most recent version - validated by consensus). The default is "validated". - - Returns: - The account info for the address. - - Raises: - XRPLRequestFailureException: if the rippled API call fails. - """ - return asyncio.run(main.get_account_info(address, client, ledger_index)) diff --git a/xrpl/account/transaction_history.py b/xrpl/account/transaction_history.py index 91334abb6..79677dcd8 100644 --- a/xrpl/account/transaction_history.py +++ b/xrpl/account/transaction_history.py @@ -1,8 +1,5 @@ """High-level methods to obtain information about account transaction history.""" import asyncio -from typing import Any, Dict, List - -from deprecated.sphinx import deprecated from xrpl.asyncio.account import transaction_history from xrpl.clients.sync_client import SyncClient @@ -24,56 +21,3 @@ def get_latest_transaction(account: str, client: SyncClient) -> Response: XRPLRequestFailureException: if the transaction fails. """ return asyncio.run(transaction_history.get_latest_transaction(account, client)) - - -@deprecated( - reason="Sending an AccountTx request directly allows you to page through all " - "results and is just as easy to use.", - version="1.6.0", -) -def get_account_transactions( - address: str, client: SyncClient -) -> List[Dict[str, Any]]: # pragma: no cover - """ - Query the ledger for a list of transactions that involved a given account. - To access more than just the first page of results, use the :class:`AccountTx` - request directly. - - Args: - address: the account to query. - client: the network client used to make network calls. - - Returns: - The most recent transaction history for the address. For the full history, - page through the :class:`AccountTx` request directly. - - Raises: - XRPLRequestFailureException: if the transaction fails. - """ - return asyncio.run(transaction_history.get_account_transactions(address, client)) - - -@deprecated( - reason="Sending an AccountTx request directly and filtering for payments allows " - "you to page through all results and is just as easy to use.", - version="1.8.0", -) -def get_account_payment_transactions( - address: str, client: SyncClient -) -> List[Dict[str, Any]]: # pragma: no cover - """ - Query the ledger for a list of payment transactions that involved a given account. - To access more than just the first page of results, use the :class:`AccountTx` - request directly then filter for transactions with a "Payment" TransactionType. - - Args: - address: the account to query. - client: the network client used to make network calls. - - Returns: - The first page of payment transaction history for the address. For the full - history, page through the :class:`AccountTx` request directly. - """ - return asyncio.run( - transaction_history.get_account_payment_transactions(address, client) - ) diff --git a/xrpl/asyncio/account/__init__.py b/xrpl/asyncio/account/__init__.py index 40b1f06b6..44b5907df 100644 --- a/xrpl/asyncio/account/__init__.py +++ b/xrpl/asyncio/account/__init__.py @@ -1,24 +1,16 @@ """Async methods for interacting with XRPL accounts.""" from xrpl.asyncio.account.main import ( does_account_exist, - get_account_info, get_account_root, get_balance, get_next_valid_seq_number, ) -from xrpl.asyncio.account.transaction_history import ( - get_account_payment_transactions, - get_account_transactions, - get_latest_transaction, -) +from xrpl.asyncio.account.transaction_history import get_latest_transaction __all__ = [ "get_next_valid_seq_number", "get_balance", "get_account_root", - "get_account_info", - "get_account_payment_transactions", - "get_account_transactions", "does_account_exist", "get_latest_transaction", ] diff --git a/xrpl/asyncio/account/main.py b/xrpl/asyncio/account/main.py index 1aa4b65be..0741114b4 100644 --- a/xrpl/asyncio/account/main.py +++ b/xrpl/asyncio/account/main.py @@ -2,13 +2,9 @@ from typing import Dict, Union, cast -from deprecated.sphinx import deprecated - from xrpl.asyncio.clients import Client, XRPLRequestFailureException -from xrpl.constants import XRPLException from xrpl.core.addresscodec import is_valid_xaddress, xaddress_to_classic_address from xrpl.models.requests import AccountInfo -from xrpl.models.response import Response async def does_account_exist( @@ -121,52 +117,3 @@ async def get_account_root( raise XRPLRequestFailureException(account_info.result) return cast(Dict[str, Union[int, str]], account_info.result["account_data"]) - - -@deprecated( - reason="Sending an AccountInfo request directly is just as easy to use.", - version="1.8.0", -) -async def get_account_info( - address: str, client: Client, ledger_index: Union[str, int] = "validated" -) -> Response: # pragma: no cover - """ - Query the ledger for account info of given address. - - Args: - address: the account to query. - client: the network client used to make network calls. - ledger_index: The ledger index to use for the request. Must be an integer - ledger value or "current" (the current working version), "closed" (for the - closed-and-proposed version), or "validated" (the most recent version - validated by consensus). The default is "validated". - - Returns: - The account info for the address. - - Raises: - XRPLException: If the ledger_index value is invalid. - XRPLRequestFailureException: if the rippled API call fails. - """ - if is_valid_xaddress(address): - address, _, _ = xaddress_to_classic_address(address) - - if isinstance(ledger_index, str) and ledger_index not in { - "validated", - "current", - "closed", - }: - raise XRPLException( - "`ledger_index` is not valid - must be an `int` or one of {'validated', " - "'current', 'closed'}." - ) - response = await client._request_impl( - AccountInfo( - account=address, - ledger_index=ledger_index, - ) - ) - if response.is_successful(): - return response - - raise XRPLRequestFailureException(response.result) diff --git a/xrpl/asyncio/account/transaction_history.py b/xrpl/asyncio/account/transaction_history.py index 3eba9bda2..84767aed7 100644 --- a/xrpl/asyncio/account/transaction_history.py +++ b/xrpl/asyncio/account/transaction_history.py @@ -1,8 +1,4 @@ """High-level methods to obtain information about account transaction history.""" -from typing import Any, Dict, List, cast - -from deprecated.sphinx import deprecated - from xrpl.asyncio.clients import Client, XRPLRequestFailureException from xrpl.core.addresscodec import is_valid_xaddress, xaddress_to_classic_address from xrpl.models.requests import AccountTx @@ -32,63 +28,3 @@ async def get_latest_transaction(account: str, client: Client) -> Response: if not response.is_successful(): raise XRPLRequestFailureException(response.result) return response - - -@deprecated( - reason="Sending an AccountTx request directly allows you to page through all " - "results and is just as easy to use.", - version="1.6.0", -) -async def get_account_transactions( - address: str, - client: Client, -) -> List[Dict[str, Any]]: # pragma: no cover - """ - Query the ledger for a list of transactions that involved a given account. - To access more than just the first page of results, use the :class:`AccountTx` - request directly. - - Args: - address: the account to query. - client: the network client used to make network calls. - - Returns: - The most recent transaction history for the address. For the full history, - page through the :class:`AccountTx` request directly. - - Raises: - XRPLRequestFailureException: if the transaction fails. - """ - if is_valid_xaddress(address): - address, _, _ = xaddress_to_classic_address(address) - request = AccountTx(account=address) - response = await client._request_impl(request) - if not response.is_successful(): - raise XRPLRequestFailureException(response.result) - return cast(List[Dict[str, Any]], response.result["transactions"]) - - -@deprecated( - reason="Sending an AccountTx request directly and filtering for payments allows " - "you to page through all results and is just as easy to use.", - version="1.8.0", -) -async def get_account_payment_transactions( - address: str, - client: Client, -) -> List[Dict[str, Any]]: # pragma: no cover - """ - Query the ledger for a list of payment transactions that involved a given account. - To access more than just the first page of results, use the :class:`AccountTx` - request directly then filter for transactions with a "Payment" TransactionType. - - Args: - address: the account to query. - client: the network client used to make network calls. - - Returns: - The first page of payment transaction history for the address. For the full - history, page through the :class:`AccountTx` request directly. - """ - all_transactions = await get_account_transactions(address, client) - return [tx for tx in all_transactions if tx["tx"]["TransactionType"] == "Payment"] diff --git a/xrpl/asyncio/clients/client.py b/xrpl/asyncio/clients/client.py index 54819ae4f..15acadd2c 100644 --- a/xrpl/asyncio/clients/client.py +++ b/xrpl/asyncio/clients/client.py @@ -2,6 +2,7 @@ from __future__ import annotations from abc import ABC, abstractmethod +from typing import Optional from xrpl.models.requests.request import Request from xrpl.models.response import Response @@ -22,6 +23,8 @@ def __init__(self: Client, url: str) -> None: url: The url to which this client will connect """ self.url = url + self.network_id: Optional[int] = None + self.build_version: Optional[str] = None @abstractmethod async def _request_impl(self: Client, request: Request) -> Response: diff --git a/xrpl/asyncio/transaction/__init__.py b/xrpl/asyncio/transaction/__init__.py index c1047a1fc..abe53a886 100644 --- a/xrpl/asyncio/transaction/__init__.py +++ b/xrpl/asyncio/transaction/__init__.py @@ -1,36 +1,24 @@ """Async methods for working with transactions on the XRP Ledger.""" -from xrpl.asyncio.transaction.ledger import get_transaction_from_hash from xrpl.asyncio.transaction.main import ( autofill, autofill_and_sign, - safe_sign_and_autofill_transaction, - safe_sign_and_submit_transaction, - safe_sign_transaction, sign, sign_and_submit, submit, - submit_transaction, transaction_json_to_binary_codec_form, ) from xrpl.asyncio.transaction.reliable_submission import ( XRPLReliableSubmissionException, - send_reliable_submission, submit_and_wait, ) __all__ = [ "autofill", "autofill_and_sign", - "get_transaction_from_hash", - "safe_sign_transaction", - "safe_sign_and_autofill_transaction", - "safe_sign_and_submit_transaction", "sign", "sign_and_submit", "submit", "submit_and_wait", - "submit_transaction", "transaction_json_to_binary_codec_form", - "send_reliable_submission", "XRPLReliableSubmissionException", ] diff --git a/xrpl/asyncio/transaction/ledger.py b/xrpl/asyncio/transaction/ledger.py deleted file mode 100644 index 0a9b3bea9..000000000 --- a/xrpl/asyncio/transaction/ledger.py +++ /dev/null @@ -1,57 +0,0 @@ -"""High-level methods that fetch transaction information from the XRP Ledger.""" - -from typing import Optional - -from deprecated.sphinx import deprecated - -from xrpl.asyncio.clients import Client, XRPLRequestFailureException -from xrpl.models.requests import Tx -from xrpl.models.response import Response - - -@deprecated( - reason="Sending a Tx request directly is just as easy to use.", - version="1.8.0", -) -async def get_transaction_from_hash( - tx_hash: str, - client: Client, - binary: bool = False, - min_ledger: Optional[int] = None, - max_ledger: Optional[int] = None, -) -> Response: # pragma: no cover - """ - Given a transaction hash, fetch the corresponding transaction from the ledger. - - Args: - tx_hash: the transaction hash. - client: the network client used to communicate with a rippled node. - binary: If true, return transaction data and metadata as binary - serialized to hexadecimal strings. If false, return transaction data and - metadata as JSON. The default is false. - min_ledger: Use this with max_ledger to specify a range of up to - 1000 ledger indexes, starting with this ledger (inclusive). If the server - cannot find the transaction, it confirms whether it was able to search all - the ledgers in this range. - max_ledger: Use this with min_ledger to specify a range of up to - 1000 ledger indexes, ending with this ledger (inclusive). If the server - cannot find the transaction, it confirms whether it was able to search - all the ledgers in the requested range. - - Returns: - The Response object containing the transaction info. - - Raises: - XRPLRequestFailureException: if the transaction fails. - """ - response = await client._request_impl( - Tx( - transaction=tx_hash, - binary=binary, - max_ledger=max_ledger, - min_ledger=min_ledger, - ) - ) - if not response.is_successful(): - raise XRPLRequestFailureException(response.result) - return response diff --git a/xrpl/asyncio/transaction/main.py b/xrpl/asyncio/transaction/main.py index c50d299b5..4ee7a4c3b 100644 --- a/xrpl/asyncio/transaction/main.py +++ b/xrpl/asyncio/transaction/main.py @@ -11,7 +11,7 @@ from xrpl.core.addresscodec import is_valid_xaddress, xaddress_to_classic_address from xrpl.core.binarycodec import encode, encode_for_multisigning, encode_for_signing from xrpl.core.keypairs.main import sign as keypairs_sign -from xrpl.models.requests import ServerState, SubmitOnly +from xrpl.models.requests import ServerInfo, ServerState, SubmitOnly from xrpl.models.response import Response from xrpl.models.transactions import EscrowFinish from xrpl.models.transactions.transaction import Signer, Transaction @@ -23,15 +23,22 @@ from xrpl.wallet.main import Wallet _LEDGER_OFFSET: Final[int] = 20 - +# Sidechains are expected to have network IDs above this. +# Networks with ID above this restricted number are expected to specify an +# accurate NetworkID field in every transaction to that chain to prevent replay attacks. +# Mainnet and testnet are exceptions. +# More context: https://github.com/XRPLF/rippled/pull/4370 +_RESTRICTED_NETWORKS = 1024 +_REQUIRED_NETWORKID_VERSION = "1.11.0" +_HOOKS_TESTNET_ID = 21338 # TODO: make this dynamic based on the current ledger fee _OWNER_RESERVE_FEE: Final[int] = int(xrp_to_drops(2)) async def sign_and_submit( transaction: Transaction, - wallet: Wallet, client: Client, + wallet: Wallet, autofill: bool = True, check_fee: bool = True, ) -> Response: @@ -41,8 +48,8 @@ async def sign_and_submit( Args: transaction: the transaction to be signed and submitted. - wallet: the wallet with which to sign the transaction. client: the network client with which to submit the transaction. + wallet: the wallet with which to sign the transaction. autofill: whether to autofill the relevant fields. Defaults to True. check_fee: whether to check if the fee is higher than the expected transaction type fee. Defaults to True. @@ -51,19 +58,21 @@ async def sign_and_submit( The response from the ledger. """ if autofill: - transaction = await autofill_and_sign(transaction, wallet, client, check_fee) + transaction = await autofill_and_sign(transaction, client, wallet, check_fee) else: - transaction = await sign(transaction, wallet, check_fee) + if check_fee: + await _check_fee(transaction, client) + transaction = sign(transaction, wallet) return await submit(transaction, client) -safe_sign_and_submit_transaction = sign_and_submit - - -async def sign( +# Even though this is synchronous - this is here because it used to be async in +# xrpl-py 1.0, and we decided it wasn't worth breaking people's imports to move +# It to a central location as part of the xrpl-py 2.0 changes. It is aliased in +# The synchronous half of the library as well. +def sign( transaction: Transaction, wallet: Wallet, - check_fee: bool = True, multisign: bool = False, ) -> Transaction: """ @@ -72,8 +81,6 @@ async def sign( Args: transaction: the transaction to be signed. wallet: the wallet with which to sign the transaction. - check_fee: whether to check if the fee is higher than the expected transaction - type fee. Defaults to True. multisign: whether to sign the transaction for a multisignature transaction. Returns: @@ -84,7 +91,7 @@ async def sign( bytes.fromhex( encode_for_multisigning( transaction.to_xrpl(), - wallet.classic_address, + wallet.address, ) ), wallet.private_key, @@ -92,15 +99,13 @@ async def sign( tx_dict = transaction.to_dict() tx_dict["signers"] = [ Signer( - account=wallet.classic_address, + account=wallet.address, txn_signature=signature, signing_pub_key=wallet.public_key, ) ] return Transaction.from_dict(tx_dict) - if check_fee: - await _check_fee(transaction) transaction_json = _prepare_transaction(transaction, wallet) serialized_for_signing = encode_for_signing(transaction_json) serialized_bytes = bytes.fromhex(serialized_for_signing) @@ -109,13 +114,10 @@ async def sign( return Transaction.from_xrpl(transaction_json) -safe_sign_transaction = sign - - async def autofill_and_sign( transaction: Transaction, - wallet: Wallet, client: Client, + wallet: Wallet, check_fee: bool = True, ) -> Transaction: """ @@ -138,10 +140,7 @@ async def autofill_and_sign( if check_fee: await _check_fee(transaction, client) - return await sign(await autofill(transaction, client), wallet, False) - - -safe_sign_and_autofill_transaction = autofill_and_sign + return sign(await autofill(transaction, client), wallet, multisign=False) async def submit( @@ -176,9 +175,6 @@ async def submit( raise XRPLRequestFailureException(response.result) -submit_transaction = submit - - def _prepare_transaction( transaction: Transaction, wallet: Wallet, @@ -235,6 +231,10 @@ async def autofill( The autofilled transaction. """ transaction_json = transaction.to_dict() + if not client.network_id: + await _get_network_id_and_build_version(client) + if "network_id" not in transaction_json and _tx_needs_networkID(client): + transaction_json["network_id"] = client.network_id if "sequence" not in transaction_json: sequence = await get_next_valid_seq_number(transaction_json["account"], client) transaction_json["sequence"] = sequence @@ -248,6 +248,105 @@ async def autofill( return Transaction.from_dict(transaction_json) +async def _get_network_id_and_build_version(client: Client) -> None: + """ + Get the network id and build version of the connected server. + + Args: + client: The network client to use to send the request. + + Raises: + XRPLRequestFailureException: if the rippled API call fails. + """ + response = await client._request_impl(ServerInfo()) + if response.is_successful(): + if "network_id" in response.result["info"]: + client.network_id = response.result["info"]["network_id"] + if not client.build_version and "build_version" in response.result["info"]: + client.build_version = response.result["info"]["build_version"] + return + + raise XRPLRequestFailureException(response.result) + + +def _tx_needs_networkID(client: Client) -> bool: + """ + Determines whether the transactions required network ID to be valid. + Transaction needs networkID if later than restricted ID and either + the network is hooks testnet or build version is >= 1.11.0. + More context: https://github.com/XRPLF/rippled/pull/4370 + + Args: + client (Client): The network client to use to send the request. + + Returns: + bool: whether the transactions required network ID to be valid + """ + if client.network_id and client.network_id > _RESTRICTED_NETWORKS: + # TODO: remove the buildVersion logic when 1.11.0 is out and widely used. + # Issue: https://github.com/XRPLF/xrpl-py/issues/595 + if ( + client.build_version + and _is_not_later_rippled_version( + _REQUIRED_NETWORKID_VERSION, client.build_version + ) + ) or client.network_id == _HOOKS_TESTNET_ID: + return True + return False + + +def _is_not_later_rippled_version(source: str, target: str) -> bool: + """ + Determines whether the source version is not a later release than the + target version. + + Args: + source: the source rippled version. + target: the target rippled version. + + Returns: + bool: true if source is earlier, false otherwise. + """ + if source == target: + return True + source_decomp = source.split(".") + target_decomp = target.split(".") + source_major, source_minor = int(source_decomp[0]), int(source_decomp[1]) + target_major, target_minor = int(target_decomp[0]), int(target_decomp[1]) + + # Compare major version + if source_major != target_major: + return source_major < target_major + + # Compare minor version + if source_minor != target_minor: + return source_minor < target_minor + + source_patch = source_decomp[2].split("-") + target_patch = target_decomp[2].split("-") + source_patch_version = int(source_patch[0]) + target_patch_version = int(target_patch[0]) + + # Compare patch version + if source_patch_version != target_patch_version: + return source_patch_version < target_patch_version + + # Compare release version + if len(source_patch) != len(target_patch): + return len(source_patch) > len(target_patch) + + if len(source_patch) == 2: + # Compare release types + if not source_patch[1][0].startswith(target_patch[1][0]): + return source_patch[1] < target_patch[1] + # Compare beta versions + if source_patch[1].startswith("b"): + return int(source_patch[1][1:]) < int(target_patch[1][1:]) + # Compare rc versions + return int(source_patch[1][2:]) < int(target_patch[1][2:]) + return False + + def _validate_account_xaddress( json: Dict[str, Any], account_field: str, tag_field: str ) -> None: @@ -299,20 +398,26 @@ def transaction_json_to_binary_codec_form(dictionary: Dict[str, Any]) -> Dict[st return model_transaction_to_binary_codec(dictionary) -async def _check_fee(transaction: Transaction, client: Optional[Client] = None) -> None: +async def _check_fee( + transaction: Transaction, + client: Optional[Client] = None, + signers_count: Optional[int] = None, +) -> None: """ Checks if the Transaction fee is lower than the expected Transaction type fee. Args: transaction: The transaction to check. client: Client instance to use to look up network load + signers_count: the expected number of signers for this transaction. + Only used for multisigned transactions. Raises: XRPLException: if the transaction fee is higher than the expected fee. """ expected_fee = max( xrp_to_drops(0.1), # a fee that is obviously too high - await _calculate_fee_per_transaction_type(transaction, client), + await _calculate_fee_per_transaction_type(transaction, client, signers_count), ) if transaction.fee and int(transaction.fee) > int(expected_fee): diff --git a/xrpl/asyncio/transaction/reliable_submission.py b/xrpl/asyncio/transaction/reliable_submission.py index ffd08d843..ddf202408 100644 --- a/xrpl/asyncio/transaction/reliable_submission.py +++ b/xrpl/asyncio/transaction/reliable_submission.py @@ -74,7 +74,7 @@ async def _wait_for_final_transaction_outcome( ) -async def send_reliable_submission( +async def _send_reliable_submission( transaction: Transaction, client: Client, *, fail_hard: bool = False ) -> Response: """ @@ -165,9 +165,9 @@ async def _get_signed_tx( transaction = await _autofill(transaction, client) if transaction.signers: - return await sign(transaction, wallet, True, True) + return sign(transaction, wallet, multisign=True) - return await sign(transaction, wallet, True, False) + return sign(transaction, wallet, multisign=False) async def submit_and_wait( @@ -206,6 +206,6 @@ async def submit_and_wait( signed_transaction = await _get_signed_tx( transaction, client, wallet, check_fee, autofill ) - return await send_reliable_submission( + return await _send_reliable_submission( signed_transaction, client, fail_hard=fail_hard ) diff --git a/xrpl/asyncio/wallet/wallet_generation.py b/xrpl/asyncio/wallet/wallet_generation.py index 515e0d30c..ea53106f2 100644 --- a/xrpl/asyncio/wallet/wallet_generation.py +++ b/xrpl/asyncio/wallet/wallet_generation.py @@ -34,6 +34,8 @@ async def generate_faucet_wallet( wallet: Optional[Wallet] = None, debug: bool = False, faucet_host: Optional[str] = None, + usage_context: Optional[str] = None, + user_agent: Optional[str] = "xrpl-py", ) -> Wallet: """ Generates a random wallet and funds it using the XRPL Testnet Faucet. @@ -44,6 +46,12 @@ async def generate_faucet_wallet( debug: Whether to print debug information as it creates the wallet. faucet_host: A custom host to use for funding a wallet. In environments other than devnet and testnet, this parameter is required. + usage_context: The intended use case for the funding request + (for example, testing). This information will be included + in the json body of the HTTP request to the faucet. + user_agent: A string representing the user agent (software/ client used) + for the HTTP request. Default is "xrpl-py". + Returns: A Wallet on the testnet that contains some amount of XRP. @@ -60,7 +68,7 @@ async def generate_faucet_wallet( if wallet is None: wallet = Wallet.create() - address = wallet.classic_address + address = wallet.address # The faucet *can* be flakey... by printing info about this it's easier to # understand if tests are actually failing, or if it was just a faucet failure. if debug: @@ -69,7 +77,7 @@ async def generate_faucet_wallet( starting_balance = await _check_wallet_balance(address, client) # Ask the faucet to send funds to the given address - await _request_funding(faucet_url, address) + await _request_funding(faucet_url, address, usage_context, user_agent) # Wait for the faucet to fund our account or until timeout # Waits one second checks if balance has changed # If balance doesn't change it will attempt again until _TIMEOUT_SECONDS @@ -86,7 +94,6 @@ async def generate_faucet_wallet( else: # wallet has been funded, now the ledger needs to know the account exists next_seq_num = await _try_to_get_next_seq(address, client) if next_seq_num is not None: - wallet.sequence = next_seq_num return wallet raise XRPLFaucetException( @@ -143,9 +150,17 @@ async def _check_wallet_balance(address: str, client: Client) -> int: raise -async def _request_funding(url: str, address: str) -> None: +async def _request_funding( + url: str, + address: str, + usage_context: Optional[str] = None, + user_agent: Optional[str] = None, +) -> None: async with httpx.AsyncClient() as http_client: - response = await http_client.post(url=url, json={"destination": address}) + json_body = {"destination": address, "userAgent": user_agent} + if usage_context is not None: + json_body["usageContext"] = usage_context + response = await http_client.post(url=url, json=json_body) if not response.status_code == httpx.codes.OK: response.raise_for_status() diff --git a/xrpl/core/addresscodec/__init__.py b/xrpl/core/addresscodec/__init__.py index 4ee4fc250..0d3826e36 100644 --- a/xrpl/core/addresscodec/__init__.py +++ b/xrpl/core/addresscodec/__init__.py @@ -14,6 +14,7 @@ from xrpl.core.addresscodec.exceptions import XRPLAddressCodecException from xrpl.core.addresscodec.main import ( classic_address_to_xaddress, + ensure_classic_address, is_valid_xaddress, xaddress_to_classic_address, ) @@ -29,6 +30,7 @@ "encode_account_public_key", "encode_classic_address", "encode_node_public_key", + "ensure_classic_address", "is_valid_classic_address", "is_valid_xaddress", "SEED_LENGTH", diff --git a/xrpl/core/addresscodec/main.py b/xrpl/core/addresscodec/main.py index 34cbee673..0d0a4f68f 100644 --- a/xrpl/core/addresscodec/main.py +++ b/xrpl/core/addresscodec/main.py @@ -95,6 +95,37 @@ def xaddress_to_classic_address(xaddress: str) -> Tuple[str, Optional[int], bool return (classic_address, tag, is_test_network) +def ensure_classic_address(account: str) -> str: + """ + If an address is an X-Address, converts it to a classic address. + + Args: + account: A classic address or X-address. + + Returns: + The account's classic address + + Raises: + XRPLAddressCodecException: if the X-Address has an associated tag. + """ + if is_valid_xaddress(account): + classic_address, tag, _ = xaddress_to_classic_address(account) + + """ + Except for special cases, X-addresses used for requests must not + have an embedded tag. In other words, `tag` should be None. + """ + if tag is not None: + raise XRPLAddressCodecException( + "This command does not support the use of a tag. Use " + "an address without a tag" + ) + + return classic_address + + return account + + def _is_test_address(prefix: bytes) -> bool: """ Returns whether a decoded X-Address is a test address. diff --git a/xrpl/core/keypairs/ed25519.py b/xrpl/core/keypairs/ed25519.py index e9f6242c5..9b9b9ed3e 100644 --- a/xrpl/core/keypairs/ed25519.py +++ b/xrpl/core/keypairs/ed25519.py @@ -96,4 +96,6 @@ def _public_key_to_str(cls: Type[ED25519], key: ECPublicKey) -> str: @classmethod def _format_key(cls: Type[ED25519], keystr: str) -> str: + if len(keystr) < 64: + keystr = keystr.zfill(64) return (PREFIX + keystr).upper() diff --git a/xrpl/core/keypairs/main.py b/xrpl/core/keypairs/main.py index e213dd8d3..7eae73c52 100644 --- a/xrpl/core/keypairs/main.py +++ b/xrpl/core/keypairs/main.py @@ -29,19 +29,22 @@ def generate_seed( Generate a seed value that cryptographic keys can be derived from. Args: - entropy: Must be at least addresscodec.SEED_LENGTH bytes long and - will be truncated to that length - algorithm: CryptoAlgorithm to use for seed generation. The default is + entropy: Hexadecimal string that is addresscodec.SEED_LENGTH bytes long + algorithm: CryptoAlgorithm to use for seed generation. The default is :data:`CryptoAlgorithm.ED25519 `. Returns: A seed value that can be used to derive a key pair with the given - cryptographic algorithm. + cryptographic algorithm. + + Raises: + XRPLAddressCodecException: If entropy is not of length addresscodec.SEED_LENGTH, + this exception will be thrown in addresscodec.encode_seed. """ if entropy is None: parsed_entropy = token_bytes(addresscodec.SEED_LENGTH) else: - parsed_entropy = bytes(entropy, "UTF-8")[: addresscodec.SEED_LENGTH] + parsed_entropy = bytes.fromhex(entropy) return addresscodec.encode_seed(parsed_entropy, algorithm) diff --git a/xrpl/models/currencies/xrp.py b/xrpl/models/currencies/xrp.py index c1a75d655..82dd47746 100644 --- a/xrpl/models/currencies/xrp.py +++ b/xrpl/models/currencies/xrp.py @@ -61,7 +61,7 @@ def to_dict(self: XRP) -> Dict[str, Any]: """ return {**super().to_dict(), "currency": "XRP"} - def to_amount(self: XRP, value: Union[str, int]) -> str: + def to_amount(self: XRP, value: Union[str, int, float]) -> str: """ Converts value to XRP. @@ -71,7 +71,12 @@ def to_amount(self: XRP, value: Union[str, int]) -> str: Returns: A string representation of XRP amount. """ - return str(value) + # import needed here to avoid circular dependency + from xrpl.utils.xrp_conversions import xrp_to_drops + + if isinstance(value, str): + return xrp_to_drops(float(value)) + return xrp_to_drops(value) def __repr__(self: XRP) -> str: """ diff --git a/xrpl/models/flags.py b/xrpl/models/flags.py index 8cf3c86a3..484aae6eb 100644 --- a/xrpl/models/flags.py +++ b/xrpl/models/flags.py @@ -1,4 +1,4 @@ -"""All transacion flags and utils to build a list of ints from a FlagInterface""" +"""All transaction flags and utils to build a list of ints from a FlagInterface""" from typing import Dict, List, Union @@ -10,16 +10,12 @@ TX_FLAGS: Dict[str, Dict[str, int]] = { "AccountSet": { - "asf_account_tx_id": 0x00000005, - "asf_authorized_nftoken_minter": 0x0000000A, - "asf_default_ripple": 0x00000008, - "asf_deposit_auth": 0x00000009, - "asf_disable_master": 0x00000004, - "asf_disallow_xrp": 0x00000003, - "asf_global_freeze": 0x00000007, - "asf_no_freeze": 0x00000006, - "asf_require_auth": 0x00000002, - "asf_require_dest": 0x00000001, + "tf_require_dest_tag": 0x00010000, + "tf_optional_dest_tag": 0x00020000, + "tf_require_auth": 0x00040000, + "tf_optional_auth": 0x00080000, + "tf_disallow_xrp": 0x00100000, + "tf_allow_xrp": 0x00200000, }, "AMMDeposit": { "tf_lp_token": 0x00010000, diff --git a/xrpl/models/requests/account_channels.py b/xrpl/models/requests/account_channels.py index acb7e46fe..506183141 100644 --- a/xrpl/models/requests/account_channels.py +++ b/xrpl/models/requests/account_channels.py @@ -8,16 +8,16 @@ `See account_channels `_ """ from dataclasses import dataclass, field -from typing import Any, Optional, Union +from typing import Any, Optional -from xrpl.models.requests.request import Request, RequestMethod +from xrpl.models.requests.request import LookupByLedgerRequest, Request, RequestMethod from xrpl.models.required import REQUIRED from xrpl.models.utils import require_kwargs_on_init @require_kwargs_on_init @dataclass(frozen=True) -class AccountChannels(Request): +class AccountChannels(Request, LookupByLedgerRequest): """ This request returns information about an account's Payment Channels. This includes only channels where the specified account is the channel's source, not the @@ -41,5 +41,3 @@ class AccountChannels(Request): # marker data shape is actually undefined in the spec, up to the # implementation of an individual server marker: Optional[Any] = None - ledger_hash: Optional[str] = None - ledger_index: Optional[Union[str, int]] = None diff --git a/xrpl/models/requests/account_currencies.py b/xrpl/models/requests/account_currencies.py index b742886fa..213cdc492 100644 --- a/xrpl/models/requests/account_currencies.py +++ b/xrpl/models/requests/account_currencies.py @@ -8,16 +8,15 @@ `See account_currencies `_ """ from dataclasses import dataclass, field -from typing import Optional, Union -from xrpl.models.requests.request import Request, RequestMethod +from xrpl.models.requests.request import LookupByLedgerRequest, Request, RequestMethod from xrpl.models.required import REQUIRED from xrpl.models.utils import require_kwargs_on_init @require_kwargs_on_init @dataclass(frozen=True) -class AccountCurrencies(Request): +class AccountCurrencies(Request, LookupByLedgerRequest): """ This request retrieves a list of currencies that an account can send or receive, based on its trust lines. @@ -34,8 +33,5 @@ class AccountCurrencies(Request): :meta hide-value: """ - - ledger_hash: Optional[str] = None - ledger_index: Optional[Union[str, int]] = None method: RequestMethod = field(default=RequestMethod.ACCOUNT_CURRENCIES, init=False) strict: bool = False diff --git a/xrpl/models/requests/account_info.py b/xrpl/models/requests/account_info.py index 641035b3b..4e2859dd0 100644 --- a/xrpl/models/requests/account_info.py +++ b/xrpl/models/requests/account_info.py @@ -7,16 +7,15 @@ `See account_info `_ """ from dataclasses import dataclass, field -from typing import Optional, Union -from xrpl.models.requests.request import Request, RequestMethod +from xrpl.models.requests.request import LookupByLedgerRequest, Request, RequestMethod from xrpl.models.required import REQUIRED from xrpl.models.utils import require_kwargs_on_init @require_kwargs_on_init @dataclass(frozen=True) -class AccountInfo(Request): +class AccountInfo(Request, LookupByLedgerRequest): """ This request retrieves information about an account, its activity, and its XRP balance. @@ -33,8 +32,6 @@ class AccountInfo(Request): :meta hide-value: """ - ledger_hash: Optional[str] = None - ledger_index: Optional[Union[str, int]] = None method: RequestMethod = field(default=RequestMethod.ACCOUNT_INFO, init=False) queue: bool = False signer_lists: bool = False diff --git a/xrpl/models/requests/account_lines.py b/xrpl/models/requests/account_lines.py index 8e8808378..99e0aa7fd 100644 --- a/xrpl/models/requests/account_lines.py +++ b/xrpl/models/requests/account_lines.py @@ -6,16 +6,16 @@ `See account_lines `_ """ from dataclasses import dataclass, field -from typing import Any, Optional, Union +from typing import Any, Optional -from xrpl.models.requests.request import Request, RequestMethod +from xrpl.models.requests.request import LookupByLedgerRequest, Request, RequestMethod from xrpl.models.required import REQUIRED from xrpl.models.utils import require_kwargs_on_init @require_kwargs_on_init @dataclass(frozen=True) -class AccountLines(Request): +class AccountLines(Request, LookupByLedgerRequest): """ This request returns information about an account's trust lines, including balances in all non-XRP currencies and assets. All information retrieved is relative to a @@ -31,8 +31,6 @@ class AccountLines(Request): :meta hide-value: """ - ledger_hash: Optional[str] = None - ledger_index: Optional[Union[str, int]] = None method: RequestMethod = field(default=RequestMethod.ACCOUNT_LINES, init=False) peer: Optional[str] = None limit: Optional[int] = None diff --git a/xrpl/models/requests/account_nfts.py b/xrpl/models/requests/account_nfts.py index 398add290..2941b3a9e 100644 --- a/xrpl/models/requests/account_nfts.py +++ b/xrpl/models/requests/account_nfts.py @@ -2,14 +2,14 @@ from dataclasses import dataclass, field from typing import Any, Optional -from xrpl.models.requests.request import Request, RequestMethod +from xrpl.models.requests.request import LookupByLedgerRequest, Request, RequestMethod from xrpl.models.required import REQUIRED from xrpl.models.utils import require_kwargs_on_init @require_kwargs_on_init @dataclass(frozen=True) -class AccountNFTs(Request): +class AccountNFTs(Request, LookupByLedgerRequest): """ This method retrieves all of the NFTs currently owned by the specified account. diff --git a/xrpl/models/requests/account_objects.py b/xrpl/models/requests/account_objects.py index 39097a012..510ae08e8 100644 --- a/xrpl/models/requests/account_objects.py +++ b/xrpl/models/requests/account_objects.py @@ -8,9 +8,9 @@ """ from dataclasses import dataclass, field from enum import Enum -from typing import Any, Optional, Union +from typing import Any, Optional -from xrpl.models.requests.request import Request, RequestMethod +from xrpl.models.requests.request import LookupByLedgerRequest, Request, RequestMethod from xrpl.models.required import REQUIRED from xrpl.models.utils import require_kwargs_on_init @@ -34,7 +34,7 @@ class AccountObjectType(str, Enum): @require_kwargs_on_init @dataclass(frozen=True) -class AccountObjects(Request): +class AccountObjects(Request, LookupByLedgerRequest): """ This request returns the raw ledger format for all objects owned by an account. @@ -51,8 +51,6 @@ class AccountObjects(Request): :meta hide-value: """ - ledger_hash: Optional[str] = None - ledger_index: Optional[Union[str, int]] = None method: RequestMethod = field(default=RequestMethod.ACCOUNT_OBJECTS, init=False) type: Optional[AccountObjectType] = None deletion_blockers_only: bool = False diff --git a/xrpl/models/requests/account_offers.py b/xrpl/models/requests/account_offers.py index 92a186e23..2b6977c59 100644 --- a/xrpl/models/requests/account_offers.py +++ b/xrpl/models/requests/account_offers.py @@ -5,16 +5,16 @@ `See account_offers `_ """ from dataclasses import dataclass, field -from typing import Any, Optional, Union +from typing import Any, Optional -from xrpl.models.requests.request import Request, RequestMethod +from xrpl.models.requests.request import LookupByLedgerRequest, Request, RequestMethod from xrpl.models.required import REQUIRED from xrpl.models.utils import require_kwargs_on_init @require_kwargs_on_init @dataclass(frozen=True) -class AccountOffers(Request): +class AccountOffers(Request, LookupByLedgerRequest): """ This request retrieves a list of offers made by a given account that are outstanding as of a particular ledger version. @@ -29,8 +29,6 @@ class AccountOffers(Request): :meta hide-value: """ - ledger_hash: Optional[str] = None - ledger_index: Optional[Union[str, int]] = None method: RequestMethod = field(default=RequestMethod.ACCOUNT_OFFERS, init=False) limit: Optional[int] = None # marker data shape is actually undefined in the spec, up to the diff --git a/xrpl/models/requests/account_tx.py b/xrpl/models/requests/account_tx.py index 85f54c7cc..9b9c300ff 100644 --- a/xrpl/models/requests/account_tx.py +++ b/xrpl/models/requests/account_tx.py @@ -5,16 +5,16 @@ `See account_tx `_ """ from dataclasses import dataclass, field -from typing import Any, Optional, Union +from typing import Any, Optional -from xrpl.models.requests.request import Request, RequestMethod +from xrpl.models.requests.request import LookupByLedgerRequest, Request, RequestMethod from xrpl.models.required import REQUIRED from xrpl.models.utils import require_kwargs_on_init @require_kwargs_on_init @dataclass(frozen=True) -class AccountTx(Request): +class AccountTx(Request, LookupByLedgerRequest): """ This request retrieves from the ledger a list of transactions that involved the specified account. @@ -29,8 +29,6 @@ class AccountTx(Request): :meta hide-value: """ - ledger_hash: Optional[str] = None - ledger_index: Optional[Union[str, int]] = None method: RequestMethod = field(default=RequestMethod.ACCOUNT_TX, init=False) ledger_index_min: Optional[int] = None ledger_index_max: Optional[int] = None diff --git a/xrpl/models/requests/book_offers.py b/xrpl/models/requests/book_offers.py index cdd8815bf..d64daf937 100644 --- a/xrpl/models/requests/book_offers.py +++ b/xrpl/models/requests/book_offers.py @@ -3,17 +3,17 @@ as the order book, between two currencies. """ from dataclasses import dataclass, field -from typing import Optional, Union +from typing import Optional from xrpl.models.currencies import Currency -from xrpl.models.requests.request import Request, RequestMethod +from xrpl.models.requests.request import LookupByLedgerRequest, Request, RequestMethod from xrpl.models.required import REQUIRED from xrpl.models.utils import require_kwargs_on_init @require_kwargs_on_init @dataclass(frozen=True) -class BookOffers(Request): +class BookOffers(Request, LookupByLedgerRequest): """ The book_offers method retrieves a list of offers, also known as the order book, between two currencies. @@ -34,7 +34,5 @@ class BookOffers(Request): """ method: RequestMethod = field(default=RequestMethod.BOOK_OFFERS, init=False) - ledger_hash: Optional[str] = None - ledger_index: Optional[Union[str, int]] = None limit: Optional[int] = None taker: Optional[str] = None diff --git a/xrpl/models/requests/deposit_authorized.py b/xrpl/models/requests/deposit_authorized.py index 40aff141a..c3490098a 100644 --- a/xrpl/models/requests/deposit_authorized.py +++ b/xrpl/models/requests/deposit_authorized.py @@ -5,16 +5,15 @@ authorization to deliver money to your account. """ from dataclasses import dataclass, field -from typing import Optional -from xrpl.models.requests.request import Request, RequestMethod +from xrpl.models.requests.request import LookupByLedgerRequest, Request, RequestMethod from xrpl.models.required import REQUIRED from xrpl.models.utils import require_kwargs_on_init @require_kwargs_on_init @dataclass(frozen=True) -class DepositAuthorized(Request): +class DepositAuthorized(Request, LookupByLedgerRequest): """ The deposit_authorized command indicates whether one account is authorized to send payments directly to another. See @@ -37,5 +36,3 @@ class DepositAuthorized(Request): """ method: RequestMethod = field(default=RequestMethod.DEPOSIT_AUTHORIZED, init=False) - ledger_hash: Optional[str] = None - ledger_index: Optional[str] = None diff --git a/xrpl/models/requests/gateway_balances.py b/xrpl/models/requests/gateway_balances.py index c45ad4a51..825f459cb 100644 --- a/xrpl/models/requests/gateway_balances.py +++ b/xrpl/models/requests/gateway_balances.py @@ -7,14 +7,14 @@ from dataclasses import dataclass, field from typing import List, Optional, Union -from xrpl.models.requests.request import Request, RequestMethod +from xrpl.models.requests.request import LookupByLedgerRequest, Request, RequestMethod from xrpl.models.required import REQUIRED from xrpl.models.utils import require_kwargs_on_init @require_kwargs_on_init @dataclass(frozen=True) -class GatewayBalances(Request): +class GatewayBalances(Request, LookupByLedgerRequest): """ This request calculates the total balances issued by a given account, optionally excluding amounts held by operational addresses. @@ -29,8 +29,6 @@ class GatewayBalances(Request): :meta hide-value: """ - ledger_hash: Optional[str] = None - ledger_index: Optional[Union[str, int]] = None method: RequestMethod = field(default=RequestMethod.GATEWAY_BALANCES, init=False) strict: bool = False hotwallet: Optional[Union[str, List[str]]] = None diff --git a/xrpl/models/requests/ledger.py b/xrpl/models/requests/ledger.py index 56dd9fa6d..148ee11ec 100644 --- a/xrpl/models/requests/ledger.py +++ b/xrpl/models/requests/ledger.py @@ -3,24 +3,22 @@ `See ledger `_ """ from dataclasses import dataclass, field -from typing import Optional, Union +from typing import Optional from xrpl.models.requests.ledger_entry import LedgerEntryType -from xrpl.models.requests.request import Request, RequestMethod +from xrpl.models.requests.request import LookupByLedgerRequest, Request, RequestMethod from xrpl.models.utils import require_kwargs_on_init @require_kwargs_on_init @dataclass(frozen=True) -class Ledger(Request): +class Ledger(Request, LookupByLedgerRequest): """ Retrieve information about the public ledger. `See ledger `_ """ method: RequestMethod = field(default=RequestMethod.LEDGER, init=False) - ledger_hash: Optional[str] = None - ledger_index: Optional[Union[str, int]] = None full: bool = False accounts: bool = False transactions: bool = False diff --git a/xrpl/models/requests/ledger_data.py b/xrpl/models/requests/ledger_data.py index b034fc290..551d8aacb 100644 --- a/xrpl/models/requests/ledger_data.py +++ b/xrpl/models/requests/ledger_data.py @@ -6,16 +6,16 @@ `See ledger data `_ """ from dataclasses import dataclass, field -from typing import Any, Optional, Union +from typing import Any, Optional from xrpl.models.requests.ledger_entry import LedgerEntryType -from xrpl.models.requests.request import Request, RequestMethod +from xrpl.models.requests.request import LookupByLedgerRequest, Request, RequestMethod from xrpl.models.utils import require_kwargs_on_init @require_kwargs_on_init @dataclass(frozen=True) -class LedgerData(Request): +class LedgerData(Request, LookupByLedgerRequest): """ The ledger_data method retrieves contents of the specified ledger. You can iterate through @@ -25,8 +25,6 @@ class LedgerData(Request): """ method: RequestMethod = field(default=RequestMethod.LEDGER_DATA, init=False) - ledger_hash: Optional[str] = None - ledger_index: Optional[Union[str, int]] = None binary: bool = False limit: Optional[int] = None # marker data shape is actually undefined in the spec, up to the diff --git a/xrpl/models/requests/ledger_entry.py b/xrpl/models/requests/ledger_entry.py index ac848f07b..7db72dee6 100644 --- a/xrpl/models/requests/ledger_entry.py +++ b/xrpl/models/requests/ledger_entry.py @@ -13,7 +13,7 @@ from xrpl.models.base_model import BaseModel from xrpl.models.currencies import Currency -from xrpl.models.requests.request import Request, RequestMethod +from xrpl.models.requests.request import LookupByLedgerRequest, Request, RequestMethod from xrpl.models.required import REQUIRED from xrpl.models.utils import require_kwargs_on_init @@ -199,7 +199,7 @@ class XChainCreateAccountClaimID(BaseModel): @require_kwargs_on_init @dataclass(frozen=True) -class LedgerEntry(Request): +class LedgerEntry(Request, LookupByLedgerRequest): """ The ledger_entry method returns a single ledger object from the XRP Ledger in its raw format. @@ -226,8 +226,8 @@ class LedgerEntry(Request): ] = None binary: bool = False - ledger_hash: Optional[str] = None - ledger_index: Optional[Union[str, int]] = None + nft_page: Optional[str] = None + """Must be the object ID of the NFToken page, as hexadecimal""" def _get_errors(self: LedgerEntry) -> Dict[str, str]: errors = super()._get_errors() diff --git a/xrpl/models/requests/nft_buy_offers.py b/xrpl/models/requests/nft_buy_offers.py index 27d744db0..74a4168be 100644 --- a/xrpl/models/requests/nft_buy_offers.py +++ b/xrpl/models/requests/nft_buy_offers.py @@ -4,14 +4,14 @@ """ from dataclasses import dataclass, field -from xrpl.models.requests.request import Request, RequestMethod +from xrpl.models.requests.request import LookupByLedgerRequest, Request, RequestMethod from xrpl.models.required import REQUIRED from xrpl.models.utils import require_kwargs_on_init @require_kwargs_on_init @dataclass(frozen=True) -class NFTBuyOffers(Request): +class NFTBuyOffers(Request, LookupByLedgerRequest): """ The `nft_buy_offers` method retrieves all of buy offers for the specified NFToken. diff --git a/xrpl/models/requests/nft_history.py b/xrpl/models/requests/nft_history.py index d9fd92aeb..50ff06b79 100644 --- a/xrpl/models/requests/nft_history.py +++ b/xrpl/models/requests/nft_history.py @@ -3,16 +3,16 @@ specified NFToken. """ from dataclasses import dataclass, field -from typing import Any, Optional, Union +from typing import Any, Optional -from xrpl.models.requests.request import Request, RequestMethod +from xrpl.models.requests.request import LookupByLedgerRequest, Request, RequestMethod from xrpl.models.required import REQUIRED from xrpl.models.utils import require_kwargs_on_init @require_kwargs_on_init @dataclass(frozen=True) -class NFTHistory(Request): +class NFTHistory(Request, LookupByLedgerRequest): """ The `nft_history` method retreives a list of transactions that involved the specified NFToken. @@ -27,8 +27,6 @@ class NFTHistory(Request): :meta hide-value: """ - ledger_hash: Optional[str] = None - ledger_index: Optional[Union[str, int]] = None ledger_index_min: Optional[int] = None ledger_index_max: Optional[int] = None binary: bool = False diff --git a/xrpl/models/requests/nft_info.py b/xrpl/models/requests/nft_info.py index bf8d74be3..87966970e 100644 --- a/xrpl/models/requests/nft_info.py +++ b/xrpl/models/requests/nft_info.py @@ -3,16 +3,15 @@ NFToken """ from dataclasses import dataclass, field -from typing import Optional, Union -from xrpl.models.requests.request import Request, RequestMethod +from xrpl.models.requests.request import LookupByLedgerRequest, Request, RequestMethod from xrpl.models.required import REQUIRED from xrpl.models.utils import require_kwargs_on_init @require_kwargs_on_init @dataclass(frozen=True) -class NFTInfo(Request): +class NFTInfo(Request, LookupByLedgerRequest): """ The `nft_info` method retrieves all the information about the NFToken @@ -26,6 +25,3 @@ class NFTInfo(Request): :meta hide-value: """ - - ledger_hash: Optional[str] = None - ledger_index: Optional[Union[str, int]] = None diff --git a/xrpl/models/requests/nft_sell_offers.py b/xrpl/models/requests/nft_sell_offers.py index 7b652fba7..0e3f6442f 100644 --- a/xrpl/models/requests/nft_sell_offers.py +++ b/xrpl/models/requests/nft_sell_offers.py @@ -4,14 +4,14 @@ """ from dataclasses import dataclass, field -from xrpl.models.requests.request import Request, RequestMethod +from xrpl.models.requests.request import LookupByLedgerRequest, Request, RequestMethod from xrpl.models.required import REQUIRED from xrpl.models.utils import require_kwargs_on_init @require_kwargs_on_init @dataclass(frozen=True) -class NFTSellOffers(Request): +class NFTSellOffers(Request, LookupByLedgerRequest): """ The `nft_sell_offers` method retrieves all of sell offers for the specified NFToken. diff --git a/xrpl/models/requests/no_ripple_check.py b/xrpl/models/requests/no_ripple_check.py index e424b0cd7..eec92f305 100644 --- a/xrpl/models/requests/no_ripple_check.py +++ b/xrpl/models/requests/no_ripple_check.py @@ -7,9 +7,9 @@ """ from dataclasses import dataclass, field from enum import Enum -from typing import Optional, Union +from typing import Optional -from xrpl.models.requests.request import Request, RequestMethod +from xrpl.models.requests.request import LookupByLedgerRequest, Request, RequestMethod from xrpl.models.required import REQUIRED from xrpl.models.utils import require_kwargs_on_init @@ -23,7 +23,7 @@ class NoRippleCheckRole(str, Enum): @require_kwargs_on_init @dataclass(frozen=True) -class NoRippleCheck(Request): +class NoRippleCheck(Request, LookupByLedgerRequest): """ This request provides a quick way to check the status of the Default Ripple field for an account and the No Ripple flag of its trust lines, compared with the @@ -39,8 +39,6 @@ class NoRippleCheck(Request): :meta hide-value: """ - ledger_hash: Optional[str] = None - ledger_index: Optional[Union[str, int]] = None method: RequestMethod = field(default=RequestMethod.NO_RIPPLE_CHECK, init=False) role: NoRippleCheckRole = REQUIRED # type: ignore """ diff --git a/xrpl/models/requests/request.py b/xrpl/models/requests/request.py index 16c9ffacc..a91c4e2b8 100644 --- a/xrpl/models/requests/request.py +++ b/xrpl/models/requests/request.py @@ -86,7 +86,6 @@ class RequestMethod(str, Enum): R = TypeVar("R", bound="Request") -@require_kwargs_on_init @dataclass(frozen=True) class Request(BaseModel): """ @@ -183,3 +182,18 @@ def to_dict(self: Request) -> Dict[str, Any]: # we need to override this because method is using ``field`` # which will not include the value in the object's __dict__ return {**super().to_dict(), "method": self.method.value} + + +@require_kwargs_on_init +@dataclass(frozen=True) +class LookupByLedgerRequest: + """Represents requests that need specifying an instance of the ledger""" + + ledger_hash: Optional[str] = None + """ + A 20-byte hex string for the ledger version to use. + """ + ledger_index: Optional[Union[str, int]] = None + """ + The ledger index of the ledger to use, or a shortcut string. + """ diff --git a/xrpl/models/requests/ripple_path_find.py b/xrpl/models/requests/ripple_path_find.py index 29682fed4..cbc8849a7 100644 --- a/xrpl/models/requests/ripple_path_find.py +++ b/xrpl/models/requests/ripple_path_find.py @@ -12,18 +12,18 @@ the paths returned by this method are, in fact, the best paths. """ from dataclasses import dataclass, field -from typing import List, Optional, Union +from typing import List, Optional from xrpl.models.amounts import Amount from xrpl.models.currencies import Currency -from xrpl.models.requests.request import Request, RequestMethod +from xrpl.models.requests.request import LookupByLedgerRequest, Request, RequestMethod from xrpl.models.required import REQUIRED from xrpl.models.utils import require_kwargs_on_init @require_kwargs_on_init @dataclass(frozen=True) -class RipplePathFind(Request): +class RipplePathFind(Request, LookupByLedgerRequest): """ The ripple_path_find method is a simplified version of the path_find method that provides a single response with a payment @@ -62,5 +62,3 @@ class RipplePathFind(Request): method: RequestMethod = field(default=RequestMethod.RIPPLE_PATH_FIND, init=False) send_max: Optional[Amount] = None source_currencies: Optional[List[Currency]] = None - ledger_hash: Optional[str] = None - ledger_index: Optional[Union[str, int]] = None diff --git a/xrpl/models/requests/transaction_entry.py b/xrpl/models/requests/transaction_entry.py index e3aa5dac1..044082fb0 100644 --- a/xrpl/models/requests/transaction_entry.py +++ b/xrpl/models/requests/transaction_entry.py @@ -9,16 +9,15 @@ from __future__ import annotations from dataclasses import dataclass, field -from typing import Optional, Union -from xrpl.models.requests.request import Request, RequestMethod +from xrpl.models.requests.request import LookupByLedgerRequest, Request, RequestMethod from xrpl.models.required import REQUIRED from xrpl.models.utils import require_kwargs_on_init @require_kwargs_on_init @dataclass(frozen=True) -class TransactionEntry(Request): +class TransactionEntry(Request, LookupByLedgerRequest): """ The transaction_entry method retrieves information on a single transaction from a specific ledger version. (The tx method, by contrast, searches all ledgers for the @@ -28,8 +27,6 @@ class TransactionEntry(Request): """ method: RequestMethod = field(default=RequestMethod.TRANSACTION_ENTRY, init=False) - ledger_hash: Optional[str] = None - ledger_index: Optional[Union[str, int]] = None tx_hash: str = REQUIRED # type: ignore """ This field is required. diff --git a/xrpl/models/transactions/__init__.py b/xrpl/models/transactions/__init__.py index 3a6a8112a..7a05a461a 100644 --- a/xrpl/models/transactions/__init__.py +++ b/xrpl/models/transactions/__init__.py @@ -6,6 +6,7 @@ from xrpl.models.transactions.account_delete import AccountDelete from xrpl.models.transactions.account_set import ( AccountSet, + AccountSetAsfFlag, AccountSetFlag, AccountSetFlagInterface, ) @@ -88,6 +89,7 @@ __all__ = [ "AccountDelete", "AccountSet", + "AccountSetAsfFlag", "AccountSetFlag", "AccountSetFlagInterface", "AMMBid", @@ -122,13 +124,13 @@ "OfferCreateFlag", "OfferCreateFlagInterface", "Payment", - "PaymentFlag", - "PaymentFlagInterface", "PaymentChannelClaim", "PaymentChannelClaimFlag", "PaymentChannelClaimFlagInterface", "PaymentChannelCreate", "PaymentChannelFund", + "PaymentFlag", + "PaymentFlagInterface", "SetRegularKey", "Signer", "SignerEntry", diff --git a/xrpl/models/transactions/account_set.py b/xrpl/models/transactions/account_set.py index 6ea67d435..094f7a129 100644 --- a/xrpl/models/transactions/account_set.py +++ b/xrpl/models/transactions/account_set.py @@ -23,8 +23,10 @@ _MAX_DOMAIN_LENGTH: Final[int] = 256 -class AccountSetFlag(int, Enum): +class AccountSetAsfFlag(int, Enum): """ + Enum for AccountSet Flags. + There are several options which can be either enabled or disabled for an account. Account options are represented by different types of flags depending on the situation. The AccountSet transaction type has several "AccountSet Flags" (prefixed @@ -32,7 +34,7 @@ class AccountSetFlag(int, Enum): an option when passed as the ClearFlag parameter. This enum represents those options. - `See AccountSet Flags `_ + `See AccountSet asf Flags `_ """ ASF_ACCOUNT_TXN_ID = 5 @@ -102,32 +104,61 @@ class AccountSetFlag(int, Enum): """Disallow other accounts from creating Trustlines directed at this account.""" +class AccountSetFlag(int, Enum): + """ + Enum for AccountSet Transaction Flags. + + Transactions of the AccountSet type support additional values in the Flags field. + This enum represents those options. + + `See AccountSet tf Flags `_ + """ + + TF_REQUIRE_DEST_TAG = 0x00010000 + """ + The same as SetFlag: asfRequireDest. + """ + + TF_OPTIONAL_DEST_TAG = 0x00020000 + """ + The same as ClearFlag: asfRequireDest. + """ + + TF_REQUIRE_AUTH = 0x00040000 + """ + The same as SetFlag: asfRequireAuth. + """ + + TF_OPTIONAL_AUTH = 0x00080000 + """ + The same as ClearFlag: asfRequireAuth. + """ + + TF_DISALLOW_XRP = 0x00100000 + """ + The same as SetFlag: asfDisallowXRP. + """ + + TF_ALLOW_XRP = 0x00200000 + """ + The same as ClearFlag: asfDisallowXRP. + """ + + class AccountSetFlagInterface(FlagInterface): """ - There are several options which can be either enabled or disabled for an account. - Account options are represented by different types of flags depending on the - situation. The AccountSet transaction type has several "AccountSet Flags" (prefixed - `asf`) that can enable an option when passed as the SetFlag parameter, or disable - an option when passed as the ClearFlag parameter. This TypedDict represents those - options. + Transactions of the AccountSet type support additional values in the Flags field. + This TypedDict represents those options. - `See AccountSet Flags `_ + `See AccountSet tf Flags `_ """ - ASF_ACCOUNT_TXN_ID: bool - ASF_DEFAULT_RIPPLE: bool - ASF_DEPOSIT_AUTH: bool - ASF_DISABLE_MASTER: bool - ASF_DISALLOW_XRP: bool - ASF_GLOBAL_FREEZE: bool - ASF_NO_FREEZE: bool - ASF_REQUIRE_AUTH: bool - ASF_REQUIRE_DEST: bool - ASF_AUTHORIZED_NFTOKEN_MINTER: bool - ASF_DISABLE_INCOMING_NFTOKEN_OFFER: bool - ASF_DISABLE_INCOMING_CHECK: bool - ASF_DISABLE_INCOMING_PAYCHAN: bool - ASF_DISABLE_INCOMING_TRUSTLINE: bool + TF_REQUIRE_DEST_TAG: bool + TF_OPTIONAL_DEST_TAG: bool + TF_REQUIRE_AUTH: bool + TF_OPTIONAL_AUTH: bool + TF_DISALLOW_XRP: bool + TF_ALLOW_XRP: bool @require_kwargs_on_init @@ -138,7 +169,7 @@ class AccountSet(Transaction): which modifies the properties of an account in the XRP Ledger. """ - clear_flag: Optional[int] = None + clear_flag: Optional[AccountSetAsfFlag] = None """ Disable a specific `AccountSet Flag `_ @@ -159,7 +190,7 @@ class AccountSet(Transaction): message_key: Optional[str] = None """Set a public key for sending encrypted messages to this account.""" - set_flag: Optional[int] = None + set_flag: Optional[AccountSetAsfFlag] = None """ Enable a specific `AccountSet Flag `_ @@ -183,7 +214,7 @@ class AccountSet(Transaction): """ Sets an alternate account that is allowed to mint NFTokens on this account's behalf using NFTokenMint's `Issuer` field. If set, you must - also set the AccountSetFlag.ASF_AUTHORIZED_NFTOKEN_MINTER flag. + also set the AccountSetAsfFlag.ASF_AUTHORIZED_NFTOKEN_MINTER flag. """ transaction_type: TransactionType = field( @@ -240,25 +271,28 @@ def _get_clear_flag_error(self: AccountSet) -> Optional[str]: def _get_nftoken_minter_error(self: AccountSet) -> Optional[str]: if ( - self.set_flag != AccountSetFlag.ASF_AUTHORIZED_NFTOKEN_MINTER + self.set_flag != AccountSetAsfFlag.ASF_AUTHORIZED_NFTOKEN_MINTER and self.nftoken_minter is not None ): return ( "Will not set the minter unless " - "AccountSetFlag.ASF_AUTHORIZED_NFTOKEN_MINTER is set" + "AccountSetAsfFlag.ASF_AUTHORIZED_NFTOKEN_MINTER is set" ) if ( - self.set_flag == AccountSetFlag.ASF_AUTHORIZED_NFTOKEN_MINTER + self.set_flag == AccountSetAsfFlag.ASF_AUTHORIZED_NFTOKEN_MINTER and self.nftoken_minter is None ): - return "\ - Must be present if AccountSetFlag.ASF_AUTHORIZED_NFTOKEN_MINTER is set" + return ( + "Must be present if AccountSetAsfFlag.ASF_AUTHORIZED_NFTOKEN_MINTER " + "is set" + ) if ( - self.clear_flag == AccountSetFlag.ASF_AUTHORIZED_NFTOKEN_MINTER + self.clear_flag == AccountSetAsfFlag.ASF_AUTHORIZED_NFTOKEN_MINTER and self.nftoken_minter is not None ): return ( - "Must not be present if AccountSetFlag.ASF_AUTHORIZED_NFTOKEN_MINTER " - "is unset using clear_flag" + "Must not be present if " + "AccountSetAsfFlag.ASF_AUTHORIZED_NFTOKEN_MINTER is unset " + "using clear_flag" ) return None diff --git a/xrpl/models/transactions/transaction.py b/xrpl/models/transactions/transaction.py index d1964df2e..207cdc16a 100644 --- a/xrpl/models/transactions/transaction.py +++ b/xrpl/models/transactions/transaction.py @@ -260,6 +260,9 @@ class Transaction(BaseModel): transaction. Automatically added during signing. """ + network_id: Optional[int] = None + """The network id of the transaction.""" + def _get_errors(self: Transaction) -> Dict[str, str]: errors = super()._get_errors() if self.ticket_sequence is not None and ( diff --git a/xrpl/transaction/__init__.py b/xrpl/transaction/__init__.py index ec37eb987..0e04ea191 100644 --- a/xrpl/transaction/__init__.py +++ b/xrpl/transaction/__init__.py @@ -3,38 +3,24 @@ XRPLReliableSubmissionException, transaction_json_to_binary_codec_form, ) -from xrpl.transaction.ledger import get_transaction_from_hash from xrpl.transaction.main import ( autofill, autofill_and_sign, - safe_sign_and_autofill_transaction, - safe_sign_and_submit_transaction, - safe_sign_transaction, sign, sign_and_submit, submit, - submit_transaction, ) from xrpl.transaction.multisign import multisign -from xrpl.transaction.reliable_submission import ( - send_reliable_submission, - submit_and_wait, -) +from xrpl.transaction.reliable_submission import submit_and_wait __all__ = [ "autofill", "autofill_and_sign", - "get_transaction_from_hash", - "safe_sign_transaction", - "safe_sign_and_autofill_transaction", - "safe_sign_and_submit_transaction", "sign", "sign_and_submit", "submit", "submit_and_wait", - "submit_transaction", "transaction_json_to_binary_codec_form", - "send_reliable_submission", "multisign", "XRPLReliableSubmissionException", ] diff --git a/xrpl/transaction/ledger.py b/xrpl/transaction/ledger.py deleted file mode 100644 index 3a30b13c8..000000000 --- a/xrpl/transaction/ledger.py +++ /dev/null @@ -1,56 +0,0 @@ -"""High-level methods that fetch transaction information from the XRP Ledger.""" - -import asyncio -from typing import Optional - -from deprecated.sphinx import deprecated - -from xrpl.asyncio.transaction import ledger -from xrpl.clients.sync_client import SyncClient -from xrpl.models.response import Response - - -@deprecated( - reason="Sending a Tx request directly is just as easy to use.", - version="1.8.0", -) -def get_transaction_from_hash( - tx_hash: str, - client: SyncClient, - binary: bool = False, - min_ledger: Optional[int] = None, - max_ledger: Optional[int] = None, -) -> Response: # pragma: no cover - """ - Given a transaction hash, fetch the corresponding transaction from the ledger. - - Args: - tx_hash: the transaction hash. - client: the network client used to communicate with a rippled node. - binary: If true, return transaction data and metadata as binary - serialized to hexadecimal strings. If false, return transaction data and - metadata as JSON. The default is false. - min_ledger: Use this with max_ledger to specify a range of up to - 1000 ledger indexes, starting with this ledger (inclusive). If the server - cannot find the transaction, it confirms whether it was able to search all - the ledgers in this range. - max_ledger: Use this with min_ledger to specify a range of up to - 1000 ledger indexes, ending with this ledger (inclusive). If the server - cannot find the transaction, it confirms whether it was able to search - all the ledgers in the requested range. - - Returns: - The Response object containing the transaction info. - - Raises: - XRPLRequestFailureException: if the transaction fails. - """ - return asyncio.run( - ledger.get_transaction_from_hash( - tx_hash, - client, - binary, - min_ledger, - max_ledger, - ) - ) diff --git a/xrpl/transaction/main.py b/xrpl/transaction/main.py index 3b59aca57..99f1ff43e 100644 --- a/xrpl/transaction/main.py +++ b/xrpl/transaction/main.py @@ -11,8 +11,8 @@ def sign_and_submit( transaction: Transaction, - wallet: Wallet, client: SyncClient, + wallet: Wallet, autofill: bool = True, check_fee: bool = True, ) -> Response: @@ -22,8 +22,8 @@ def sign_and_submit( Args: transaction: the transaction to be signed and submitted. - wallet: the wallet with which to sign the transaction. client: the network client with which to submit the transaction. + wallet: the wallet with which to sign the transaction. autofill: whether to autofill the relevant fields. Defaults to True. check_fee: whether to check if the fee is higher than the expected transaction type fee. Defaults to True. @@ -34,17 +34,14 @@ def sign_and_submit( return asyncio.run( main.sign_and_submit( transaction, - wallet, client, + wallet, autofill, check_fee, ) ) -safe_sign_and_submit_transaction = sign_and_submit - - def submit( transaction: Transaction, client: SyncClient, @@ -76,45 +73,13 @@ def submit( ) -submit_transaction = submit - - -def sign( - transaction: Transaction, - wallet: Wallet, - check_fee: bool = True, - multisign: bool = False, -) -> Transaction: - """ - Signs a transaction locally, without trusting external rippled nodes. - - Args: - transaction: the transaction to be signed. - wallet: the wallet with which to sign the transaction. - check_fee: whether to check if the fee is higher than the expected transaction - type fee. Defaults to True. - multisign: whether to sign the transaction for a multisignature transaction. - - Returns: - The signed transaction. - """ - return asyncio.run( - main.sign( - transaction, - wallet, - check_fee, - multisign, - ) - ) - - -safe_sign_transaction = sign +sign = main.sign def autofill_and_sign( transaction: Transaction, - wallet: Wallet, client: SyncClient, + wallet: Wallet, check_fee: bool = True, ) -> Transaction: """ @@ -123,8 +88,8 @@ def autofill_and_sign( Args: transaction: the transaction to be signed. - wallet: the wallet with which to sign the transaction. client: a network client. + wallet: the wallet with which to sign the transaction. check_fee: whether to check if the fee is higher than the expected transaction type fee. Defaults to True. @@ -134,16 +99,13 @@ def autofill_and_sign( return asyncio.run( main.autofill_and_sign( transaction, - wallet, client, + wallet, check_fee, ) ) -safe_sign_and_autofill_transaction = autofill_and_sign - - def autofill( transaction: Transaction, client: SyncClient, signers_count: Optional[int] = None ) -> Transaction: diff --git a/xrpl/transaction/multisign.py b/xrpl/transaction/multisign.py index cef308886..b6b1af2e4 100644 --- a/xrpl/transaction/multisign.py +++ b/xrpl/transaction/multisign.py @@ -1,6 +1,7 @@ """Multisign transaction methods with XRPL transactions.""" from typing import List +from xrpl.core.addresscodec import decode_classic_address from xrpl.models.transactions.transaction import Signer, Transaction @@ -28,5 +29,6 @@ def multisign(transaction: Transaction, tx_list: List[Transaction]) -> Transacti ) for decoded_tx_signer in decoded_tx_signers ] + tx_dict["signers"].sort(key=lambda signer: decode_classic_address(signer.account)) return Transaction.from_dict(tx_dict) diff --git a/xrpl/transaction/reliable_submission.py b/xrpl/transaction/reliable_submission.py index d42372c18..cfd27a11f 100644 --- a/xrpl/transaction/reliable_submission.py +++ b/xrpl/transaction/reliable_submission.py @@ -3,9 +3,6 @@ import asyncio from typing import Optional -from xrpl.asyncio.transaction import ( - send_reliable_submission as async_send_reliable_submission, -) from xrpl.asyncio.transaction import submit_and_wait as async_submit_and_wait from xrpl.clients.sync_client import SyncClient from xrpl.models.response import Response @@ -13,38 +10,6 @@ from xrpl.wallet.main import Wallet -def send_reliable_submission( - transaction: Transaction, - client: SyncClient, - *, - fail_hard: bool = False, -) -> Response: - """ - Submits a transaction and verifies that it has been included in a validated ledger - (or has errored/will not be included for some reason). - - `See Reliable Transaction Submission - `_ - - Note: This cannot be used with a standalone rippled node, because ledgers do not - close automatically. - - Args: - transaction: the signed transaction to submit to the ledger. Requires a - `last_ledger_sequence` param. - client: the network client used to submit the transaction to a rippled node. - fail_hard: an optional boolean. If True, and the transaction fails for - the initial server, do not retry or relay the transaction to other - servers. Defaults to False. - - Returns: - The response from a validated ledger. - """ - return asyncio.run( - async_send_reliable_submission(transaction, client, fail_hard=fail_hard) - ) - - def submit_and_wait( transaction: Transaction, client: SyncClient, diff --git a/xrpl/wallet/main.py b/xrpl/wallet/main.py index 7c5c6a3ed..34f9c1699 100644 --- a/xrpl/wallet/main.py +++ b/xrpl/wallet/main.py @@ -2,10 +2,10 @@ from __future__ import annotations -from typing import Optional, Type +from typing import List, Optional, Type -from xrpl.constants import CryptoAlgorithm -from xrpl.core.addresscodec import classic_address_to_xaddress +from xrpl.constants import CryptoAlgorithm, XRPLException +from xrpl.core.addresscodec import classic_address_to_xaddress, ensure_classic_address from xrpl.core.keypairs import derive_classic_address, derive_keypair, generate_seed @@ -16,21 +16,46 @@ class Wallet: details. """ + @property + def address(self: Wallet) -> str: + """ + The XRPL address that publicly identifies this wallet, + as a base58 string. This is the same value as the `classic_address`. + """ # noqa: DAR201 + return self._address + + # TODO: Just alias classic_address once mypy has resolved this issue: + # https://github.com/python/mypy/issues/6700 + @property + def classic_address(self: Wallet) -> str: + """ + `classic_address` is the same as `address`. It is called `classic_address` to + differentiate it from the x-address standard, which encodes the network, + destination tag, and XRPL address into a single value. + It's also a base58 string. + """ # noqa: DAR201 + return self._address + def __init__( self: Wallet, - seed: str, - sequence: int, + public_key: str, + private_key: str, *, + master_address: Optional[str] = None, + seed: Optional[str] = None, algorithm: Optional[CryptoAlgorithm] = None, ) -> None: """ Generate a new Wallet. Args: - seed: The seed from which the public and private keys are derived. - sequence: The next sequence number for the account. + public_key: The public key for the account. + private_key: The private key used for signing transactions for the account. + master_address: Include if a Wallet uses a Regular Key Pair. This sets the + address that this wallet corresponds to. The default is `None`. + seed: The seed used to derive the account keys. The default is `None`. algorithm: The algorithm used to encode the keys. Inferred from the seed if - not included. + not included """ self.seed = seed """ @@ -39,7 +64,7 @@ def __init__( """ if algorithm is None: - if self.seed.startswith("sEd"): + if seed is not None and seed.startswith("sEd"): wallet_algorithm = CryptoAlgorithm.ED25519 else: wallet_algorithm = CryptoAlgorithm.SECP256K1 @@ -51,47 +76,166 @@ def __init__( The algorithm that is used to convert the seed into its public/private keypair. """ - pk, sk = derive_keypair(self.seed, algorithm=algorithm) - self.public_key = pk + self.public_key = public_key """ The public key that is used to identify this wallet's signatures, as a hexadecimal string. """ - self.private_key = sk + self.private_key = private_key """ The private key that is used to create signatures, as a hexadecimal string. MUST be kept secret! """ - self.classic_address = derive_classic_address(self.public_key) - """The address that publicly identifies this wallet, as a base58 string.""" - - self.sequence = sequence - """ - The next available sequence number to use for transactions from this - wallet. - Must be updated by the user. Increments on the ledger with every successful - transaction submission, and stays the same with every failed transaction - submission. - """ + self._address = ( + ensure_classic_address(master_address) + if master_address is not None + else derive_classic_address(self.public_key) + ) + """Internal variable for classic_address. Use classic_address instead.""" @classmethod def create( - cls: Type[Wallet], crypto_algorithm: CryptoAlgorithm = CryptoAlgorithm.ED25519 + cls: Type[Wallet], algorithm: CryptoAlgorithm = CryptoAlgorithm.ED25519 ) -> Wallet: """ Generates a new seed and Wallet. Args: - crypto_algorithm: The key-generation algorithm to use when generating the - seed. The default is Ed25519. + algorithm: The key-generation algorithm to use when generating the seed. + The default is `ED25519`. Returns: The wallet that is generated from the given seed. """ - seed = generate_seed(algorithm=crypto_algorithm) - return cls(seed, sequence=0, algorithm=crypto_algorithm) + seed = generate_seed(algorithm=algorithm) + return Wallet.from_seed(seed, algorithm=algorithm) + + @classmethod + def from_seed( + cls: Type[Wallet], + seed: str, + *, + master_address: Optional[str] = None, + algorithm: CryptoAlgorithm = CryptoAlgorithm.ED25519, + ) -> Wallet: + """ + Generates a new Wallet from seed (secret). + + Args: + seed: The seed (secret) used to derive the account keys. + master_address: Include if a Wallet uses a Regular Key Pair. This sets the + address that this wallet corresponds to. The default is `None`. + algorithm: The key-generation algorithm to use when generating the seed. + The default is `ED25519`. + + Returns: + The wallet that is generated from the given secret. + """ + public_key, private_key = derive_keypair(seed, algorithm=algorithm) + return cls( + public_key, + private_key, + master_address=master_address, + seed=seed, + algorithm=algorithm, + ) + + from_secret = from_seed + + @classmethod + def from_entropy( + cls: Type[Wallet], + entropy: str, + *, + master_address: Optional[str] = None, + algorithm: CryptoAlgorithm = CryptoAlgorithm.ED25519, + ) -> Wallet: + """ + Generates a new Wallet from entropy (hexadecimal string of random numbers). + + Args: + entropy: A hexadecimal string of random numbers to generate a seed used + to derive a wallet. + master_address: Include if a Wallet uses a Regular Key Pair. This sets the + address that this wallet corresponds to. The default is `None`. + algorithm: The key-generation algorithm to use when generating the seed. + The default is `ED25519`. + + Returns: + The wallet that is generated from the given entropy. + + Raises: + XRPLException: If passed in entropy is not a bytestring. + """ + if entropy is not None and len(entropy) != 32: + raise XRPLException( + "Entropy must be a 16-byte hexadecimal string of random numbers." + ) + + seed = generate_seed(entropy, algorithm) + return Wallet.from_seed( + seed, master_address=master_address, algorithm=algorithm + ) + + @classmethod + def from_secret_numbers( + self: Type[Wallet], + secret_numbers: List[str] | str, + *, + master_address: Optional[str] = None, + algorithm: CryptoAlgorithm = CryptoAlgorithm.SECP256K1, + ) -> Wallet: + """ + Generates a new Wallet from secret numbers. + + Args: + secret_numbers: A string (whitespace delimited) or string array consisting + of 8 times 6 numbers used to derive a wallet. + master_address: Include if a Wallet uses a Regular Key Pair. It must be + the master address of the account. The default is `None`. + algorithm: The digital signature algorithm to generate an address for. + The default is `SECP256K1 + `_ + (XUMM standard as of December 2022). + + Returns: + The wallet that is generated from the given secret numbers. + + Raises: + XRPLException: If the number of secret numbers is not 8. If the length of + any secret number is not 6. If the checksum of any secret number is + invalid. + """ + # Logic adapted from xrpl-secret-numbers secretToEntropy function + # https://github.com/WietseWind/xrpl-secret-numbers/blob/master/src/utils/index.ts + + parsed_secret_numbers = ( + secret_numbers.split() + if isinstance(secret_numbers, str) + else secret_numbers + ) + + if len(parsed_secret_numbers) != 8: + raise XRPLException("There must be 8 secret numbers.") + + entropy = "" + for i, secret_number in enumerate(parsed_secret_numbers): + no = int(secret_number[:5]) + checksum = int(secret_number[5:]) + + if len(secret_number) != 6: + raise XRPLException("Each secret number must be 6 digits long.") + if no * (i * 2 + 1) % 9 != checksum: + raise XRPLException(f"Checksum of secret number {i} is invalid.") + + hexed = hex(no)[2:].zfill(4) + entropy += hexed + + return Wallet.from_entropy( + entropy, master_address=master_address, algorithm=algorithm + ) def get_xaddress( self: Wallet, *, tag: Optional[int] = None, is_test: bool = False @@ -100,13 +244,14 @@ def get_xaddress( Returns the X-Address of the Wallet's account. Args: - tag: the destination tag of the address. Defaults to `None`. - is_test: whether the address corresponds to an address on the test network. + tag: The destination tag of the address. Defaults to `None`. + is_test: Whether the address corresponds to an address on the test network. + Defaults to `False`. Returns: The X-Address of the Wallet's account. """ - return classic_address_to_xaddress(self.classic_address, tag, is_test) + return classic_address_to_xaddress(self.address, tag, is_test) def __str__(self: Wallet) -> str: """ @@ -119,6 +264,6 @@ def __str__(self: Wallet) -> str: [ f"public_key: {self.public_key}", "private_key: -HIDDEN-", - f"classic_address: {self.classic_address}", + f"classic_address: {self.address}", ] ) diff --git a/xrpl/wallet/wallet_generation.py b/xrpl/wallet/wallet_generation.py index 8f24809a0..af9511962 100644 --- a/xrpl/wallet/wallet_generation.py +++ b/xrpl/wallet/wallet_generation.py @@ -12,6 +12,7 @@ def generate_faucet_wallet( wallet: Optional[Wallet] = None, debug: bool = False, faucet_host: Optional[str] = None, + usage_context: Optional[str] = None, ) -> Wallet: """ Generates a random wallet and funds it using the XRPL Testnet Faucet. @@ -22,6 +23,9 @@ def generate_faucet_wallet( debug: Whether to print debug information as it creates the wallet. faucet_host: A custom host to use for funding a wallet. In environments other than devnet and testnet, this parameter is required. + usage_context: The intended use case for the funding request + (for example, testing). This information will be included in json body + of the HTTP request to the faucet. Returns: A Wallet on the testnet that contains some amount of XRP. @@ -33,4 +37,6 @@ def generate_faucet_wallet( .. # noqa: DAR402 exception raised in private method """ - return asyncio.run(async_generate_faucet_wallet(client, wallet, debug, faucet_host)) + return asyncio.run( + async_generate_faucet_wallet(client, wallet, debug, faucet_host, usage_context) + )