diff --git a/.vscode/settings.json b/.vscode/settings.json index c6830efc1..ca5cdb549 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -4,13 +4,17 @@ "aiounittest", "altnet", "asyncio", + "autofilling", "autofills", "binarycodec", "Clawback", "isnumeric", "keypair", "keypairs", + "multiaccount", "multisign", + "multisigned", + "multisigning", "nftoken", "PATHSET", "rippletest", @@ -27,5 +31,8 @@ "source.organizeImports": "always" } }, - "isort.args": ["--profile", "black"] + "isort.args": [ + "--profile", + "black" + ] } diff --git a/CHANGELOG.md b/CHANGELOG.md index 7ccc4d3d2..db3ff3204 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,8 +17,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### BREAKING CHANGE: - Remove Python 3.7 support to fix dependency installation and use 3.8 as new default. +### Added +- Support for the `Batch` amendment (XLS-56d). + ### Fixed - Grab the FeeSettings values from the latest validated ledger. Remove hard-coded reference to 10 drops as the reference transaction cost. +- Handle autofilling better when multisigning transactions. +- Better typing for transaction-related helper functions. +- Better handling of `TicketSequence`. ## [3.0.0] - 2024-07-16 @@ -96,6 +102,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [2.0.0] - 2023-07-05 ### BREAKING CHANGE - The default signing algorithm in the `Wallet` was changed from secp256k1 to ed25519 + ### Added: - Wallet support for regular key compatibility - Added new ways of wallet generation: `from_seed`, `from_secret`, `from_entropy`, `from_secret_numbers` diff --git a/tests/integration/it_utils.py b/tests/integration/it_utils.py index b7713c861..b75e630a9 100644 --- a/tests/integration/it_utils.py +++ b/tests/integration/it_utils.py @@ -119,7 +119,9 @@ async def fund_wallet_async( destination=wallet.address, amount=FUNDING_AMOUNT, ) - await sign_and_submit_async(payment, client, MASTER_WALLET, check_fee=True) + await sign_and_submit_async( + payment, client, MASTER_WALLET, autofill=True, check_fee=True + ) await client.request(LEDGER_ACCEPT_REQUEST) diff --git a/tests/integration/sugar/test_transaction.py b/tests/integration/sugar/test_transaction.py index 278d71d05..4efc59e73 100644 --- a/tests/integration/sugar/test_transaction.py +++ b/tests/integration/sugar/test_transaction.py @@ -6,26 +6,33 @@ ) from tests.integration.reusable_values import DESTINATION as DESTINATION_WALLET from tests.integration.reusable_values import WALLET +from xrpl.asyncio.account import get_next_valid_seq_number from xrpl.asyncio.ledger import get_fee, get_latest_validated_ledger_sequence from xrpl.asyncio.transaction import ( XRPLReliableSubmissionException, autofill, autofill_and_sign, sign, + sign_and_submit, ) 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 ( - _calculate_fee_per_transaction_type, - sign_and_submit, -) +from xrpl.asyncio.transaction.main import _calculate_fee_per_transaction_type from xrpl.clients import XRPLRequestFailureException from xrpl.core.addresscodec import classic_address_to_xaddress from xrpl.core.binarycodec.main import encode from xrpl.models.amounts.issued_currency_amount import IssuedCurrencyAmount from xrpl.models.exceptions import XRPLException from xrpl.models.requests import ServerState, Tx -from xrpl.models.transactions import AccountDelete, AccountSet, EscrowFinish, Payment +from xrpl.models.transactions import ( + AccountDelete, + AccountSet, + Batch, + DepositPreauth, + EscrowFinish, + Payment, + TransactionFlag, +) from xrpl.utils import xrp_to_drops ACCOUNT = WALLET.address @@ -254,6 +261,39 @@ async def test_networkid_reserved_networks(self, client): self.assertIsNone(transaction.network_id) self.assertEqual(client.network_id, 1) + @test_async_and_sync( + globals(), + ["xrpl.transaction.autofill", "xrpl.account.get_next_valid_seq_number"], + ) + async def test_batch_autofill(self, client): + tx = Batch( + account="rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", + raw_transactions=[ + DepositPreauth( + account=WALLET.address, + authorize="rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", + ), + DepositPreauth( + account=WALLET.address, + authorize="rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", + ), + ], + ) + transaction = await autofill(tx, client) + self.assertEqual(len(transaction.transaction_ids), 2) + + sequence = await get_next_valid_seq_number(WALLET.address, client) + for i in range(len(transaction.raw_transactions)): + raw_tx = transaction.raw_transactions[i] + self.assertTrue(raw_tx.has_flag(TransactionFlag.TF_INNER_BATCH_TXN)) + self.assertEqual(raw_tx.sequence, sequence + i) + self.assertEqual(raw_tx.get_hash(), transaction.transaction_ids[i]) + self.assertIsNone(raw_tx.network_id) + self.assertIsNone(raw_tx.last_ledger_sequence) + self.assertEqual(raw_tx.fee, "0") + self.assertEqual(raw_tx.signing_pub_key, "") + self.assertEqual(raw_tx.txn_signature, "") + class TestSubmitAndWait(IntegrationTestCase): @test_async_and_sync( diff --git a/tests/integration/transactions/test_batch.py b/tests/integration/transactions/test_batch.py new file mode 100644 index 000000000..cc37d693d --- /dev/null +++ b/tests/integration/transactions/test_batch.py @@ -0,0 +1,26 @@ +from tests.integration.integration_test_case import IntegrationTestCase +from tests.integration.it_utils import ( + sign_and_reliable_submission_async, + test_async_and_sync, +) +from tests.integration.reusable_values import DESTINATION, WALLET +from xrpl.models import Batch, BatchFlag, Payment +from xrpl.models.response import ResponseStatus + + +class TestBatch(IntegrationTestCase): + @test_async_and_sync(globals()) + async def test_basic_functionality(self, client): + payment = Payment( + account=WALLET.address, + amount="1", + destination=DESTINATION.address, + ) + batch = Batch( + account=WALLET.address, + flags=BatchFlag.TF_ALL_OR_NOTHING, + raw_transactions=[payment, payment], + ) + response = await sign_and_reliable_submission_async(batch, WALLET, client) + self.assertEqual(response.status, ResponseStatus.SUCCESS) + self.assertEqual(response.result["engine_result"], "tesSUCCESS") diff --git a/tests/unit/core/binarycodec/test_main.py b/tests/unit/core/binarycodec/test_main.py index 6c6ead083..3dea3f50d 100644 --- a/tests/unit/core/binarycodec/test_main.py +++ b/tests/unit/core/binarycodec/test_main.py @@ -11,6 +11,7 @@ encode, encode_for_multisigning, encode_for_signing, + encode_for_signing_batch, encode_for_signing_claim, ) @@ -401,6 +402,32 @@ def test_claim(self): ) self.assertEqual(encode_for_signing_claim(json), expected) + def test_batch(self): + flags = 1 + transaction_ids = [ + "ABE4871E9083DF66727045D49DEEDD3A6F166EB7F8D1E92FE868F02E76B2C5CA", + "795AAC88B59E95C3497609749127E69F12958BC016C600C770AEEB1474C840B4", + ] + + json = {"flags": flags, "transaction_ids": transaction_ids} + actual = encode_for_signing_batch(json) + self.assertEqual( + actual, + "".join( + [ + # hash prefix + "42434800", + # flags + "00000001", + # transaction_ids length + "00000002", + # transaction_ids + "ABE4871E9083DF66727045D49DEEDD3A6F166EB7F8D1E92FE868F02E76B2C5CA", + "795AAC88B59E95C3497609749127E69F12958BC016C600C770AEEB1474C840B4", + ] + ), + ) + def test_multisig(self): signing_account = "rJZdUusLDtY9NEsGea7ijqhVrXv98rYBYN" multisig_json = {**signing_json, "SigningPubKey": ""} diff --git a/tests/unit/models/transactions/test_transaction.py b/tests/unit/models/transactions/test_transaction.py index dea0bb267..c1d11509c 100644 --- a/tests/unit/models/transactions/test_transaction.py +++ b/tests/unit/models/transactions/test_transaction.py @@ -1,8 +1,9 @@ from unittest import TestCase from xrpl.asyncio.transaction.main import sign +from xrpl.core.addresscodec.main import classic_address_to_xaddress from xrpl.models.exceptions import XRPLModelException -from xrpl.models.transactions import AccountSet, OfferCreate, Payment +from xrpl.models.transactions import AccountSet, DepositPreauth, OfferCreate, Payment from xrpl.models.transactions.transaction import Transaction from xrpl.models.transactions.types.transaction_type import TransactionType from xrpl.transaction.multisign import multisign @@ -158,12 +159,28 @@ def test_is_signed_for_multisigned_transaction(self): multisigned_tx = multisign(tx, [tx_1, tx_2]) self.assertTrue(multisigned_tx.is_signed()) + def test_multisigned_transaction_xaddress(self): + tx = DepositPreauth( + account=classic_address_to_xaddress(_WALLET.address, 1, False), + authorize=classic_address_to_xaddress(_ACCOUNT, 1, False), + ) + tx_1 = sign(tx, _FIRST_SIGNER, multisign=True) + tx_2 = sign(tx, _SECOND_SIGNER, multisign=True) + + for tx_signed in (tx_1, tx_2): + self.assertEqual(tx_signed.account, _WALLET.address) + self.assertEqual(tx_signed.source_tag, 1) + self.assertEqual(tx_signed.authorize, _ACCOUNT) + + multisigned_tx = multisign(tx, [tx_1, tx_2]) + self.assertTrue(multisigned_tx.is_signed()) + # test the usage of DeliverMax field in Payment transactions - def test_payment_txn_API_no_deliver_max(self): + def test_payment_txn_api_no_deliver_max(self): delivered_amount = "200000" payment_tx_json = { - "Account": "rGWTUVmm1fB5QUjMYn8KfnyrFNgDiD9H9e", - "Destination": "rw71Qs1UYQrSQ9hSgRohqNNQcyjCCfffkQ", + "Account": _WALLET.address, + "Destination": _ACCOUNT, "TransactionType": "Payment", "Amount": delivered_amount, "Fee": "15", @@ -175,11 +192,11 @@ def test_payment_txn_API_no_deliver_max(self): payment_txn = Payment.from_xrpl(payment_tx_json) self.assertEqual(delivered_amount, payment_txn.to_dict()["amount"]) - def test_payment_txn_API_no_amount(self): + def test_payment_txn_api_no_amount(self): delivered_amount = "200000" payment_tx_json = { - "Account": "rGWTUVmm1fB5QUjMYn8KfnyrFNgDiD9H9e", - "Destination": "rw71Qs1UYQrSQ9hSgRohqNNQcyjCCfffkQ", + "Account": _WALLET.address, + "Destination": _ACCOUNT, "TransactionType": "Payment", "DeliverMax": delivered_amount, "Fee": "15", @@ -191,10 +208,10 @@ def test_payment_txn_API_no_amount(self): payment_txn = Payment.from_xrpl(payment_tx_json) self.assertEqual(delivered_amount, payment_txn.to_dict()["amount"]) - def test_payment_txn_API_different_amount_and_deliver_max(self): + def test_payment_txn_api_different_amount_and_deliver_max(self): payment_tx_json = { - "Account": "rGWTUVmm1fB5QUjMYn8KfnyrFNgDiD9H9e", - "Destination": "rw71Qs1UYQrSQ9hSgRohqNNQcyjCCfffkQ", + "Account": _WALLET.address, + "Destination": _ACCOUNT, "TransactionType": "Payment", "DeliverMax": "200000", "Amount": "200010", @@ -207,11 +224,11 @@ def test_payment_txn_API_different_amount_and_deliver_max(self): with self.assertRaises(XRPLModelException): Payment.from_xrpl(payment_tx_json) - def test_payment_txn_API_identical_amount_and_deliver_max(self): + def test_payment_txn_api_identical_amount_and_deliver_max(self): delivered_amount = "200000" payment_tx_json = { - "Account": "rGWTUVmm1fB5QUjMYn8KfnyrFNgDiD9H9e", - "Destination": "rw71Qs1UYQrSQ9hSgRohqNNQcyjCCfffkQ", + "Account": _WALLET.address, + "Destination": _ACCOUNT, "TransactionType": "Payment", "DeliverMax": delivered_amount, "Amount": delivered_amount, diff --git a/tests/unit/transaction/__init__.py b/tests/unit/transaction/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/unit/transaction/test_batch_signers.py b/tests/unit/transaction/test_batch_signers.py new file mode 100644 index 000000000..6b4f4ec34 --- /dev/null +++ b/tests/unit/transaction/test_batch_signers.py @@ -0,0 +1,203 @@ +from unittest import TestCase + +from xrpl.constants import CryptoAlgorithm, XRPLException +from xrpl.core.binarycodec.main import decode +from xrpl.models.transactions import Batch +from xrpl.models.transactions.batch import BatchSigner +from xrpl.models.transactions.transaction import Transaction +from xrpl.transaction.batch_signers import ( + combine_batch_signers, + sign_multiaccount_batch, +) +from xrpl.wallet import Wallet + +secp_wallet = Wallet.from_seed( + "spkcsko6Ag3RbCSVXV2FJ8Pd4Zac1", + algorithm=CryptoAlgorithm.SECP256K1, +) +ed_wallet = Wallet.from_seed( + "spkcsko6Ag3RbCSVXV2FJ8Pd4Zac1", + algorithm=CryptoAlgorithm.ED25519, +) +submit_wallet = Wallet.from_seed( + "sEd7HmQFsoyj5TAm6d98gytM9LJA1MF", + algorithm=CryptoAlgorithm.ED25519, +) +other_wallet = Wallet.create() + + +class TestSignMultiAccountBatch(TestCase): + batch_tx = Batch.from_xrpl( + { + "Account": "rJCxK2hX9tDMzbnn3cg1GU2g19Kfmhzxkp", + "Flags": 1, + "RawTransactions": [ + { + "RawTransaction": { + "Account": "rJy554HmWFFJQGnRfZuoo8nV97XSMq77h7", + "Flags": 1073741824, + "Amount": "5000000", + "Destination": "rPMh7Pi9ct699iZUTWaytJUoHcJ7cgyziK", + "Fee": "0", + "NetworkID": 21336, + "Sequence": 0, + "SigningPubKey": "", + "TransactionType": "Payment", + }, + }, + { + "RawTransaction": { + "Account": "rPMh7Pi9ct699iZUTWaytJUoHcJ7cgyziK", + "Amount": "1000000", + "Flags": 1073741824, + "Destination": "rJCxK2hX9tDMzbnn3cg1GU2g19Kfmhzxkp", + "Fee": "0", + "NetworkID": 21336, + "Sequence": 0, + "SigningPubKey": "", + "TransactionType": "Payment", + }, + }, + ], + "TransactionType": "Batch", + "TransactionIDs": [ + "ABE4871E9083DF66727045D49DEEDD3A6F166EB7F8D1E92FE868F02E76B2C5CA", + "795AAC88B59E95C3497609749127E69F12958BC016C600C770AEEB1474C840B4", + ], + } + ) + + def test_secp_wallet(self): + result = sign_multiaccount_batch(secp_wallet, self.batch_tx) + expected = [ + BatchSigner( + account="rPMh7Pi9ct699iZUTWaytJUoHcJ7cgyziK", + signing_pub_key=( + "02691AC5AE1C4C333AE5DF8A93BDC495F0EEBFC6DB0DA7EB6EF80" + "8F3AFC006E3FE" + ), + txn_signature=( + "30450221008E595499C334127A23190F61FB9ADD8B8C501D543E379" + "45B11FABB66B097A6130220138C908E8C4929B47E994A46D611FAC1" + "7AB295CFB8D9E0828B32F2947B97394B" + ), + ) + ] + + self.assertIsNotNone(result.batch_signers) + self.assertEqual(result.batch_signers, expected) + + def test_ed_wallet(self): + result = sign_multiaccount_batch(ed_wallet, self.batch_tx) + expected = [ + BatchSigner( + account="rJy554HmWFFJQGnRfZuoo8nV97XSMq77h7", + signing_pub_key=( + "ED3CC3D14FD80C213BC92A98AFE13A405A030F845EDCFD5E39528" + "6A6E9E62BA638" + ), + txn_signature=( + "E3337EE8C746523B5F96BEBE1190164B8B384EE2DC99F327D95ABC1" + "4E27F3AE16CC00DA7D61FC535DBFF0ADA3AF06394F8A703EE952A14" + "1BD871B75166C5CD0A" + ), + ) + ] + + self.assertIsNotNone(result.batch_signers) + self.assertEqual(result.batch_signers, expected) + + def test_not_included_account(self): + with self.assertRaises(XRPLException): + sign_multiaccount_batch(other_wallet, self.batch_tx) + + +class TestCombineBatchSigners(TestCase): + batch_tx = Batch.from_xrpl( + { + "Account": "rJCxK2hX9tDMzbnn3cg1GU2g19Kfmhzxkp", + "Flags": 1, + "LastLedgerSequence": 14973, + "NetworkID": 21336, + "RawTransactions": [ + { + "RawTransaction": { + "Account": "rJy554HmWFFJQGnRfZuoo8nV97XSMq77h7", + "Amount": "5000000", + "Flags": 1073741824, + "Destination": "rPMh7Pi9ct699iZUTWaytJUoHcJ7cgyziK", + "Fee": "0", + "NetworkID": 21336, + "Sequence": 0, + "SigningPubKey": "", + "TransactionType": "Payment", + }, + }, + { + "RawTransaction": { + "Account": "rPMh7Pi9ct699iZUTWaytJUoHcJ7cgyziK", + "Amount": "1000000", + "Flags": 1073741824, + "Destination": "rJCxK2hX9tDMzbnn3cg1GU2g19Kfmhzxkp", + "Fee": "0", + "NetworkID": 21336, + "Sequence": 0, + "SigningPubKey": "", + "TransactionType": "Payment", + }, + }, + ], + "Sequence": 215, + "TransactionType": "Batch", + "TransactionIDs": [ + "ABE4871E9083DF66727045D49DEEDD3A6F166EB7F8D1E92FE868F02E76B2C5CA", + "795AAC88B59E95C3497609749127E69F12958BC016C600C770AEEB1474C840B4", + ], + } + ) + tx1 = sign_multiaccount_batch(ed_wallet, batch_tx) + tx2 = sign_multiaccount_batch(secp_wallet, batch_tx) + expected_valid = tx1.to_xrpl().get("BatchSigners", []) + tx2.to_xrpl().get( + "BatchSigners", [] + ) + + def test_valid(self): + result = combine_batch_signers([self.tx1, self.tx2]) + self.assertEqual(decode(result)["BatchSigners"], self.expected_valid) + + def test_valid_serialized(self): + result = combine_batch_signers([self.tx1.blob(), self.tx2.blob()]) + self.assertEqual(decode(result)["BatchSigners"], self.expected_valid) + + def test_valid_sorted(self): + result = combine_batch_signers([self.tx2, self.tx1]) + self.assertEqual(decode(result)["BatchSigners"], self.expected_valid) + + def test_remove_submitter_signature(self): + tx = Transaction.from_xrpl( + { + "Account": "rJCxK2hX9tDMzbnn3cg1GU2g19Kfmhzxkp", + "Amount": "1000000", + "Flags": 1073741824, + "Destination": "rPMh7Pi9ct699iZUTWaytJUoHcJ7cgyziK", + "Fee": "0", + "NetworkID": 21336, + "Sequence": 0, + "SigningPubKey": "", + "TransactionType": "Payment", + } + ) + original_dict = self.batch_tx.to_xrpl() + original_dict["RawTransactions"].append({"RawTransaction": tx.to_xrpl()}) + original_dict["TransactionIDs"].append(tx.get_hash()) + + batch_tx = Batch.from_xrpl(original_dict) + tx1 = sign_multiaccount_batch(ed_wallet, batch_tx) + tx2 = sign_multiaccount_batch(secp_wallet, batch_tx) + tx3 = sign_multiaccount_batch(submit_wallet, batch_tx) + + result = combine_batch_signers([tx1, tx2, tx3]) + expected_valid = tx1.to_xrpl().get("BatchSigners", []) + tx2.to_xrpl().get( + "BatchSigners", [] + ) + self.assertEqual(decode(result)["BatchSigners"], expected_valid) diff --git a/tools/generate_tx_models.py b/tools/generate_tx_models.py index 28b0dcbef..25bc2af8c 100644 --- a/tools/generate_tx_models.py +++ b/tools/generate_tx_models.py @@ -26,7 +26,7 @@ def _parse_rippled_source( folder: str, ) -> Tuple[Dict[str, List[str]], Dict[str, List[Tuple[str, ...]]]]: # Get SFields - sfield_cpp = _read_file(os.path.join(folder, "src/ripple/protocol/impl/SField.cpp")) + sfield_cpp = _read_file(os.path.join(folder, "src/libxrpl/protocol/SField.cpp")) sfield_hits = re.findall( r'^ *CONSTRUCT_[^\_]+_SFIELD *\( *[^,\n]*,[ \n]*"([^\"\n ]+)"[ \n]*,[ \n]*' + r"([^, \n]+)[ \n]*,[ \n]*([0-9]+)(,.*?(notSigning))?", @@ -37,7 +37,7 @@ def _parse_rippled_source( # Get TxFormats tx_formats_cpp = _read_file( - os.path.join(folder, "src/ripple/protocol/impl/TxFormats.cpp") + os.path.join(folder, "src/libxrpl/protocol/TxFormats.cpp") ) tx_formats_hits = re.findall( r"^ *add\(jss::([^\"\n, ]+),[ \n]*tt[A-Z_]+,[ \n]*{[ \n]*(({sf[A-Za-z0-9]+, " @@ -102,6 +102,7 @@ def _main( existing_library_txs = {m.value for m in TransactionType} | { m.value for m in PseudoTransactionType } + print(sorted(existing_library_txs)) for tx in tx_formats: if tx not in existing_library_txs: txs_to_add.append((tx, _key_to_json(tx))) @@ -130,7 +131,7 @@ def _generate_param_line(param: str, is_required: bool) -> str: param_lines.sort(key=lambda x: "REQUIRED" not in x) params = "\n".join(param_lines) model = f"""@require_kwargs_on_init -@dataclass(frozen=True, **KW_ONLY_DATACLASS) +@dataclass(frozen=True, **KW_ONLY_DATACLASS) class {tx}(Transaction): \"\"\"Represents a {tx} transaction.\"\"\" @@ -184,6 +185,8 @@ class {tx}(Transaction): if __name__ == "__main__": + if len(sys.argv) < 2: + print("Usage: poetry run python generate_tx_models.py path/to/rippled") folder = sys.argv[1] sfields, tx_formats = _parse_rippled_source(folder) _main(sfields, tx_formats) diff --git a/xrpl/asyncio/transaction/main.py b/xrpl/asyncio/transaction/main.py index 02eb55876..843acab7e 100644 --- a/xrpl/asyncio/transaction/main.py +++ b/xrpl/asyncio/transaction/main.py @@ -1,9 +1,9 @@ """High-level transaction methods with XRPL transactions.""" import math -from typing import Any, Dict, Optional, cast +from typing import Any, Dict, List, Optional, Tuple, cast -from typing_extensions import Final +from typing_extensions import Final, TypeVar from xrpl.asyncio.account import get_next_valid_seq_number from xrpl.asyncio.clients import Client, XRPLRequestFailureException @@ -13,16 +13,21 @@ 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.response import Response -from xrpl.models.transactions import EscrowFinish -from xrpl.models.transactions.transaction import Signer, Transaction +from xrpl.models import ( + Batch, + EscrowFinish, + Response, + ServerState, + SubmitOnly, + Transaction, + TransactionFlag, +) from xrpl.models.transactions.transaction import ( transaction_json_to_binary_codec_form as model_transaction_to_binary_codec, ) from xrpl.models.transactions.types.transaction_type import TransactionType from xrpl.utils import drops_to_xrp, xrp_to_drops -from xrpl.wallet.main import Wallet +from xrpl.wallet import Wallet _LEDGER_OFFSET: Final[int] = 20 # Sidechains are expected to have network IDs above this. @@ -33,6 +38,8 @@ _RESTRICTED_NETWORKS = 1024 _REQUIRED_NETWORKID_VERSION = "1.11.0" +T = TypeVar("T", bound=Transaction, default=Transaction) + async def sign_and_submit( transaction: Transaction, @@ -70,10 +77,10 @@ async def sign_and_submit( # 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, + transaction: T, wallet: Wallet, multisign: bool = False, -) -> Transaction: +) -> T: """ Signs a transaction locally, without trusting external rippled nodes. @@ -85,40 +92,42 @@ def sign( Returns: The signed transaction blob. """ + transaction_json = _prepare_transaction(transaction) if multisign: signature = keypairs_sign( bytes.fromhex( encode_for_multisigning( - transaction.to_xrpl(), + transaction_json, wallet.address, ) ), wallet.private_key, ) - tx_dict = transaction.to_dict() - tx_dict["signers"] = [ - Signer( - account=wallet.address, - txn_signature=signature, - signing_pub_key=wallet.public_key, - ) + transaction_json["Signers"] = [ + { + "Signer": { + "Account": wallet.address, + "TxnSignature": signature, + "SigningPubKey": wallet.public_key, + } + } ] - return Transaction.from_dict(tx_dict) + return cast(T, Transaction.from_xrpl(transaction_json)) - transaction_json = _prepare_transaction(transaction, wallet) + transaction_json["SigningPubKey"] = wallet.public_key serialized_for_signing = encode_for_signing(transaction_json) serialized_bytes = bytes.fromhex(serialized_for_signing) signature = keypairs_sign(serialized_bytes, wallet.private_key) transaction_json["TxnSignature"] = signature - return Transaction.from_xrpl(transaction_json) + return cast(T, Transaction.from_xrpl(transaction_json)) async def autofill_and_sign( - transaction: Transaction, + transaction: T, client: Client, wallet: Wallet, check_fee: bool = True, -) -> Transaction: +) -> T: """ Autofills relevant fields. Then, signs a transaction locally, without trusting external rippled nodes. @@ -174,10 +183,7 @@ async def submit( raise XRPLRequestFailureException(response.result) -def _prepare_transaction( - transaction: Transaction, - wallet: Wallet, -) -> Dict[str, Any]: +def _prepare_transaction(transaction: Transaction) -> Dict[str, Any]: """ Prepares a Transaction by converting it to a JSON-like dictionary, converting the field names to CamelCase. If a Client is provided, then it also autofills any @@ -185,17 +191,19 @@ def _prepare_transaction( Args: transaction: the Transaction to be prepared. - wallet: the wallet that will be used for signing. Returns: A JSON-like dictionary that is ready to be signed. Raises: XRPLException: if both LastLedgerSequence and `ledger_offset` are provided, or - if an address tag is provided that does not match the X-Address tag. + if an address tag is provided that does not match the X-Address tag, or if + attempting to directly sign a Batch inner transaction. """ + if transaction.has_flag(TransactionFlag.TF_INNER_BATCH_TXN): + raise XRPLException("Cannot directly sign a batch inner transaction.") + transaction_json = transaction.to_xrpl() - transaction_json["SigningPubKey"] = wallet.public_key _validate_account_xaddress(transaction_json, "Account", "SourceTag") if "Destination" in transaction_json: @@ -213,12 +221,12 @@ def _prepare_transaction( async def autofill( - transaction: Transaction, client: Client, signers_count: Optional[int] = None -) -> Transaction: + transaction: T, client: Client, signers_count: Optional[int] = None +) -> T: """ - Autofills fields in a transaction. This will set `sequence`, `fee`, and - `last_ledger_sequence` according to the current state of the server this Client is - connected to. It also converts all X-Addresses to classic addresses. + Autofills fields in a transaction. This will set all autofill-able fields according + to the current state of the server this Client is connected to. It also converts all + X-Addresses to classic addresses. Args: transaction: the transaction to be signed. @@ -226,6 +234,9 @@ async def autofill( signers_count: the expected number of signers for this transaction. Only used for multisigned transactions. + Raises: + XRPLException: If a field is pre-filled out incorrectly. + Returns: The autofilled transaction. """ @@ -234,7 +245,12 @@ async def autofill( 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) + if "ticket_sequence" in transaction_json: + sequence = 0 + else: + sequence = await get_next_valid_seq_number( + transaction_json["account"], client + ) transaction_json["sequence"] = sequence if "fee" not in transaction_json: transaction_json["fee"] = await _calculate_fee_per_transaction_type( @@ -243,7 +259,17 @@ async def autofill( if "last_ledger_sequence" not in transaction_json: ledger_sequence = await get_latest_validated_ledger_sequence(client) transaction_json["last_ledger_sequence"] = ledger_sequence + _LEDGER_OFFSET - return Transaction.from_dict(transaction_json) + if transaction.transaction_type == TransactionType.BATCH: + inner_txs, transaction_ids = await _autofill_batch(client, transaction_json) + transaction_json["raw_transactions"] = inner_txs + if "transaction_ids" in transaction_json: + if transaction_json["transaction_ids"] != transaction_ids: + raise XRPLException( + "Batch `TransactionIDs` don't match what `autofill` generated." + ) + else: + transaction_json["transaction_ids"] = transaction_ids + return cast(T, Transaction.from_dict(transaction_json)) def _tx_needs_networkID(client: Client) -> bool: @@ -259,7 +285,7 @@ def _tx_needs_networkID(client: Client) -> bool: Returns: bool: whether the transactions required network ID to be valid """ - if client.network_id and client.network_id > _RESTRICTED_NETWORKS: + if client.network_id is not None and client.network_id > _RESTRICTED_NETWORKS: if client.build_version and _is_not_later_rippled_version( _REQUIRED_NETWORKID_VERSION, client.build_version ): @@ -353,7 +379,7 @@ def _convert_to_classic_address(json: Dict[str, Any], field: str) -> None: field: the field in `json` that may contain an X-Address """ if field in json and is_valid_xaddress(json[field]): - json[field] = xaddress_to_classic_address(json[field]) + json[field] = xaddress_to_classic_address(json[field])[0] def transaction_json_to_binary_codec_form(dictionary: Dict[str, Any]) -> Dict[str, Any]: @@ -439,12 +465,18 @@ async def _calculate_fee_per_transaction_type( base_fee = math.ceil(net_fee * (33 + (len(fulfillment_bytes) / 16))) # AccountDelete Transaction - if transaction.transaction_type in ( + elif transaction.transaction_type in ( TransactionType.ACCOUNT_DELETE, TransactionType.AMM_CREATE, ): base_fee = await _fetch_owner_reserve_fee(client) + elif transaction.transaction_type == TransactionType.BATCH: + batch = cast(Batch, transaction) + base_fee = base_fee * (2 + len(batch.raw_transactions)) + if batch.batch_signers is not None: + base_fee += base_fee * len(batch.batch_signers) + # Multi-signed Transaction # BaseFee × (1 + Number of Signatures Provided) if signers_count is not None and signers_count > 0: @@ -457,3 +489,78 @@ async def _fetch_owner_reserve_fee(client: Client) -> int: server_state = await client._request_impl(ServerState()) fee = server_state.result["state"]["validated_ledger"]["reserve_inc"] return int(fee) + + +async def _autofill_batch( + client: Client, transaction_dict: Dict[str, Any] +) -> Tuple[List[Dict[str, Any]], List[str]]: + transaction = Batch.from_dict(transaction_dict) + assert transaction.sequence is not None + account_sequences: Dict[str, int] = {transaction.account: transaction.sequence + 1} + transaction_ids: List[str] = [] + inner_txs: List[Dict[str, Any]] = [] + + for raw_txn in transaction.raw_transactions: + raw_txn_dict = raw_txn.to_dict() + + if raw_txn.transaction_type == TransactionType.BATCH: + raise XRPLException( + "Cannot have a Batch transaction inside a Batch transaction." + ) + + if not raw_txn.has_flag(TransactionFlag.TF_INNER_BATCH_TXN): + if isinstance(raw_txn.flags, int): + raw_txn_dict["flags"] |= TransactionFlag.TF_INNER_BATCH_TXN + elif isinstance(raw_txn.flags, dict): + raw_txn_dict["flags"]["TF_INNER_BATCH_TXN"] = True + else: # is List[int] + raw_txn_dict["flags"].append(TransactionFlag.TF_INNER_BATCH_TXN) + + if raw_txn.sequence is None and raw_txn.ticket_sequence is None: + # autofill sequence + if raw_txn.account in account_sequences: + raw_txn_dict["sequence"] = account_sequences[raw_txn.account] + account_sequences[raw_txn.account] += 1 + else: + sequence = await get_next_valid_seq_number(raw_txn.account, client) + account_sequences[raw_txn.account] = sequence + 1 + raw_txn_dict["sequence"] = sequence + + if raw_txn.is_signed(): + raise XRPLException("Inner Batch transactions must not be signed.") + + # validate fields that are supposed to be empty/zeroed + def _validate_field(field_name: str, expected_value: str) -> None: + if field_name not in raw_txn_dict: + raw_txn_dict[field_name] = expected_value + elif raw_txn_dict[field_name] != expected_value: + raise XRPLException( + f"Must have a `{field_name}` of ${repr(expected_value)} in an " + "inner Batch transaction." + ) + + _validate_field("fee", "0") + _validate_field("signing_pub_key", "") + # _validate_field("txn_signature", "") + + if raw_txn.txn_signature is not None: + raise XRPLException( + "Must not have a `txn_signature` field in an inner Batch transaction." + ) + if raw_txn.signers is not None: + raise XRPLException( + "Must not have a `signers` field in an inner Batch transaction." + ) + if raw_txn.network_id is None: + if _tx_needs_networkID(client): + raw_txn_dict["network_id"] = client.network_id + if raw_txn.last_ledger_sequence is not None: + raise XRPLException( + "Must not have a `last_ledger_sequence` field in an inner Batch " + "transaction." + ) + + inner_txs.append(raw_txn_dict) + transaction_ids.append(Transaction.from_dict(raw_txn_dict).get_hash()) + + return inner_txs, transaction_ids diff --git a/xrpl/core/binarycodec/__init__.py b/xrpl/core/binarycodec/__init__.py index 5362fa334..f7e5bcd36 100644 --- a/xrpl/core/binarycodec/__init__.py +++ b/xrpl/core/binarycodec/__init__.py @@ -9,12 +9,14 @@ encode, encode_for_multisigning, encode_for_signing, + encode_for_signing_batch, encode_for_signing_claim, ) __all__ = [ "decode", "encode", + "encode_for_signing_batch", "encode_for_multisigning", "encode_for_signing", "encode_for_signing_claim", diff --git a/xrpl/core/binarycodec/definitions/definitions.json b/xrpl/core/binarycodec/definitions/definitions.json index c6940aba8..4dd05f8ce 100644 --- a/xrpl/core/binarycodec/definitions/definitions.json +++ b/xrpl/core/binarycodec/definitions/definitions.json @@ -1,912 +1,1122 @@ { + "TYPES": { + "Done": -1, + "Unknown": -2, + "NotPresent": 0, + "UInt16": 1, + "UInt32": 2, + "UInt64": 3, + "Hash128": 4, + "Hash256": 5, + "Amount": 6, + "Blob": 7, + "AccountID": 8, + "STObject": 14, + "STArray": 15, + "UInt8": 16, + "Hash160": 17, + "PathSet": 18, + "Vector256": 19, + "UInt96": 20, + "Hash192": 21, + "UInt384": 22, + "UInt512": 23, + "Issue": 24, + "XChainBridge": 25, + "Currency": 26, + "Transaction": 10001, + "LedgerEntry": 10002, + "Validation": 10003, + "Metadata": 10004 + }, + "LEDGER_ENTRY_TYPES": { + "Any": -3, + "Child": -2, + "Invalid": -1, + "NFTokenOffer": 55, + "Check": 67, + "DID": 73, + "NegativeUNL": 78, + "NFTokenPage": 80, + "SignerList": 83, + "Ticket": 84, + "AccountRoot": 97, + "DirectoryNode": 100, + "Amendments": 102, + "LedgerHashes": 104, + "Bridge": 105, + "Offer": 111, + "DepositPreauth": 112, + "XChainOwnedClaimID": 113, + "RippleState": 114, + "FeeSettings": 115, + "XChainOwnedCreateAccountClaimID": 116, + "Escrow": 117, + "PayChannel": 120, + "AMM": 121, + "Oracle": 128, + "MPTokenIssuance": 126, + "MPToken": 127, + "Credential": 129 + }, "FIELDS": [ [ "Generic", { + "nth": 0, + "isVLEncoded": false, "isSerialized": false, "isSigningField": false, - "isVLEncoded": false, - "nth": 0, "type": "Unknown" } ], [ "Invalid", { + "nth": -1, + "isVLEncoded": false, "isSerialized": false, "isSigningField": false, - "isVLEncoded": false, - "nth": -1, "type": "Unknown" } ], [ "ObjectEndMarker", { + "nth": 1, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 1, "type": "STObject" } ], [ "ArrayEndMarker", { + "nth": 1, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 1, "type": "STArray" } ], [ - "taker_gets_funded", + "hash", { + "nth": 257, + "isVLEncoded": false, "isSerialized": false, "isSigningField": false, + "type": "Hash256" + } + ], + [ + "index", + { + "nth": 258, "isVLEncoded": false, + "isSerialized": false, + "isSigningField": false, + "type": "Hash256" + } + ], + [ + "taker_gets_funded", + { "nth": 258, + "isVLEncoded": false, + "isSerialized": false, + "isSigningField": false, "type": "Amount" } ], [ "taker_pays_funded", { + "nth": 259, + "isVLEncoded": false, "isSerialized": false, "isSigningField": false, - "isVLEncoded": false, - "nth": 259, "type": "Amount" } ], [ - "LedgerEntryType", + "LedgerEntry", { - "isSerialized": true, - "isSigningField": true, + "nth": 257, "isVLEncoded": false, - "nth": 1, - "type": "UInt16" + "isSerialized": false, + "isSigningField": false, + "type": "LedgerEntry" } ], [ - "TransactionType", + "Transaction", { - "isSerialized": true, - "isSigningField": true, + "nth": 257, "isVLEncoded": false, - "nth": 2, - "type": "UInt16" + "isSerialized": false, + "isSigningField": false, + "type": "Transaction" } ], [ - "SignerWeight", + "Validation", { - "isSerialized": true, - "isSigningField": true, + "nth": 257, "isVLEncoded": false, - "nth": 3, - "type": "UInt16" + "isSerialized": false, + "isSigningField": false, + "type": "Validation" } ], [ - "TransferFee", + "Metadata", { - "isSerialized": true, - "isSigningField": true, + "nth": 257, "isVLEncoded": false, - "nth": 4, - "type": "UInt16" + "isSerialized": false, + "isSigningField": false, + "type": "Metadata" } ], [ - "TradingFee", + "CloseResolution", { + "nth": 1, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 5, - "type": "UInt16" + "type": "UInt8" } ], [ - "DiscountedFee", + "Method", { + "nth": 2, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 6, - "type": "UInt16" + "type": "UInt8" } ], [ - "Version", + "TransactionResult", { + "nth": 3, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 16, - "type": "UInt16" + "type": "UInt8" } ], [ - "HookStateChangeCount", + "Scale", { + "nth": 4, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 17, - "type": "UInt16" + "type": "UInt8" } ], [ - "HookEmitCount", + "AssetScale", { + "nth": 5, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 18, - "type": "UInt16" + "type": "UInt8" } ], [ - "HookExecutionIndex", + "TickSize", { + "nth": 16, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 19, - "type": "UInt16" + "type": "UInt8" } ], [ - "HookApiVersion", + "UNLModifyDisabling", { + "nth": 17, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 20, - "type": "UInt16" + "type": "UInt8" } ], [ - "LedgerFixType", + "HookResult", { + "nth": 18, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 21, - "type": "UInt16" + "type": "UInt8" } ], [ - "NetworkID", + "WasLockingChainSend", { + "nth": 19, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 1, - "type": "UInt32" + "type": "UInt8" } ], [ - "Flags", + "LedgerEntryType", { + "nth": 1, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 2, - "type": "UInt32" + "type": "UInt16" } ], [ - "SourceTag", + "TransactionType", { + "nth": 2, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 3, - "type": "UInt32" + "type": "UInt16" } ], [ - "Sequence", + "SignerWeight", { + "nth": 3, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 4, - "type": "UInt32" + "type": "UInt16" } ], [ - "PreviousTxnLgrSeq", + "TransferFee", { + "nth": 4, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 5, - "type": "UInt32" + "type": "UInt16" } ], [ - "LedgerSequence", + "TradingFee", { + "nth": 5, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 6, - "type": "UInt32" + "type": "UInt16" } ], [ - "CloseTime", + "DiscountedFee", { + "nth": 6, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 7, - "type": "UInt32" + "type": "UInt16" } ], [ - "ParentCloseTime", + "Version", { + "nth": 16, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 8, - "type": "UInt32" + "type": "UInt16" } ], [ - "SigningTime", + "HookStateChangeCount", { + "nth": 17, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 9, - "type": "UInt32" + "type": "UInt16" } ], [ - "Expiration", + "HookEmitCount", { + "nth": 18, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 10, - "type": "UInt32" + "type": "UInt16" } ], [ - "TransferRate", + "HookExecutionIndex", { + "nth": 19, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 11, - "type": "UInt32" + "type": "UInt16" } ], [ - "WalletSize", + "HookApiVersion", { + "nth": 20, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 12, - "type": "UInt32" + "type": "UInt16" } ], [ - "OwnerCount", + "LedgerFixType", { + "nth": 21, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 13, - "type": "UInt32" + "type": "UInt16" } ], [ - "DestinationTag", + "NetworkID", { + "nth": 1, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 14, "type": "UInt32" } ], [ - "LastUpdateTime", + "Flags", { + "nth": 2, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 15, "type": "UInt32" } ], [ - "HighQualityIn", + "SourceTag", { + "nth": 3, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 16, "type": "UInt32" } ], [ - "HighQualityOut", + "Sequence", { + "nth": 4, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 17, "type": "UInt32" } ], [ - "LowQualityIn", + "PreviousTxnLgrSeq", { + "nth": 5, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 18, "type": "UInt32" } ], [ - "LowQualityOut", + "LedgerSequence", { + "nth": 6, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 19, "type": "UInt32" } ], [ - "QualityIn", + "CloseTime", { + "nth": 7, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 20, "type": "UInt32" } ], [ - "QualityOut", + "ParentCloseTime", { + "nth": 8, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 21, "type": "UInt32" } ], [ - "StampEscrow", + "SigningTime", { + "nth": 9, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 22, "type": "UInt32" } ], [ - "BondAmount", + "Expiration", { + "nth": 10, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 23, "type": "UInt32" } ], [ - "LoadFee", + "TransferRate", { + "nth": 11, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 24, "type": "UInt32" } ], [ - "OfferSequence", + "WalletSize", { + "nth": 12, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 25, "type": "UInt32" } ], [ - "FirstLedgerSequence", + "OwnerCount", { + "nth": 13, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 26, "type": "UInt32" } ], [ - "LastLedgerSequence", + "DestinationTag", { + "nth": 14, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 27, "type": "UInt32" } ], [ - "TransactionIndex", + "LastUpdateTime", { + "nth": 15, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 28, "type": "UInt32" } ], [ - "OperationLimit", + "HighQualityIn", { + "nth": 16, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 29, "type": "UInt32" } ], [ - "ReferenceFeeUnits", + "HighQualityOut", { + "nth": 17, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 30, "type": "UInt32" } ], [ - "ReserveBase", + "LowQualityIn", { + "nth": 18, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 31, "type": "UInt32" } ], [ - "ReserveIncrement", + "LowQualityOut", { + "nth": 19, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 32, "type": "UInt32" } ], [ - "SetFlag", + "QualityIn", { + "nth": 20, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 33, "type": "UInt32" } ], [ - "ClearFlag", + "QualityOut", { + "nth": 21, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 34, "type": "UInt32" } ], [ - "SignerQuorum", + "StampEscrow", { + "nth": 22, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 35, "type": "UInt32" } ], [ - "CancelAfter", + "BondAmount", { + "nth": 23, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 36, "type": "UInt32" } ], [ - "FinishAfter", + "LoadFee", { + "nth": 24, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 37, "type": "UInt32" } ], [ - "SignerListID", + "OfferSequence", { + "nth": 25, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 38, "type": "UInt32" } ], [ - "SettleDelay", + "FirstLedgerSequence", { + "nth": 26, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 39, "type": "UInt32" } ], [ - "TicketCount", + "LastLedgerSequence", { + "nth": 27, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 40, "type": "UInt32" } ], [ - "TicketSequence", + "TransactionIndex", { + "nth": 28, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 41, "type": "UInt32" } ], [ - "NFTokenTaxon", + "OperationLimit", { + "nth": 29, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 42, "type": "UInt32" } ], [ - "MintedNFTokens", + "ReferenceFeeUnits", { + "nth": 30, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 43, "type": "UInt32" } ], [ - "BurnedNFTokens", + "ReserveBase", { + "nth": 31, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 44, "type": "UInt32" } ], [ - "HookStateCount", + "ReserveIncrement", { + "nth": 32, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 45, "type": "UInt32" } ], [ - "EmitGeneration", + "SetFlag", { + "nth": 33, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 46, "type": "UInt32" } ], [ - "VoteWeight", + "ClearFlag", { + "nth": 34, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 48, "type": "UInt32" } ], [ - "FirstNFTokenSequence", + "SignerQuorum", { + "nth": 35, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "type": "UInt32" + } + ], + [ + "CancelAfter", + { + "nth": 36, "isVLEncoded": false, - "nth": 50, + "isSerialized": true, + "isSigningField": true, "type": "UInt32" } ], [ - "OracleDocumentID", + "FinishAfter", { + "nth": 37, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "type": "UInt32" + } + ], + [ + "SignerListID", + { + "nth": 38, "isVLEncoded": false, - "nth": 51, + "isSerialized": true, + "isSigningField": true, "type": "UInt32" } ], [ - "IndexNext", + "SettleDelay", + { + "nth": 39, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt32" + } + ], + [ + "TicketCount", + { + "nth": 40, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt32" + } + ], + [ + "TicketSequence", + { + "nth": 41, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt32" + } + ], + [ + "NFTokenTaxon", + { + "nth": 42, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt32" + } + ], + [ + "MintedNFTokens", + { + "nth": 43, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt32" + } + ], + [ + "BurnedNFTokens", + { + "nth": 44, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt32" + } + ], + [ + "HookStateCount", + { + "nth": 45, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt32" + } + ], + [ + "EmitGeneration", { + "nth": 46, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt32" + } + ], + [ + "VoteWeight", + { + "nth": 48, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt32" + } + ], + [ + "FirstNFTokenSequence", + { + "nth": 50, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "type": "UInt32" + } + ], + [ + "OracleDocumentID", + { + "nth": 51, "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt32" + } + ], + [ + "IndexNext", + { "nth": 1, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, "type": "UInt64" } ], [ "IndexPrevious", { + "nth": 2, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 2, "type": "UInt64" } ], [ "BookNode", { + "nth": 3, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 3, "type": "UInt64" } ], [ "OwnerNode", { + "nth": 4, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 4, "type": "UInt64" } ], [ "BaseFee", { + "nth": 5, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 5, "type": "UInt64" } ], [ "ExchangeRate", { + "nth": 6, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 6, "type": "UInt64" } ], [ "LowNode", { + "nth": 7, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 7, "type": "UInt64" } ], [ "HighNode", { + "nth": 8, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 8, "type": "UInt64" } ], [ "DestinationNode", { + "nth": 9, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 9, "type": "UInt64" } ], [ "Cookie", { + "nth": 10, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 10, "type": "UInt64" } ], [ "ServerVersion", { + "nth": 11, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 11, "type": "UInt64" } ], [ "NFTokenOfferNode", { + "nth": 12, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 12, "type": "UInt64" } ], [ "EmitBurden", { + "nth": 13, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 13, "type": "UInt64" } ], [ "HookOn", { + "nth": 16, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 16, "type": "UInt64" } ], [ "HookInstructionCount", { + "nth": 17, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 17, "type": "UInt64" } ], [ "HookReturnCode", { + "nth": 18, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 18, "type": "UInt64" } ], [ "ReferenceCount", { + "nth": 19, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 19, "type": "UInt64" } ], [ "XChainClaimID", { + "nth": 20, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 20, "type": "UInt64" } ], [ "XChainAccountCreateCount", { + "nth": 21, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 21, "type": "UInt64" } ], [ "XChainAccountClaimCount", { + "nth": 22, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 22, "type": "UInt64" } ], [ "AssetPrice", { + "nth": 23, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 23, "type": "UInt64" } ], [ "MaximumAmount", { + "nth": 24, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 24, "type": "UInt64" } ], [ "OutstandingAmount", { + "nth": 25, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 25, "type": "UInt64" } ], [ "MPTAmount", { + "nth": 26, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 26, "type": "UInt64" } ], @@ -933,900 +1143,940 @@ [ "EmailHash", { + "nth": 1, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 1, "type": "Hash128" } ], [ - "LedgerHash", + "TakerPaysCurrency", { + "nth": 1, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 1, - "type": "Hash256" + "type": "Hash160" } ], [ - "ParentHash", + "TakerPaysIssuer", { + "nth": 2, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 2, - "type": "Hash256" + "type": "Hash160" } ], [ - "TransactionHash", + "TakerGetsCurrency", { + "nth": 3, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 3, - "type": "Hash256" + "type": "Hash160" } ], [ - "AccountHash", + "TakerGetsIssuer", { + "nth": 4, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "type": "Hash160" + } + ], + [ + "MPTokenIssuanceID", + { + "nth": 1, "isVLEncoded": false, - "nth": 4, + "isSerialized": true, + "isSigningField": true, + "type": "Hash192" + } + ], + [ + "LedgerHash", + { + "nth": 1, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, "type": "Hash256" } ], [ - "PreviousTxnID", + "ParentHash", + { + "nth": 2, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Hash256" + } + ], + [ + "TransactionHash", { + "nth": 3, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "type": "Hash256" + } + ], + [ + "AccountHash", + { + "nth": 4, "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Hash256" + } + ], + [ + "PreviousTxnID", + { "nth": 5, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, "type": "Hash256" } ], [ "LedgerIndex", { + "nth": 6, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 6, "type": "Hash256" } ], [ "WalletLocator", { + "nth": 7, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 7, "type": "Hash256" } ], [ "RootIndex", { + "nth": 8, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 8, "type": "Hash256" } ], [ "AccountTxnID", { + "nth": 9, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 9, "type": "Hash256" } ], [ "NFTokenID", { + "nth": 10, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 10, "type": "Hash256" } ], [ "EmitParentTxnID", { + "nth": 11, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 11, "type": "Hash256" } ], [ "EmitNonce", { + "nth": 12, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 12, "type": "Hash256" } ], [ "EmitHookHash", { + "nth": 13, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 13, "type": "Hash256" } ], [ "AMMID", { + "nth": 14, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 14, "type": "Hash256" } ], [ "BookDirectory", { + "nth": 16, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 16, "type": "Hash256" } ], [ "InvoiceID", { + "nth": 17, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 17, "type": "Hash256" } ], [ "Nickname", { + "nth": 18, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 18, "type": "Hash256" } ], [ "Amendment", { + "nth": 19, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 19, "type": "Hash256" } ], [ "Digest", { + "nth": 21, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 21, "type": "Hash256" } ], [ "Channel", { + "nth": 22, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 22, "type": "Hash256" } ], [ "ConsensusHash", { + "nth": 23, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 23, "type": "Hash256" } ], [ "CheckID", { + "nth": 24, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 24, "type": "Hash256" } ], [ "ValidatedHash", { + "nth": 25, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 25, "type": "Hash256" } ], [ "PreviousPageMin", { + "nth": 26, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 26, "type": "Hash256" } ], [ "NextPageMin", { + "nth": 27, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 27, "type": "Hash256" } ], [ "NFTokenBuyOffer", { + "nth": 28, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 28, "type": "Hash256" } ], [ "NFTokenSellOffer", { + "nth": 29, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 29, "type": "Hash256" } ], [ "HookStateKey", { + "nth": 30, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 30, "type": "Hash256" } ], [ "HookHash", { + "nth": 31, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 31, "type": "Hash256" } ], [ "HookNamespace", { + "nth": 32, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 32, "type": "Hash256" } ], [ "HookSetTxnID", { - "isSerialized": true, - "isSigningField": true, - "isVLEncoded": false, "nth": 33, - "type": "Hash256" - } - ], - [ - "hash", - { - "isSerialized": false, - "isSigningField": false, "isVLEncoded": false, - "nth": 257, + "isSerialized": true, + "isSigningField": true, "type": "Hash256" } ], [ - "index", + "BatchTransactionID", { - "isSerialized": false, - "isSigningField": false, + "nth": 34, "isVLEncoded": false, - "nth": 258, + "isSerialized": true, + "isSigningField": true, "type": "Hash256" } ], [ "Amount", { + "nth": 1, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 1, "type": "Amount" } ], [ "Balance", { + "nth": 2, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 2, "type": "Amount" } ], [ "LimitAmount", { + "nth": 3, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 3, "type": "Amount" } ], [ "TakerPays", { + "nth": 4, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 4, "type": "Amount" } ], [ "TakerGets", { + "nth": 5, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 5, "type": "Amount" } ], [ "LowLimit", { + "nth": 6, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 6, "type": "Amount" } ], [ "HighLimit", { + "nth": 7, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 7, "type": "Amount" } ], [ "Fee", { + "nth": 8, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 8, "type": "Amount" } ], [ "SendMax", { + "nth": 9, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 9, "type": "Amount" } ], [ "DeliverMin", { + "nth": 10, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 10, "type": "Amount" } ], [ "Amount2", { + "nth": 11, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 11, "type": "Amount" } ], [ "BidMin", { + "nth": 12, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 12, "type": "Amount" } ], [ "BidMax", { + "nth": 13, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 13, "type": "Amount" } ], [ "MinimumOffer", { + "nth": 16, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 16, "type": "Amount" } ], [ "RippleEscrow", { + "nth": 17, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 17, "type": "Amount" } ], [ "DeliveredAmount", { + "nth": 18, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 18, "type": "Amount" } ], [ "NFTokenBrokerFee", { + "nth": 19, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 19, "type": "Amount" } ], [ "BaseFeeDrops", { + "nth": 22, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 22, "type": "Amount" } ], [ "ReserveBaseDrops", { + "nth": 23, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 23, "type": "Amount" } ], [ "ReserveIncrementDrops", { + "nth": 24, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 24, "type": "Amount" } ], [ "LPTokenOut", { + "nth": 25, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 25, "type": "Amount" } ], [ "LPTokenIn", { + "nth": 26, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 26, "type": "Amount" } ], [ "EPrice", { + "nth": 27, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 27, "type": "Amount" } ], [ "Price", { + "nth": 28, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 28, "type": "Amount" } ], [ "SignatureReward", { + "nth": 29, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 29, "type": "Amount" } ], [ "MinAccountCreateAmount", { + "nth": 30, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 30, "type": "Amount" } ], [ "LPTokenBalance", { + "nth": 31, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 31, "type": "Amount" } ], [ "PublicKey", { + "nth": 1, + "isVLEncoded": true, "isSerialized": true, "isSigningField": true, - "isVLEncoded": true, - "nth": 1, "type": "Blob" } ], [ "MessageKey", { + "nth": 2, + "isVLEncoded": true, "isSerialized": true, "isSigningField": true, - "isVLEncoded": true, - "nth": 2, "type": "Blob" } ], [ "SigningPubKey", { + "nth": 3, + "isVLEncoded": true, "isSerialized": true, "isSigningField": true, - "isVLEncoded": true, - "nth": 3, "type": "Blob" } ], [ "TxnSignature", { + "nth": 4, + "isVLEncoded": true, "isSerialized": true, "isSigningField": false, - "isVLEncoded": true, - "nth": 4, "type": "Blob" } ], [ "URI", { + "nth": 5, + "isVLEncoded": true, "isSerialized": true, "isSigningField": true, - "isVLEncoded": true, - "nth": 5, "type": "Blob" } ], [ "Signature", { + "nth": 6, + "isVLEncoded": true, "isSerialized": true, "isSigningField": false, - "isVLEncoded": true, - "nth": 6, "type": "Blob" } ], [ "Domain", { + "nth": 7, + "isVLEncoded": true, "isSerialized": true, "isSigningField": true, - "isVLEncoded": true, - "nth": 7, "type": "Blob" } ], [ "FundCode", { + "nth": 8, + "isVLEncoded": true, "isSerialized": true, "isSigningField": true, - "isVLEncoded": true, - "nth": 8, "type": "Blob" } ], [ "RemoveCode", { + "nth": 9, + "isVLEncoded": true, "isSerialized": true, "isSigningField": true, - "isVLEncoded": true, - "nth": 9, "type": "Blob" } ], [ "ExpireCode", { + "nth": 10, + "isVLEncoded": true, "isSerialized": true, "isSigningField": true, - "isVLEncoded": true, - "nth": 10, "type": "Blob" } ], [ "CreateCode", { + "nth": 11, + "isVLEncoded": true, "isSerialized": true, "isSigningField": true, - "isVLEncoded": true, - "nth": 11, "type": "Blob" } ], [ "MemoType", { + "nth": 12, + "isVLEncoded": true, "isSerialized": true, "isSigningField": true, - "isVLEncoded": true, - "nth": 12, "type": "Blob" } ], [ "MemoData", { + "nth": 13, + "isVLEncoded": true, "isSerialized": true, "isSigningField": true, - "isVLEncoded": true, - "nth": 13, "type": "Blob" } ], [ "MemoFormat", { + "nth": 14, + "isVLEncoded": true, "isSerialized": true, "isSigningField": true, - "isVLEncoded": true, - "nth": 14, "type": "Blob" } ], [ "Fulfillment", { + "nth": 16, + "isVLEncoded": true, "isSerialized": true, "isSigningField": true, - "isVLEncoded": true, - "nth": 16, "type": "Blob" } ], [ "Condition", { + "nth": 17, + "isVLEncoded": true, "isSerialized": true, "isSigningField": true, - "isVLEncoded": true, - "nth": 17, "type": "Blob" } ], [ "MasterSignature", { + "nth": 18, + "isVLEncoded": true, "isSerialized": true, "isSigningField": false, - "isVLEncoded": true, - "nth": 18, "type": "Blob" } ], [ "UNLModifyValidator", { + "nth": 19, + "isVLEncoded": true, "isSerialized": true, "isSigningField": true, - "isVLEncoded": true, - "nth": 19, "type": "Blob" } ], [ "ValidatorToDisable", { + "nth": 20, + "isVLEncoded": true, "isSerialized": true, "isSigningField": true, - "isVLEncoded": true, - "nth": 20, "type": "Blob" } ], [ "ValidatorToReEnable", { + "nth": 21, + "isVLEncoded": true, "isSerialized": true, "isSigningField": true, - "isVLEncoded": true, - "nth": 21, "type": "Blob" } ], [ "HookStateData", { + "nth": 22, + "isVLEncoded": true, "isSerialized": true, "isSigningField": true, - "isVLEncoded": true, - "nth": 22, "type": "Blob" } ], [ "HookReturnString", { + "nth": 23, + "isVLEncoded": true, "isSerialized": true, "isSigningField": true, - "isVLEncoded": true, - "nth": 23, "type": "Blob" } ], [ "HookParameterName", { + "nth": 24, + "isVLEncoded": true, "isSerialized": true, "isSigningField": true, - "isVLEncoded": true, - "nth": 24, "type": "Blob" } ], [ "HookParameterValue", { + "nth": 25, + "isVLEncoded": true, "isSerialized": true, "isSigningField": true, - "isVLEncoded": true, - "nth": 25, "type": "Blob" } ], [ "DIDDocument", { + "nth": 26, + "isVLEncoded": true, "isSerialized": true, "isSigningField": true, - "isVLEncoded": true, - "nth": 26, "type": "Blob" } ], [ "Data", { + "nth": 27, + "isVLEncoded": true, "isSerialized": true, "isSigningField": true, - "isVLEncoded": true, - "nth": 27, "type": "Blob" } ], [ "AssetClass", { + "nth": 28, + "isVLEncoded": true, "isSerialized": true, "isSigningField": true, - "isVLEncoded": true, - "nth": 28, "type": "Blob" } ], [ "Provider", { + "nth": 29, + "isVLEncoded": true, "isSerialized": true, "isSigningField": true, - "isVLEncoded": true, - "nth": 29, "type": "Blob" } ], [ "MPTokenMetadata", { + "nth": 30, + "isVLEncoded": true, "isSerialized": true, "isSigningField": true, - "isVLEncoded": true, - "nth": 30, "type": "Blob" } ], @@ -1843,170 +2093,170 @@ [ "Account", { + "nth": 1, + "isVLEncoded": true, "isSerialized": true, "isSigningField": true, - "isVLEncoded": true, - "nth": 1, "type": "AccountID" } ], [ "Owner", { + "nth": 2, + "isVLEncoded": true, "isSerialized": true, "isSigningField": true, - "isVLEncoded": true, - "nth": 2, "type": "AccountID" } ], [ "Destination", { + "nth": 3, + "isVLEncoded": true, "isSerialized": true, "isSigningField": true, - "isVLEncoded": true, - "nth": 3, "type": "AccountID" } ], [ "Issuer", { + "nth": 4, + "isVLEncoded": true, "isSerialized": true, "isSigningField": true, - "isVLEncoded": true, - "nth": 4, "type": "AccountID" } ], [ "Authorize", { + "nth": 5, + "isVLEncoded": true, "isSerialized": true, "isSigningField": true, - "isVLEncoded": true, - "nth": 5, "type": "AccountID" } ], [ "Unauthorize", { + "nth": 6, + "isVLEncoded": true, "isSerialized": true, "isSigningField": true, - "isVLEncoded": true, - "nth": 6, "type": "AccountID" } ], [ "RegularKey", { + "nth": 8, + "isVLEncoded": true, "isSerialized": true, "isSigningField": true, - "isVLEncoded": true, - "nth": 8, "type": "AccountID" } ], [ "NFTokenMinter", { + "nth": 9, + "isVLEncoded": true, "isSerialized": true, "isSigningField": true, - "isVLEncoded": true, - "nth": 9, "type": "AccountID" } ], [ "EmitCallback", { + "nth": 10, + "isVLEncoded": true, "isSerialized": true, "isSigningField": true, - "isVLEncoded": true, - "nth": 10, "type": "AccountID" } ], [ "Holder", { + "nth": 11, + "isVLEncoded": true, "isSerialized": true, "isSigningField": true, - "isVLEncoded": true, - "nth": 11, "type": "AccountID" } ], [ "HookAccount", { + "nth": 16, + "isVLEncoded": true, "isSerialized": true, "isSigningField": true, - "isVLEncoded": true, - "nth": 16, "type": "AccountID" } ], [ "OtherChainSource", { + "nth": 18, + "isVLEncoded": true, "isSerialized": true, "isSigningField": true, - "isVLEncoded": true, - "nth": 18, "type": "AccountID" } ], [ "OtherChainDestination", { + "nth": 19, + "isVLEncoded": true, "isSerialized": true, "isSigningField": true, - "isVLEncoded": true, - "nth": 19, "type": "AccountID" } ], [ "AttestationSignerAccount", { + "nth": 20, + "isVLEncoded": true, "isSerialized": true, "isSigningField": true, - "isVLEncoded": true, - "nth": 20, "type": "AccountID" } ], [ "AttestationRewardAccount", { + "nth": 21, + "isVLEncoded": true, "isSerialized": true, "isSigningField": true, - "isVLEncoded": true, - "nth": 21, "type": "AccountID" } ], [ "LockingChainDoor", { + "nth": 22, + "isVLEncoded": true, "isSerialized": true, "isSigningField": true, - "isVLEncoded": true, - "nth": 22, "type": "AccountID" } ], [ "IssuingChainDoor", { + "nth": 23, + "isVLEncoded": true, "isSerialized": true, "isSigningField": true, - "isVLEncoded": true, - "nth": 23, "type": "AccountID" } ], @@ -2021,299 +2271,299 @@ } ], [ - "TransactionMetaData", + "Indexes", { + "nth": 1, + "isVLEncoded": true, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 2, - "type": "STObject" + "type": "Vector256" } ], [ - "CreatedNode", + "Hashes", { + "nth": 2, + "isVLEncoded": true, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 3, - "type": "STObject" + "type": "Vector256" } ], [ - "DeletedNode", + "Amendments", { + "nth": 3, + "isVLEncoded": true, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 4, - "type": "STObject" + "type": "Vector256" } ], [ - "ModifiedNode", + "NFTokenOffers", { + "nth": 4, + "isVLEncoded": true, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 5, - "type": "STObject" + "type": "Vector256" } ], [ - "PreviousFields", + "CredentialIDs", { + "nth": 5, + "isVLEncoded": true, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 6, - "type": "STObject" + "type": "Vector256" } ], [ - "FinalFields", + "TransactionIDs", { + "nth": 6, + "isVLEncoded": true, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 7, - "type": "STObject" + "type": "Vector256" } ], [ - "NewFields", + "Paths", { + "nth": 1, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 8, - "type": "STObject" + "type": "PathSet" } ], [ - "TemplateEntry", + "BaseAsset", { + "nth": 1, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 9, - "type": "STObject" + "type": "Currency" } ], [ - "Memo", + "QuoteAsset", { + "nth": 2, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 10, - "type": "STObject" + "type": "Currency" } ], [ - "SignerEntry", + "LockingChainIssue", { + "nth": 1, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 11, - "type": "STObject" + "type": "Issue" } ], [ - "NFToken", + "IssuingChainIssue", { + "nth": 2, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 12, - "type": "STObject" + "type": "Issue" } ], [ - "EmitDetails", + "Asset", { + "nth": 3, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 13, - "type": "STObject" + "type": "Issue" } ], [ - "Hook", + "Asset2", { + "nth": 4, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 14, - "type": "STObject" + "type": "Issue" } ], [ - "Signer", + "XChainBridge", { + "nth": 1, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 16, - "type": "STObject" + "type": "XChainBridge" } ], [ - "Majority", + "TransactionMetaData", { + "nth": 2, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 18, "type": "STObject" } ], [ - "DisabledValidator", + "CreatedNode", { + "nth": 3, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 19, "type": "STObject" } ], [ - "EmittedTxn", + "DeletedNode", { + "nth": 4, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 20, "type": "STObject" } ], [ - "HookExecution", + "ModifiedNode", { + "nth": 5, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 21, "type": "STObject" } ], [ - "HookDefinition", + "PreviousFields", { + "nth": 6, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 22, "type": "STObject" } ], [ - "HookParameter", + "FinalFields", { + "nth": 7, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 23, "type": "STObject" } ], [ - "HookGrant", + "NewFields", { + "nth": 8, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 24, "type": "STObject" } ], [ - "VoteEntry", + "TemplateEntry", { + "nth": 9, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 25, "type": "STObject" } ], [ - "AuctionSlot", + "Memo", { + "nth": 10, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 26, "type": "STObject" } ], [ - "AuthAccount", + "SignerEntry", { + "nth": 11, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 27, "type": "STObject" } ], [ - "XChainClaimProofSig", + "NFToken", { + "nth": 12, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 28, "type": "STObject" } ], [ - "XChainCreateAccountProofSig", + "EmitDetails", { + "nth": 13, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 29, "type": "STObject" } ], [ - "XChainClaimAttestationCollectionElement", + "Hook", { + "nth": 14, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 30, "type": "STObject" } ], [ - "XChainCreateAccountAttestationCollectionElement", + "Signer", { + "nth": 16, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 31, "type": "STObject" } ], [ - "PriceData", + "Majority", { + "nth": 18, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 32, "type": "STObject" } ], [ - "Credential", + "DisabledValidator", { - "nth": 33, + "nth": 19, "isVLEncoded": false, "isSerialized": true, "isSigningField": true, @@ -2321,199 +2571,199 @@ } ], [ - "Signers", + "EmittedTxn", { - "isSerialized": true, - "isSigningField": false, + "nth": 20, "isVLEncoded": false, - "nth": 3, - "type": "STArray" + "isSerialized": true, + "isSigningField": true, + "type": "STObject" } ], [ - "SignerEntries", + "HookExecution", { + "nth": 21, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 4, - "type": "STArray" + "type": "STObject" } ], [ - "Template", + "HookDefinition", { + "nth": 22, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 5, - "type": "STArray" + "type": "STObject" } ], [ - "Necessary", + "HookParameter", { + "nth": 23, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 6, - "type": "STArray" + "type": "STObject" } ], [ - "Sufficient", + "HookGrant", { + "nth": 24, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 7, - "type": "STArray" + "type": "STObject" } ], [ - "AffectedNodes", + "VoteEntry", { + "nth": 25, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 8, - "type": "STArray" + "type": "STObject" } ], [ - "Memos", + "AuctionSlot", { + "nth": 26, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 9, - "type": "STArray" + "type": "STObject" } ], [ - "NFTokens", + "AuthAccount", { + "nth": 27, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 10, - "type": "STArray" + "type": "STObject" } ], [ - "Hooks", + "XChainClaimProofSig", { + "nth": 28, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 11, - "type": "STArray" + "type": "STObject" } ], [ - "VoteSlots", + "XChainCreateAccountProofSig", { + "nth": 29, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 12, - "type": "STArray" + "type": "STObject" } ], [ - "Majorities", + "XChainClaimAttestationCollectionElement", { + "nth": 30, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 16, - "type": "STArray" + "type": "STObject" } ], [ - "DisabledValidators", + "XChainCreateAccountAttestationCollectionElement", { + "nth": 31, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 17, - "type": "STArray" + "type": "STObject" } ], [ - "HookExecutions", + "PriceData", { + "nth": 32, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 18, - "type": "STArray" + "type": "STObject" } ], [ - "HookParameters", + "Credential", { + "nth": 33, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 19, - "type": "STArray" + "type": "STObject" } ], [ - "HookGrants", + "RawTransaction", { + "nth": 34, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 20, - "type": "STArray" + "type": "STObject" } ], [ - "XChainClaimAttestations", + "BatchSigner", { + "nth": 35, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 21, - "type": "STArray" + "type": "STObject" } ], [ - "XChainCreateAccountAttestations", + "Signers", { - "isSerialized": true, - "isSigningField": true, + "nth": 3, "isVLEncoded": false, - "nth": 22, + "isSerialized": true, + "isSigningField": false, "type": "STArray" } ], [ - "PriceDataSeries", + "SignerEntries", { + "nth": 4, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 24, "type": "STArray" } ], [ - "AuthAccounts", + "Template", { + "nth": 5, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 25, "type": "STArray" } ], [ - "AuthorizeCredentials", + "Necessary", { - "nth": 26, + "nth": 6, "isVLEncoded": false, "isSerialized": true, "isSigningField": true, @@ -2521,9 +2771,9 @@ } ], [ - "UnauthorizeCredentials", + "Sufficient", { - "nth": 27, + "nth": 7, "isVLEncoded": false, "isSerialized": true, "isSigningField": true, @@ -2531,476 +2781,215 @@ } ], [ - "CloseResolution", + "AffectedNodes", { - "isSerialized": true, - "isSigningField": true, + "nth": 8, "isVLEncoded": false, - "nth": 1, - "type": "UInt8" - } - ], - [ - "Method", - { "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 2, - "type": "UInt8" + "type": "STArray" } ], [ - "TransactionResult", + "Memos", { - "isSerialized": true, - "isSigningField": true, + "nth": 9, "isVLEncoded": false, - "nth": 3, - "type": "UInt8" - } - ], - [ - "Scale", - { "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 4, - "type": "UInt8" + "type": "STArray" } ], [ - "AssetScale", + "NFTokens", { - "isSerialized": true, - "isSigningField": true, + "nth": 10, "isVLEncoded": false, - "nth": 5, - "type": "UInt8" - } - ], - [ - "TickSize", - { "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 16, - "type": "UInt8" + "type": "STArray" } ], [ - "UNLModifyDisabling", + "Hooks", { - "isSerialized": true, - "isSigningField": true, + "nth": 11, "isVLEncoded": false, - "nth": 17, - "type": "UInt8" - } - ], - [ - "HookResult", - { "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 18, - "type": "UInt8" + "type": "STArray" } ], [ - "WasLockingChainSend", + "VoteSlots", { - "isSerialized": true, - "isSigningField": true, + "nth": 12, "isVLEncoded": false, - "nth": 19, - "type": "UInt8" - } - ], - [ - "TakerPaysCurrency", - { "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 1, - "type": "Hash160" + "type": "STArray" } ], [ - "TakerPaysIssuer", + "Majorities", { - "isSerialized": true, - "isSigningField": true, + "nth": 16, "isVLEncoded": false, - "nth": 2, - "type": "Hash160" - } - ], - [ - "TakerGetsCurrency", - { "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 3, - "type": "Hash160" + "type": "STArray" } ], [ - "TakerGetsIssuer", + "DisabledValidators", { - "isSerialized": true, - "isSigningField": true, + "nth": 17, "isVLEncoded": false, - "nth": 4, - "type": "Hash160" - } - ], - [ - "Paths", - { "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 1, - "type": "PathSet" + "type": "STArray" } ], [ - "Indexes", + "HookExecutions", { + "nth": 18, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": true, - "nth": 1, - "type": "Vector256" + "type": "STArray" } ], [ - "Hashes", + "HookParameters", { + "nth": 19, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": true, - "nth": 2, - "type": "Vector256" + "type": "STArray" } ], [ - "Amendments", + "HookGrants", { + "nth": 20, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": true, - "nth": 3, - "type": "Vector256" + "type": "STArray" } ], [ - "NFTokenOffers", + "XChainClaimAttestations", { + "nth": 21, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": true, - "nth": 4, - "type": "Vector256" + "type": "STArray" } ], [ - "CredentialIDs", + "XChainCreateAccountAttestations", { - "nth": 5, - "isVLEncoded": true, + "nth": 22, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "Vector256" + "type": "STArray" } ], [ - "MPTokenIssuanceID", + "PriceDataSeries", { + "nth": 24, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 1, - "type": "Hash192" + "type": "STArray" } ], [ - "LockingChainIssue", + "AuthAccounts", { + "nth": 25, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 1, - "type": "Issue" + "type": "STArray" } ], [ - "IssuingChainIssue", + "AuthorizeCredentials", { + "nth": 26, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 2, - "type": "Issue" + "type": "STArray" } ], [ - "Asset", + "UnauthorizeCredentials", { + "nth": 27, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 3, - "type": "Issue" + "type": "STArray" } ], [ - "Asset2", + "RawTransactions", { + "nth": 28, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 4, - "type": "Issue" + "type": "STArray" } ], [ - "XChainBridge", + "BatchSigners", { + "nth": 29, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "isVLEncoded": false, - "nth": 1, - "type": "XChainBridge" - } - ], - [ - "BaseAsset", - { - "isSerialized": true, - "isSigningField": true, - "isVLEncoded": false, - "nth": 1, - "type": "Currency" - } - ], - [ - "QuoteAsset", - { - "isSerialized": true, - "isSigningField": true, - "isVLEncoded": false, - "nth": 2, - "type": "Currency" - } - ], - [ - "Transaction", - { - "isSerialized": false, - "isSigningField": false, - "isVLEncoded": false, - "nth": 257, - "type": "Transaction" - } - ], - [ - "LedgerEntry", - { - "isSerialized": false, - "isSigningField": false, - "isVLEncoded": false, - "nth": 257, - "type": "LedgerEntry" - } - ], - [ - "Validation", - { - "isSerialized": false, - "isSigningField": false, - "isVLEncoded": false, - "nth": 257, - "type": "Validation" - } - ], - [ - "Metadata", - { - "isSerialized": false, - "isSigningField": false, - "isVLEncoded": false, - "nth": 257, - "type": "Metadata" + "type": "STArray" } ] ], - "LEDGER_ENTRY_TYPES": { - "AMM": 121, - "AccountRoot": 97, - "Amendments": 102, - "Bridge": 105, - "Check": 67, - "DID": 73, - "DepositPreauth": 112, - "DirectoryNode": 100, - "Escrow": 117, - "FeeSettings": 115, - "Invalid": -1, - "LedgerHashes": 104, - "MPToken": 127, - "MPTokenIssuance": 126, - "NFTokenOffer": 55, - "NFTokenPage": 80, - "NegativeUNL": 78, - "Offer": 111, - "Oracle": 128, - "Credential": 129, - "PayChannel": 120, - "RippleState": 114, - "SignerList": 83, - "Ticket": 84, - "XChainOwnedClaimID": 113, - "XChainOwnedCreateAccountClaimID": 116 - }, "TRANSACTION_RESULTS": { - "tecAMM_ACCOUNT": 168, - "tecAMM_BALANCE": 163, - "tecAMM_EMPTY": 166, - "tecAMM_FAILED": 164, - "tecAMM_INVALID_TOKENS": 165, - "tecAMM_NOT_EMPTY": 167, - "tecARRAY_EMPTY": 190, - "tecARRAY_TOO_LARGE": 191, - "tecBAD_CREDENTIALS": 193, - "tecCANT_ACCEPT_OWN_NFTOKEN_OFFER": 158, - "tecCLAIM": 100, - "tecCRYPTOCONDITION_ERROR": 146, - "tecDIR_FULL": 121, - "tecDST_TAG_NEEDED": 143, - "tecDUPLICATE": 149, - "tecEMPTY_DID": 187, - "tecEXPIRED": 148, - "tecFAILED_PROCESSING": 105, - "tecFROZEN": 137, - "tecHAS_OBLIGATIONS": 151, - "tecINCOMPLETE": 169, - "tecINSUFFICIENT_FUNDS": 159, - "tecINSUFFICIENT_PAYMENT": 161, - "tecINSUFFICIENT_RESERVE": 141, - "tecINSUFF_FEE": 136, - "tecINSUF_RESERVE_LINE": 122, - "tecINSUF_RESERVE_OFFER": 123, - "tecINTERNAL": 144, - "tecINVALID_UPDATE_TIME": 188, - "tecINVARIANT_FAILED": 147, - "tecKILLED": 150, - "tecLOCKED": 192, - "tecMAX_SEQUENCE_REACHED": 154, - "tecNEED_MASTER_KEY": 142, - "tecNFTOKEN_BUY_SELL_MISMATCH": 156, - "tecNFTOKEN_OFFER_TYPE_MISMATCH": 157, - "tecNO_ALTERNATIVE_KEY": 130, - "tecNO_AUTH": 134, - "tecNO_DST": 124, - "tecNO_DST_INSUF_XRP": 125, - "tecNO_ENTRY": 140, - "tecNO_ISSUER": 133, - "tecNO_LINE": 135, - "tecNO_LINE_INSUF_RESERVE": 126, - "tecNO_LINE_REDUNDANT": 127, - "tecNO_PERMISSION": 139, - "tecNO_REGULAR_KEY": 131, - "tecNO_SUITABLE_NFTOKEN_PAGE": 155, - "tecNO_TARGET": 138, - "tecOBJECT_NOT_FOUND": 160, - "tecOVERSIZE": 145, - "tecOWNERS": 132, - "tecPATH_DRY": 128, - "tecPATH_PARTIAL": 101, - "tecTOKEN_PAIR_NOT_FOUND": 189, - "tecTOO_SOON": 152, - "tecUNFUNDED": 129, - "tecUNFUNDED_ADD": 102, - "tecUNFUNDED_AMM": 162, - "tecUNFUNDED_OFFER": 103, - "tecUNFUNDED_PAYMENT": 104, - "tecXCHAIN_ACCOUNT_CREATE_PAST": 181, - "tecXCHAIN_ACCOUNT_CREATE_TOO_MANY": 182, - "tecXCHAIN_BAD_CLAIM_ID": 172, - "tecXCHAIN_BAD_PUBLIC_KEY_ACCOUNT_PAIR": 185, - "tecXCHAIN_BAD_TRANSFER_ISSUE": 170, - "tecXCHAIN_CLAIM_NO_QUORUM": 173, - "tecXCHAIN_CREATE_ACCOUNT_DISABLED": 186, - "tecXCHAIN_CREATE_ACCOUNT_NONXRP_ISSUE": 175, - "tecXCHAIN_INSUFF_CREATE_AMOUNT": 180, - "tecXCHAIN_NO_CLAIM_ID": 171, - "tecXCHAIN_NO_SIGNERS_LIST": 178, - "tecXCHAIN_PAYMENT_FAILED": 183, - "tecXCHAIN_PROOF_UNKNOWN_KEY": 174, - "tecXCHAIN_REWARD_MISMATCH": 177, - "tecXCHAIN_SELF_COMMIT": 184, - "tecXCHAIN_SENDING_ACCOUNT_MISMATCH": 179, - "tecXCHAIN_WRONG_CHAIN": 176, - "tefALREADY": -198, - "tefBAD_ADD_AUTH": -197, - "tefBAD_AUTH": -196, - "tefBAD_AUTH_MASTER": -183, - "tefBAD_LEDGER": -195, - "tefBAD_QUORUM": -185, - "tefBAD_SIGNATURE": -186, - "tefCREATED": -194, - "tefEXCEPTION": -193, - "tefFAILURE": -199, - "tefINTERNAL": -192, - "tefINVALID_LEDGER_FIX_TYPE": -178, - "tefINVARIANT_FAILED": -182, - "tefMASTER_DISABLED": -188, - "tefMAX_LEDGER": -187, - "tefNFTOKEN_IS_NOT_TRANSFERABLE": -179, - "tefNOT_MULTI_SIGNING": -184, - "tefNO_AUTH_REQUIRED": -191, - "tefNO_TICKET": -180, - "tefPAST_SEQ": -190, - "tefTOO_BIG": -181, - "tefWRONG_PRIOR": -189, + "telLOCAL_ERROR": -399, "telBAD_DOMAIN": -398, "telBAD_PATH_COUNT": -397, "telBAD_PUBLIC_KEY": -396, + "telFAILED_PROCESSING": -395, + "telINSUF_FEE_P": -394, + "telNO_DST_PARTIAL": -393, "telCAN_NOT_QUEUE": -392, "telCAN_NOT_QUEUE_BALANCE": -391, - "telCAN_NOT_QUEUE_BLOCKED": -389, "telCAN_NOT_QUEUE_BLOCKS": -390, + "telCAN_NOT_QUEUE_BLOCKED": -389, "telCAN_NOT_QUEUE_FEE": -388, "telCAN_NOT_QUEUE_FULL": -387, - "telENV_RPC_FAILED": -383, - "telFAILED_PROCESSING": -395, - "telINSUF_FEE_P": -394, - "telLOCAL_ERROR": -399, - "telNETWORK_ID_MAKES_TX_NON_CANONICAL": -384, - "telNO_DST_PARTIAL": -393, - "telREQUIRES_NETWORK_ID": -385, "telWRONG_NETWORK": -386, - "temARRAY_EMPTY": -253, - "temARRAY_TOO_LARGE": -252, - "temBAD_AMM_TOKENS": -261, + "telREQUIRES_NETWORK_ID": -385, + "telNETWORK_ID_MAKES_TX_NON_CANONICAL": -384, + "telENV_RPC_FAILED": -383, + + "temMALFORMED": -299, "temBAD_AMOUNT": -298, "temBAD_CURRENCY": -297, "temBAD_EXPIRATION": -296, "temBAD_FEE": -295, "temBAD_ISSUER": -294, "temBAD_LIMIT": -293, - "temBAD_NFTOKEN_TRANSFER_FEE": -262, "temBAD_OFFER": -292, "temBAD_PATH": -291, "temBAD_PATH_LOOP": -290, - "temBAD_QUORUM": -271, "temBAD_REGKEY": -289, "temBAD_SEND_XRP_LIMIT": -288, "temBAD_SEND_XRP_MAX": -287, @@ -3009,133 +2998,215 @@ "temBAD_SEND_XRP_PATHS": -284, "temBAD_SEQUENCE": -283, "temBAD_SIGNATURE": -282, - "temBAD_SIGNER": -272, "temBAD_SRC_ACCOUNT": -281, - "temBAD_TICK_SIZE": -269, - "temBAD_TRANSFER_FEE": -251, "temBAD_TRANSFER_RATE": -280, - "temBAD_WEIGHT": -270, - "temCANNOT_PREAUTH_SELF": -267, - "temDISABLED": -273, "temDST_IS_SRC": -279, "temDST_NEEDED": -278, - "temEMPTY_DID": -254, "temINVALID": -277, - "temINVALID_ACCOUNT_ID": -268, - "temINVALID_COUNT": -266, "temINVALID_FLAG": -276, - "temMALFORMED": -299, "temREDUNDANT": -275, "temRIPPLE_EMPTY": -274, - "temSEQ_AND_TICKET": -263, + "temDISABLED": -273, + "temBAD_SIGNER": -272, + "temBAD_QUORUM": -271, + "temBAD_WEIGHT": -270, + "temBAD_TICK_SIZE": -269, + "temINVALID_ACCOUNT_ID": -268, + "temCANNOT_PREAUTH_SELF": -267, + "temINVALID_COUNT": -266, "temUNCERTAIN": -265, "temUNKNOWN": -264, + "temSEQ_AND_TICKET": -263, + "temBAD_NFTOKEN_TRANSFER_FEE": -262, + "temBAD_AMM_TOKENS": -261, + "temXCHAIN_EQUAL_DOOR_ACCOUNTS": -260, "temXCHAIN_BAD_PROOF": -259, "temXCHAIN_BRIDGE_BAD_ISSUES": -258, + "temXCHAIN_BRIDGE_NONDOOR_OWNER": -257, "temXCHAIN_BRIDGE_BAD_MIN_ACCOUNT_CREATE_AMOUNT": -256, "temXCHAIN_BRIDGE_BAD_REWARD_AMOUNT": -255, - "temXCHAIN_BRIDGE_NONDOOR_OWNER": -257, - "temXCHAIN_EQUAL_DOOR_ACCOUNTS": -260, + "temEMPTY_DID": -254, + "temARRAY_EMPTY": -253, + "temARRAY_TOO_LARGE": -252, + "temBAD_TRANSFER_FEE": -251, + "temINVALID_BATCH": -250, + + "tefFAILURE": -199, + "tefALREADY": -198, + "tefBAD_ADD_AUTH": -197, + "tefBAD_AUTH": -196, + "tefBAD_LEDGER": -195, + "tefCREATED": -194, + "tefEXCEPTION": -193, + "tefINTERNAL": -192, + "tefNO_AUTH_REQUIRED": -191, + "tefPAST_SEQ": -190, + "tefWRONG_PRIOR": -189, + "tefMASTER_DISABLED": -188, + "tefMAX_LEDGER": -187, + "tefBAD_SIGNATURE": -186, + "tefBAD_QUORUM": -185, + "tefNOT_MULTI_SIGNING": -184, + "tefBAD_AUTH_MASTER": -183, + "tefINVARIANT_FAILED": -182, + "tefTOO_BIG": -181, + "tefNO_TICKET": -180, + "tefNFTOKEN_IS_NOT_TRANSFERABLE": -179, + "tefINVALID_LEDGER_FIX_TYPE": -178, + + "terRETRY": -99, "terFUNDS_SPENT": -98, "terINSUF_FEE_B": -97, - "terLAST": -91, "terNO_ACCOUNT": -96, - "terNO_AMM": -87, "terNO_AUTH": -95, "terNO_LINE": -94, - "terNO_RIPPLE": -90, "terOWNERS": -93, "terPRE_SEQ": -92, - "terPRE_TICKET": -88, + "terLAST": -91, + "terNO_RIPPLE": -90, "terQUEUED": -89, - "terRETRY": -99, - "tesSUCCESS": 0 + "terPRE_TICKET": -88, + "terNO_AMM": -87, + + "tesSUCCESS": 0, + + "tecCLAIM": 100, + "tecPATH_PARTIAL": 101, + "tecUNFUNDED_ADD": 102, + "tecUNFUNDED_OFFER": 103, + "tecUNFUNDED_PAYMENT": 104, + "tecFAILED_PROCESSING": 105, + "tecDIR_FULL": 121, + "tecINSUF_RESERVE_LINE": 122, + "tecINSUF_RESERVE_OFFER": 123, + "tecNO_DST": 124, + "tecNO_DST_INSUF_XRP": 125, + "tecNO_LINE_INSUF_RESERVE": 126, + "tecNO_LINE_REDUNDANT": 127, + "tecPATH_DRY": 128, + "tecUNFUNDED": 129, + "tecNO_ALTERNATIVE_KEY": 130, + "tecNO_REGULAR_KEY": 131, + "tecOWNERS": 132, + "tecNO_ISSUER": 133, + "tecNO_AUTH": 134, + "tecNO_LINE": 135, + "tecINSUFF_FEE": 136, + "tecFROZEN": 137, + "tecNO_TARGET": 138, + "tecNO_PERMISSION": 139, + "tecNO_ENTRY": 140, + "tecINSUFFICIENT_RESERVE": 141, + "tecNEED_MASTER_KEY": 142, + "tecDST_TAG_NEEDED": 143, + "tecINTERNAL": 144, + "tecOVERSIZE": 145, + "tecCRYPTOCONDITION_ERROR": 146, + "tecINVARIANT_FAILED": 147, + "tecEXPIRED": 148, + "tecDUPLICATE": 149, + "tecKILLED": 150, + "tecHAS_OBLIGATIONS": 151, + "tecTOO_SOON": 152, + "tecHOOK_REJECTED": 153, + "tecMAX_SEQUENCE_REACHED": 154, + "tecNO_SUITABLE_NFTOKEN_PAGE": 155, + "tecNFTOKEN_BUY_SELL_MISMATCH": 156, + "tecNFTOKEN_OFFER_TYPE_MISMATCH": 157, + "tecCANT_ACCEPT_OWN_NFTOKEN_OFFER": 158, + "tecINSUFFICIENT_FUNDS": 159, + "tecOBJECT_NOT_FOUND": 160, + "tecINSUFFICIENT_PAYMENT": 161, + "tecUNFUNDED_AMM": 162, + "tecAMM_BALANCE": 163, + "tecAMM_FAILED": 164, + "tecAMM_INVALID_TOKENS": 165, + "tecAMM_EMPTY": 166, + "tecAMM_NOT_EMPTY": 167, + "tecAMM_ACCOUNT": 168, + "tecINCOMPLETE": 169, + "tecXCHAIN_BAD_TRANSFER_ISSUE": 170, + "tecXCHAIN_NO_CLAIM_ID": 171, + "tecXCHAIN_BAD_CLAIM_ID": 172, + "tecXCHAIN_CLAIM_NO_QUORUM": 173, + "tecXCHAIN_PROOF_UNKNOWN_KEY": 174, + "tecXCHAIN_CREATE_ACCOUNT_NONXRP_ISSUE": 175, + "tecXCHAIN_WRONG_CHAIN": 176, + "tecXCHAIN_REWARD_MISMATCH": 177, + "tecXCHAIN_NO_SIGNERS_LIST": 178, + "tecXCHAIN_SENDING_ACCOUNT_MISMATCH": 179, + "tecXCHAIN_INSUFF_CREATE_AMOUNT": 180, + "tecXCHAIN_ACCOUNT_CREATE_PAST": 181, + "tecXCHAIN_ACCOUNT_CREATE_TOO_MANY": 182, + "tecXCHAIN_PAYMENT_FAILED": 183, + "tecXCHAIN_SELF_COMMIT": 184, + "tecXCHAIN_BAD_PUBLIC_KEY_ACCOUNT_PAIR": 185, + "tecXCHAIN_CREATE_ACCOUNT_DISABLED": 186, + "tecEMPTY_DID": 187, + "tecINVALID_UPDATE_TIME": 188, + "tecTOKEN_PAIR_NOT_FOUND": 189, + "tecARRAY_EMPTY": 190, + "tecARRAY_TOO_LARGE": 191, + "tecLOCKED": 192, + "tecBAD_CREDENTIALS": 193 }, "TRANSACTION_TYPES": { - "AMMBid": 39, - "AMMCreate": 35, - "AMMDelete": 40, - "AMMDeposit": 36, - "AMMVote": 38, - "AMMWithdraw": 37, - "AccountDelete": 21, - "AccountSet": 3, - "CheckCancel": 18, - "CheckCash": 17, - "CheckCreate": 16, - "Clawback": 30, - "CredentialAccept": 59, - "CredentialCreate": 58, - "CredentialDelete": 60, - "DIDDelete": 50, - "DIDSet": 49, - "DepositPreauth": 19, - "EnableAmendment": 100, - "EscrowCancel": 4, + "Invalid": -1, + "Payment": 0, "EscrowCreate": 1, "EscrowFinish": 2, - "Invalid": -1, - "LedgerStateFix": 53, - "MPTokenAuthorize": 57, - "MPTokenIssuanceCreate": 54, - "MPTokenIssuanceDestroy": 55, - "MPTokenIssuanceSet": 56, - "NFTokenAcceptOffer": 29, - "NFTokenBurn": 26, - "NFTokenCancelOffer": 28, - "NFTokenCreateOffer": 27, - "NFTokenMint": 25, - "OfferCancel": 8, + "AccountSet": 3, + "EscrowCancel": 4, + "SetRegularKey": 5, "OfferCreate": 7, - "OracleDelete": 52, - "OracleSet": 51, - "Payment": 0, - "PaymentChannelClaim": 15, + "OfferCancel": 8, + "TicketCreate": 10, + "SignerListSet": 12, "PaymentChannelCreate": 13, "PaymentChannelFund": 14, - "SetFee": 101, - "SetRegularKey": 5, - "SignerListSet": 12, - "TicketCreate": 10, + "PaymentChannelClaim": 15, + "CheckCreate": 16, + "CheckCash": 17, + "CheckCancel": 18, + "DepositPreauth": 19, "TrustSet": 20, - "UNLModify": 102, + "AccountDelete": 21, + "NFTokenMint": 25, + "NFTokenBurn": 26, + "NFTokenCreateOffer": 27, + "NFTokenCancelOffer": 28, + "NFTokenAcceptOffer": 29, + "Clawback": 30, + "AMMClawback": 31, + "AMMCreate": 35, + "AMMDeposit": 36, + "AMMWithdraw": 37, + "AMMVote": 38, + "AMMBid": 39, + "AMMDelete": 40, + "XChainCreateClaimID": 41, + "XChainCommit": 42, + "XChainClaim": 43, "XChainAccountCreateCommit": 44, - "XChainAddAccountCreateAttestation": 46, "XChainAddClaimAttestation": 45, - "XChainClaim": 43, - "XChainCommit": 42, + "XChainAddAccountCreateAttestation": 46, + "XChainModifyBridge": 47, "XChainCreateBridge": 48, - "XChainCreateClaimID": 41, - "XChainModifyBridge": 47 - }, - "TYPES": { - "AccountID": 8, - "Amount": 6, - "Blob": 7, - "Currency": 26, - "Done": -1, - "Hash128": 4, - "Hash160": 17, - "Hash192": 21, - "Hash256": 5, - "Issue": 24, - "LedgerEntry": 10002, - "Metadata": 10004, - "NotPresent": 0, - "PathSet": 18, - "STArray": 15, - "STObject": 14, - "Transaction": 10001, - "UInt16": 1, - "UInt32": 2, - "UInt384": 22, - "UInt512": 23, - "UInt64": 3, - "UInt8": 16, - "UInt96": 20, - "Unknown": -2, - "Validation": 10003, - "Vector256": 19, - "XChainBridge": 25 + "DIDSet": 49, + "DIDDelete": 50, + "OracleSet": 51, + "OracleDelete": 52, + "LedgerStateFix": 53, + "MPTokenIssuanceCreate": 54, + "MPTokenIssuanceDestroy": 55, + "MPTokenIssuanceSet": 56, + "MPTokenAuthorize": 57, + "CredentialCreate": 58, + "CredentialAccept": 59, + "CredentialDelete": 60, + "Batch": 61, + "EnableAmendment": 100, + "SetFee": 101, + "UNLModify": 102 } -} \ No newline at end of file +} diff --git a/xrpl/core/binarycodec/main.py b/xrpl/core/binarycodec/main.py index a2b5cec86..c8cf0d8b0 100644 --- a/xrpl/core/binarycodec/main.py +++ b/xrpl/core/binarycodec/main.py @@ -8,10 +8,7 @@ from typing_extensions import Final from xrpl.core.binarycodec.binary_wrappers.binary_parser import BinaryParser -from xrpl.core.binarycodec.types.account_id import AccountID -from xrpl.core.binarycodec.types.hash256 import Hash256 -from xrpl.core.binarycodec.types.st_object import STObject -from xrpl.core.binarycodec.types.uint64 import UInt64 +from xrpl.core.binarycodec.types import AccountID, Hash256, STObject, UInt32, UInt64 def _num_to_bytes(num: int) -> bytes: @@ -21,6 +18,7 @@ def _num_to_bytes(num: int) -> bytes: _TRANSACTION_SIGNATURE_PREFIX: Final[bytes] = _num_to_bytes(0x53545800) _PAYMENT_CHANNEL_CLAIM_PREFIX: Final[bytes] = _num_to_bytes(0x434C4D00) _TRANSACTION_MULTISIG_PREFIX: Final[bytes] = _num_to_bytes(0x534D5400) +_BATCH_PREFIX: Final[bytes] = _num_to_bytes(0x42434800) def encode(json: Dict[str, Any]) -> str: @@ -73,6 +71,28 @@ def encode_for_signing_claim(json: Dict[str, Any]) -> str: return buffer.hex().upper() +def encode_for_signing_batch(json: Dict[str, Any]) -> str: + """ + Encode a Batch transaction's data to be signed. + + Args: + json: A JSON-like dictionary representation of Batch data. + + Returns: + The binary-encoded Batch data, ready to be signed. + """ + prefix = _BATCH_PREFIX + flags = UInt32.from_value(json["flags"]) + transaction_ids = json["transaction_ids"] + len_transaction_ids = UInt32.from_value(len(transaction_ids)) + + buffer = prefix + bytes(flags) + bytes(len_transaction_ids) + for tx in transaction_ids: + buffer += bytes(Hash256.from_value(tx)) + + return buffer.hex().upper() + + def encode_for_multisigning(json: Dict[str, Any], signing_account: str) -> str: """ Encode a transaction into binary format in preparation for providing one diff --git a/xrpl/models/base_model.py b/xrpl/models/base_model.py index ddaa1bafe..7cc2960bc 100644 --- a/xrpl/models/base_model.py +++ b/xrpl/models/base_model.py @@ -39,6 +39,7 @@ "amm": "AMM", "did": "DID", "id": "ID", + "ids": "IDs", "lp": "LP", "mptoken": "MPToken", "nftoken": "NFToken", diff --git a/xrpl/models/transactions/__init__.py b/xrpl/models/transactions/__init__.py index e59363d0d..15d0d2e5e 100644 --- a/xrpl/models/transactions/__init__.py +++ b/xrpl/models/transactions/__init__.py @@ -24,6 +24,7 @@ AMMWithdrawFlag, AMMWithdrawFlagInterface, ) +from xrpl.models.transactions.batch import Batch, BatchFlag, BatchFlagInterface from xrpl.models.transactions.check_cancel import CheckCancel from xrpl.models.transactions.check_cash import CheckCash from xrpl.models.transactions.check_create import CheckCreate @@ -37,6 +38,7 @@ from xrpl.models.transactions.escrow_cancel import EscrowCancel from xrpl.models.transactions.escrow_create import EscrowCreate from xrpl.models.transactions.escrow_finish import EscrowFinish +from xrpl.models.transactions.ledger_state_fix import LedgerStateFix from xrpl.models.transactions.metadata import TransactionMetadata from xrpl.models.transactions.mptoken_authorize import ( MPTokenAuthorize, @@ -86,7 +88,13 @@ from xrpl.models.transactions.set_regular_key import SetRegularKey from xrpl.models.transactions.signer_list_set import SignerEntry, SignerListSet from xrpl.models.transactions.ticket_create import TicketCreate -from xrpl.models.transactions.transaction import Memo, Signer, Transaction +from xrpl.models.transactions.transaction import ( + Memo, + Signer, + Transaction, + TransactionFlag, + TransactionFlagInterface, +) from xrpl.models.transactions.trust_set import ( TrustSet, TrustSetFlag, @@ -128,6 +136,9 @@ "AMMWithdrawFlag", "AMMWithdrawFlagInterface", "AuthAccount", + "Batch", + "BatchFlag", + "BatchFlagInterface", "CheckCancel", "CheckCash", "CheckCreate", @@ -141,6 +152,7 @@ "EscrowCancel", "EscrowCreate", "EscrowFinish", + "LedgerStateFix", "Memo", "MPTokenAuthorize", "MPTokenAuthorizeFlag", @@ -181,6 +193,8 @@ "SignerListSet", "TicketCreate", "Transaction", + "TransactionFlag", + "TransactionFlagInterface", "TransactionMetadata", "TrustSet", "TrustSetFlag", diff --git a/xrpl/models/transactions/batch.py b/xrpl/models/transactions/batch.py new file mode 100644 index 000000000..538a8a006 --- /dev/null +++ b/xrpl/models/transactions/batch.py @@ -0,0 +1,110 @@ +"""Model for Batch transaction type.""" + +from __future__ import annotations + +from dataclasses import dataclass, field +from enum import Enum +from typing import Any, Dict, List, Optional, Type + +from typing_extensions import Self + +from xrpl.models.flags import FlagInterface +from xrpl.models.nested_model import NestedModel +from xrpl.models.required import REQUIRED +from xrpl.models.transactions.transaction import Signer, Transaction +from xrpl.models.transactions.types import TransactionType +from xrpl.models.utils import KW_ONLY_DATACLASS, require_kwargs_on_init + + +class BatchFlag(int, Enum): + """ + Transactions of the Batch type support additional values in the Flags field. + This enum represents those options. + """ + + TF_ALL_OR_NOTHING = 0x00010000 + + TF_ONLY_ONE = 0x00020000 + + TF_UNTIL_FAILURE = 0x00040000 + + TF_INDEPENDENT = 0x00080000 + + +class BatchFlagInterface(FlagInterface): + """ + Transactions of the Batch type support additional values in the Flags field. + This TypedDict represents those options. + """ + + TF_ALL_OR_NOTHING: bool + TF_ONLY_ONE: bool + TF_UNTIL_FAILURE: bool + TF_INDEPENDENT: bool + + +@require_kwargs_on_init +@dataclass(frozen=True, **KW_ONLY_DATACLASS) +class BatchSigner(NestedModel): + """Represents a Batch signer.""" + + account: str = REQUIRED # type: ignore + + signing_pub_key: Optional[str] = None + + txn_signature: Optional[str] = None + + signers: Optional[List[Signer]] = None + + +@require_kwargs_on_init +@dataclass(frozen=True, **KW_ONLY_DATACLASS) +class Batch(Transaction): + """Represents a Batch transaction.""" + + raw_transactions: List[Transaction] = REQUIRED # type: ignore + transaction_ids: Optional[List[str]] = None + batch_signers: Optional[List[BatchSigner]] = None + + transaction_type: TransactionType = field( + default=TransactionType.BATCH, + init=False, + ) + + @classmethod + def from_dict(cls: Type[Self], value: Dict[str, Any]) -> Self: + """ + Construct a new Batch from a dictionary of parameters. + + Args: + value: The value to construct the Batch from. + + Returns: + A new Batch object, constructed using the given parameters. + + Raises: + XRPLModelException: If the dictionary provided is invalid. + """ + new_value = {**value} + new_value["raw_transactions"] = [ + ( + tx["raw_transaction"] + if isinstance(tx, dict) and "raw_transaction" in tx + else tx + ) + for tx in value["raw_transactions"] + ] + return super(Transaction, cls).from_dict(new_value) + + def to_dict(self: Self) -> Dict[str, Any]: + """ + Returns the dictionary representation of a Batch. + + Returns: + The dictionary representation of a Batch. + """ + tx_dict = super().to_dict() + tx_dict["raw_transactions"] = [ + {"raw_transaction": tx} for tx in tx_dict["raw_transactions"] + ] + return tx_dict diff --git a/xrpl/models/transactions/ledger_state_fix.py b/xrpl/models/transactions/ledger_state_fix.py new file mode 100644 index 000000000..fbc03dd9d --- /dev/null +++ b/xrpl/models/transactions/ledger_state_fix.py @@ -0,0 +1,25 @@ +"""Model for LedgerStateFix transaction type.""" + +from __future__ import annotations + +from dataclasses import dataclass, field +from typing import Optional + +from xrpl.models.required import REQUIRED +from xrpl.models.transactions.transaction import Transaction +from xrpl.models.transactions.types import TransactionType +from xrpl.models.utils import KW_ONLY_DATACLASS, require_kwargs_on_init + + +@require_kwargs_on_init +@dataclass(frozen=True, **KW_ONLY_DATACLASS) +class LedgerStateFix(Transaction): + """Represents a LedgerStateFix transaction.""" + + ledger_fix_type: int = REQUIRED # type: ignore + owner: Optional[str] = None + + transaction_type: TransactionType = field( + default=TransactionType.LEDGER_STATE_FIX, + init=False, + ) diff --git a/xrpl/models/transactions/transaction.py b/xrpl/models/transactions/transaction.py index 9f9556593..53a7faa62 100644 --- a/xrpl/models/transactions/transaction.py +++ b/xrpl/models/transactions/transaction.py @@ -3,6 +3,7 @@ from __future__ import annotations from dataclasses import dataclass +from enum import Enum from hashlib import sha512 from typing import Any, Dict, List, Optional, Type, Union @@ -13,7 +14,11 @@ from xrpl.models.amounts.mpt_amount import MPTAmount from xrpl.models.base_model import ABBREVIATIONS, BaseModel from xrpl.models.exceptions import XRPLModelException -from xrpl.models.flags import check_false_flag_definition, interface_to_flag_list +from xrpl.models.flags import ( + FlagInterface, + check_false_flag_definition, + interface_to_flag_list, +) from xrpl.models.nested_model import NestedModel from xrpl.models.requests import PathStep from xrpl.models.required import REQUIRED @@ -156,6 +161,24 @@ class Signer(NestedModel): """ +class TransactionFlag(int, Enum): + """ + Transactions of the Transaction type support additional values in the Flags field. + This enum represents those options. + """ + + TF_INNER_BATCH_TXN = 0x40000000 + + +class TransactionFlagInterface(FlagInterface): + """ + Transactions of the Transaction type support additional values in the Flags field. + This TypedDict represents those options. + """ + + TF_INNER_BATCH_TXN: bool + + @require_kwargs_on_init @dataclass(frozen=True, **KW_ONLY_DATACLASS) class Transaction(BaseModel): @@ -414,7 +437,11 @@ def get_hash(self: Self) -> str: Raises: XRPLModelException: if the Transaction is unsigned. """ - if self.txn_signature is None and self.signers is None: + if ( + self.txn_signature is None + and self.signers is None + and not self.has_flag(TransactionFlag.TF_INNER_BATCH_TXN) + ): raise XRPLModelException( "Cannot get the hash from an unsigned Transaction." ) diff --git a/xrpl/models/transactions/types/transaction_type.py b/xrpl/models/transactions/types/transaction_type.py index 1d2119a90..c46b76f1d 100644 --- a/xrpl/models/transactions/types/transaction_type.py +++ b/xrpl/models/transactions/types/transaction_type.py @@ -14,6 +14,7 @@ class TransactionType(str, Enum): AMM_DEPOSIT = "AMMDeposit" AMM_VOTE = "AMMVote" AMM_WITHDRAW = "AMMWithdraw" + BATCH = "Batch" CHECK_CANCEL = "CheckCancel" CHECK_CASH = "CheckCash" CHECK_CREATE = "CheckCreate" @@ -27,6 +28,7 @@ class TransactionType(str, Enum): ESCROW_CANCEL = "EscrowCancel" ESCROW_CREATE = "EscrowCreate" ESCROW_FINISH = "EscrowFinish" + LEDGER_STATE_FIX = "LedgerStateFix" MPTOKEN_AUTHORIZE = "MPTokenAuthorize" MPTOKEN_ISSUANCE_CREATE = "MPTokenIssuanceCreate" MPTOKEN_ISSUANCE_DESTROY = "MPTokenIssuanceDestroy" diff --git a/xrpl/transaction/__init__.py b/xrpl/transaction/__init__.py index 7ae153d97..85300ea34 100644 --- a/xrpl/transaction/__init__.py +++ b/xrpl/transaction/__init__.py @@ -4,6 +4,10 @@ XRPLReliableSubmissionException, transaction_json_to_binary_codec_form, ) +from xrpl.transaction.batch_signers import ( + combine_batch_signers, + sign_multiaccount_batch, +) from xrpl.transaction.main import ( _calculate_fee_per_transaction_type, autofill, @@ -24,6 +28,8 @@ "submit_and_wait", "transaction_json_to_binary_codec_form", "multisign", + "sign_multiaccount_batch", + "combine_batch_signers", "XRPLReliableSubmissionException", "_calculate_fee_per_transaction_type", ] diff --git a/xrpl/transaction/batch_signers.py b/xrpl/transaction/batch_signers.py new file mode 100644 index 000000000..aa5df1de0 --- /dev/null +++ b/xrpl/transaction/batch_signers.py @@ -0,0 +1,135 @@ +"""Helper functions for signing multi-account Batch transactions.""" + +from typing import Any, Dict, List, Optional, Union, cast + +from xrpl.constants import XRPLException +from xrpl.core.addresscodec.codec import decode_classic_address +from xrpl.core.binarycodec import encode, encode_for_signing_batch +from xrpl.core.keypairs import sign +from xrpl.models.transactions import Batch, Signer, Transaction +from xrpl.models.transactions.batch import BatchSigner +from xrpl.wallet import Wallet + + +def sign_multiaccount_batch( + wallet: Wallet, transaction: Batch, multisign: Optional[bool] = False +) -> Batch: + """ + Sign a multi-account Batch transaction. + + Args: + wallet: Wallet instance. + transaction: The Batch transaction to sign. + multisign: Specify true/false to use multisign. Defaults to False. + + Raises: + XRPLException: If the wallet signing the transaction doesn't have an account in + the Batch. + + Returns: + The Batch transaction with the BatchSigner included. + """ + involved_accounts = set(tx.account for tx in transaction.raw_transactions) + if wallet.address not in involved_accounts: + raise XRPLException("Must be signing for an address included in the Batch.") + + fields_to_sign: Dict[str, Any] = { + "flags": transaction.flags, + "transaction_ids": transaction.transaction_ids, + } + if multisign: + signer = Signer( + account=wallet.address, + signing_pub_key=wallet.public_key, + txn_signature=sign( + encode_for_signing_batch(fields_to_sign), wallet.private_key + ), + ) + batch_signer = BatchSigner(account=wallet.address, signers=[signer]) + else: + batch_signer = BatchSigner( + account=wallet.address, + signing_pub_key=wallet.public_key, + txn_signature=sign( + encode_for_signing_batch(fields_to_sign), wallet.private_key + ), + ) + + transaction_dict = transaction.to_dict() + transaction_dict["batch_signers"] = [batch_signer] + + return Batch.from_dict(transaction_dict) + + +def combine_batch_signers(transactions: List[Union[Batch, str]]) -> str: + """ + Takes several transactions with BatchSigners fields (in object or blob form) and + creates a single transaction with all BatchSigners that then gets signed and + returned. + + Args: + transactions: The transactions to combine `BatchSigners` values on. + + Raises: + XRPLException: If the list of transactions provided is invalid. + + Returns: + A single signed Transaction which has all BatchSigners from transactions within + it. + """ + if len(transactions) == 0: + raise XRPLException("There were 0 transactions to combine.") + + decoded_txs: List[Transaction] = [ + Transaction.from_blob(tx) if isinstance(tx, str) else tx for tx in transactions + ] + for tx in decoded_txs: + if tx.transaction_type != "Batch": + raise XRPLException("TransactionType must be `Batch`.") + batch = cast(Batch, tx) + if batch.batch_signers is None or len(batch.batch_signers) == 0: + raise XRPLException( + "For combining Batch transaction signatures, all transactions must " + "include a BatchSigners field containing an array of signatures." + ) + if ( + tx.signing_pub_key != "" + or tx.txn_signature is not None + or tx.signers is not None + ): + raise XRPLException("Transaction must be unsigned.") + + batch_txs = cast(List[Batch], decoded_txs) + _validate_batch_equivalence(batch_txs) + + return encode(_get_batch_with_all_signers(batch_txs).to_xrpl()) + + return "" + + +def _validate_batch_equivalence(transactions: List[Batch]) -> None: + example_tx = transactions[0] + for tx in transactions: + if ( + tx.flags != example_tx.flags + or tx.transaction_ids != example_tx.transaction_ids + ): + raise XRPLException( + "Flags and TransactionIDs are not the same for all provided " + "transactions." + ) + + +def _get_batch_with_all_signers(transactions: List[Batch]) -> Batch: + batch_signers = [ + signer + for tx in transactions + if tx.batch_signers is not None + for signer in tx.batch_signers + if signer.account != transactions[0].account + ] + batch_signers.sort( + key=lambda signer: decode_classic_address(signer.account).hex().upper() + ) + returned_tx_dict = transactions[0].to_dict() + return Batch.from_dict({**returned_tx_dict, "batch_signers": batch_signers}) diff --git a/xrpl/transaction/main.py b/xrpl/transaction/main.py index 05c8eb26d..63c2e961d 100644 --- a/xrpl/transaction/main.py +++ b/xrpl/transaction/main.py @@ -3,12 +3,16 @@ import asyncio from typing import Optional +from typing_extensions import TypeVar + from xrpl.asyncio.transaction import main from xrpl.clients.sync_client import SyncClient from xrpl.models.response import Response from xrpl.models.transactions.transaction import Transaction from xrpl.wallet.main import Wallet +T = TypeVar("T", bound=Transaction, default=Transaction) + def sign_and_submit( transaction: Transaction, @@ -78,11 +82,11 @@ def submit( def autofill_and_sign( - transaction: Transaction, + transaction: T, client: SyncClient, wallet: Wallet, check_fee: bool = True, -) -> Transaction: +) -> T: """ Signs a transaction locally, without trusting external rippled nodes. Autofills relevant fields. @@ -108,8 +112,8 @@ def autofill_and_sign( def autofill( - transaction: Transaction, client: SyncClient, signers_count: Optional[int] = None -) -> Transaction: + transaction: T, client: SyncClient, signers_count: Optional[int] = None +) -> T: """ Autofills fields in a transaction. This will set `sequence`, `fee`, and `last_ledger_sequence` according to the current state of the server this Client is