From 6da45b8a61023921dec7aa1bc4a5ca014811ca16 Mon Sep 17 00:00:00 2001 From: nkramer44 Date: Wed, 19 Jul 2023 12:02:06 -0400 Subject: [PATCH 01/10] Add Zero AccountSetFlag (#459) add zero AccountSetFlag --- .../xrpl4j/model/transactions/AccountSet.java | 8 +++- .../json/AccountSetJsonTests.java | 38 +++++++++++++++++ .../org/xrpl/xrpl4j/tests/AccountSetIT.java | 41 +++++++++++++++++++ 3 files changed, 85 insertions(+), 2 deletions(-) diff --git a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/AccountSet.java b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/AccountSet.java index b82f7b217..ace5b6994 100644 --- a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/AccountSet.java +++ b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/AccountSet.java @@ -205,7 +205,11 @@ default void checkTickSize() { * */ enum AccountSetFlag { - + /** + * This flag will do nothing but exists to accurately deserialize AccountSet transactions whose {@code SetFlag} + * or {@code ClearFlag} fields are zero. + */ + NONE(0), /** * Require a destination tag to send transactions to this account. */ @@ -274,7 +278,7 @@ enum AccountSetFlag { */ DISALLOW_INCOMING_TRUSTLINE(15); - int value; + final int value; AccountSetFlag(int value) { this.value = value; diff --git a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/json/AccountSetJsonTests.java b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/json/AccountSetJsonTests.java index ce88119d4..25ce81b17 100644 --- a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/json/AccountSetJsonTests.java +++ b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/json/AccountSetJsonTests.java @@ -153,4 +153,42 @@ public void testJsonWithEmptyFlags() throws JsonProcessingException, JSONExcepti assertCanSerializeAndDeserialize(accountSet, json); } + + @Test + void testJsonWithZeroClearFlagAndSetFlag() throws JSONException, JsonProcessingException { + AccountSet accountSet = AccountSet.builder() + .account(Address.of("rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn")) + .fee(XrpCurrencyAmount.ofDrops(12)) + .sequence(UnsignedInteger.valueOf(5)) + .domain("6578616D706C652E636F6D") + .setFlag(AccountSetFlag.NONE) + .messageKey("03AB40A0490F9B7ED8DF29D246BF2D6269820A0EE7742ACDD457BEA7C7D0931EDB") + .transferRate(UnsignedInteger.valueOf(1000000001)) + .tickSize(UnsignedInteger.valueOf(15)) + .clearFlag(AccountSetFlag.NONE) + .emailHash("f9879d71855b5ff21e4963273a886bfc") + .signingPublicKey( + PublicKey.fromBase16EncodedPublicKey("02356E89059A75438887F9FEE2056A2890DB82A68353BE9C0C0C8F89C0018B37FC") + ) + .mintAccount(Address.of("rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn")) + .build(); + + String json = "{\n" + + " \"TransactionType\":\"AccountSet\",\n" + + " \"Account\":\"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn\",\n" + + " \"Fee\":\"12\",\n" + + " \"Sequence\":5,\n" + + " \"Domain\":\"6578616D706C652E636F6D\",\n" + + " \"SetFlag\":0,\n" + + " \"MessageKey\":\"03AB40A0490F9B7ED8DF29D246BF2D6269820A0EE7742ACDD457BEA7C7D0931EDB\",\n" + + " \"TransferRate\":1000000001,\n" + + " \"TickSize\":15,\n" + + " \"ClearFlag\":0,\n" + + " \"SigningPubKey\" : \"02356E89059A75438887F9FEE2056A2890DB82A68353BE9C0C0C8F89C0018B37FC\",\n" + + " \"NFTokenMinter\" : \"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn\",\n" + + " \"EmailHash\":\"f9879d71855b5ff21e4963273a886bfc\"\n" + + "}"; + + assertCanSerializeAndDeserialize(accountSet, json); + } } diff --git a/xrpl4j-integration-tests/src/test/java/org/xrpl/xrpl4j/tests/AccountSetIT.java b/xrpl4j-integration-tests/src/test/java/org/xrpl/xrpl4j/tests/AccountSetIT.java index 7124e1825..0ea468489 100644 --- a/xrpl4j-integration-tests/src/test/java/org/xrpl/xrpl4j/tests/AccountSetIT.java +++ b/xrpl4j-integration-tests/src/test/java/org/xrpl/xrpl4j/tests/AccountSetIT.java @@ -33,6 +33,7 @@ import org.xrpl.xrpl4j.model.client.fees.FeeResult; import org.xrpl.xrpl4j.model.client.fees.FeeUtils; import org.xrpl.xrpl4j.model.client.transactions.SubmitResult; +import org.xrpl.xrpl4j.model.client.transactions.TransactionResult; import org.xrpl.xrpl4j.model.flags.AccountRootFlags; import org.xrpl.xrpl4j.model.flags.AccountSetTransactionFlags; import org.xrpl.xrpl4j.model.transactions.AccountSet; @@ -343,6 +344,46 @@ void disableMasterFailsWithNoSignerList() throws JsonRpcClientErrorException, Js logger.info("AccountSet SetFlag transaction failed successfully:"); } + @Test + void submitAndRetrieveAccountSetWithZeroClearFlagAndSetFlag() + throws JsonRpcClientErrorException, JsonProcessingException { + KeyPair keyPair = constructRandomAccount(); + + /////////////////////// + // Get validated account info and validate account state + AccountInfoResult accountInfo = this.scanForResult( + () -> this.getValidatedAccountInfo(keyPair.publicKey().deriveAddress()) + ); + + FeeResult feeResult = xrplClient.fee(); + AccountSet accountSet = AccountSet.builder() + .account(keyPair.publicKey().deriveAddress()) + .fee(FeeUtils.computeNetworkFees(feeResult).recommendedFee()) + .sequence(accountInfo.accountData().sequence()) + .setFlag(AccountSetFlag.NONE) + .clearFlag(AccountSetFlag.NONE) + .signingPublicKey(keyPair.publicKey()) + .build(); + + SingleSignedTransaction signedAccountSet = signatureService.sign( + keyPair.privateKey(), accountSet + ); + SubmitResult response = xrplClient.submit(signedAccountSet); + + assertThat(response.engineResult()).isEqualTo("tesSUCCESS"); + assertThat(signedAccountSet.hash()).isEqualTo(response.transactionResult().hash()); + logger.info( + "AccountSet transaction successful: https://testnet.xrpl.org/transactions/" + response.transactionResult().hash() + ); + + TransactionResult accountSetTransactionResult = this.scanForResult(() -> + this.getValidatedTransaction(signedAccountSet.hash(), AccountSet.class) + ); + + assertThat(accountSetTransactionResult.transaction().setFlag()).isNotEmpty().get().isEqualTo(AccountSetFlag.NONE); + assertThat(accountSetTransactionResult.transaction().clearFlag()).isNotEmpty().get().isEqualTo(AccountSetFlag.NONE); + } + ////////////////////// // Test Helpers ////////////////////// From 3f10844311504b6e9eae9218a95caa8ff5e42fd5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 1 Aug 2023 13:45:47 -0400 Subject: [PATCH 02/10] Bump maven-failsafe-plugin from 2.22.2 to 3.1.2 (#454) Bumps [maven-failsafe-plugin](https://github.com/apache/maven-surefire) from 2.22.2 to 3.1.2. - [Release notes](https://github.com/apache/maven-surefire/releases) - [Commits](https://github.com/apache/maven-surefire/compare/surefire-2.22.2...surefire-3.1.2) --- updated-dependencies: - dependency-name: org.apache.maven.plugins:maven-failsafe-plugin dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: nkramer44 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 526d9ef17..e03e63665 100644 --- a/pom.xml +++ b/pom.xml @@ -356,7 +356,7 @@ org.apache.maven.plugins maven-failsafe-plugin - 2.22.2 + 3.1.2 integration-tests From 648fd1f971436d8f8888967fc4ede92f0c8d3e60 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 1 Aug 2023 14:31:31 -0400 Subject: [PATCH 03/10] Bump assertj-core from 3.23.1 to 3.24.2 (#455) Bumps assertj-core from 3.23.1 to 3.24.2. --- updated-dependencies: - dependency-name: org.assertj:assertj-core dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: nkramer44 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index e03e63665..2aef23fc0 100644 --- a/pom.xml +++ b/pom.xml @@ -74,7 +74,7 @@ org.assertj assertj-core - 3.23.1 + 3.24.2 test From 9af32f0e5844a42914f9584d41f10b17d84d6822 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 1 Aug 2023 17:03:49 -0400 Subject: [PATCH 04/10] Bump caffeine from 2.8.8 to 2.9.3 (#457) Bumps [caffeine](https://github.com/ben-manes/caffeine) from 2.8.8 to 2.9.3. - [Release notes](https://github.com/ben-manes/caffeine/releases) - [Commits](https://github.com/ben-manes/caffeine/compare/v2.8.8...v2.9.3) --- updated-dependencies: - dependency-name: com.github.ben-manes.caffeine:caffeine dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: nkramer44 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 2aef23fc0..51ba98198 100644 --- a/pom.xml +++ b/pom.xml @@ -54,7 +54,7 @@ com.github.ben-manes.caffeine caffeine - 2.8.8 + 2.9.3 com.ripple.cryptoconditions From 100a5b349fa72ca50ebd4034465424c71028c395 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 3 Aug 2023 10:31:13 -0400 Subject: [PATCH 05/10] Bump jacoco-maven-plugin from 0.8.8 to 0.8.10 (#458) Bumps [jacoco-maven-plugin](https://github.com/jacoco/jacoco) from 0.8.8 to 0.8.10. - [Release notes](https://github.com/jacoco/jacoco/releases) - [Commits](https://github.com/jacoco/jacoco/compare/v0.8.8...v0.8.10) --- updated-dependencies: - dependency-name: org.jacoco:jacoco-maven-plugin dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: nkramer44 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 51ba98198..fb2dd8045 100644 --- a/pom.xml +++ b/pom.xml @@ -445,7 +445,7 @@ org.jacoco - 0.8.8 + 0.8.10 jacoco-maven-plugin From 27cb9ce22d1ce5aa00e1f03491d7ba4299d0388b Mon Sep 17 00:00:00 2001 From: nkramer44 Date: Fri, 4 Aug 2023 11:07:58 -0400 Subject: [PATCH 06/10] Support for NetworkID (#461) * update definitions.json * add NetworkId wrapper class, and add NetworkId as an Optional field in all Transactions. * add network_id to ServerInfo * remove old codec fields from data-driven-tests.json * pr feedback --- .../model/client/serverinfo/ServerInfo.java | 13 +- .../modules/NetworkIdDeserializer.java | 47 + .../jackson/modules/NetworkIdSerializer.java | 46 + .../model/transactions/Transaction.java | 3 + .../xrpl4j/model/transactions/Wrappers.java | 29 + .../src/main/resources/definitions.json | 1680 +++++++++++------ .../binary/BinarySerializationTests.java | 66 +- .../codec/binary/FieldHeaderCodecTest.java | 2 +- .../serverinfo/ServerInfoResultTest.java | 79 +- .../model/transactions/NetworkIdTest.java | 90 + .../json/AccountDeleteJsonTests.java | 3 + .../json/AccountSetJsonTests.java | 3 + .../transactions/json/CheckJsonTests.java | 7 + .../transactions/json/EscrowJsonTests.java | 7 + .../json/NfTokenAcceptOfferJsonTests.java | 3 + .../json/NfTokenBurnJsonTests.java | 3 + .../json/NfTokenCancelOfferJsonTests.java | 3 + .../json/NfTokenCreateOfferJsonTests.java | 3 + .../json/NfTokenMintJsonTests.java | 3 + .../transactions/json/OfferJsonTests.java | 5 + .../json/PaymentChannelJsonTests.java | 7 + .../transactions/json/PaymentJsonTests.java | 3 + .../json/SetRegularKeyJsonTest.java | 3 + .../json/SignerListSetJsonTests.java | 3 + .../json/TicketCreateJsonTest.java | 3 + .../transactions/json/TrustSetJsonTests.java | 3 + .../src/test/resources/data-driven-tests.json | 14 - 27 files changed, 1569 insertions(+), 562 deletions(-) create mode 100644 xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/jackson/modules/NetworkIdDeserializer.java create mode 100644 xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/jackson/modules/NetworkIdSerializer.java create mode 100644 xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/NetworkIdTest.java diff --git a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/client/serverinfo/ServerInfo.java b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/client/serverinfo/ServerInfo.java index fa5d01f0b..25eaa33e6 100644 --- a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/client/serverinfo/ServerInfo.java +++ b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/client/serverinfo/ServerInfo.java @@ -9,9 +9,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -37,6 +37,7 @@ import org.immutables.value.Value; import org.xrpl.xrpl4j.model.client.common.LedgerIndex; import org.xrpl.xrpl4j.model.transactions.Hash256; +import org.xrpl.xrpl4j.model.transactions.NetworkId; import org.xrpl.xrpl4j.model.transactions.XrpCurrencyAmount; import java.io.IOException; @@ -177,6 +178,14 @@ default boolean isLedgerInCompleteLedgers(final UnsignedLong ledgerIndex) { @JsonProperty("validation_quorum") Optional validationQuorum(); + /** + * The {@link NetworkId} of the network that this server is connected to. + * + * @return An optionally-present {@link NetworkId}. + */ + @JsonProperty("network_id") + Optional networkId(); + /** * Deserializes complete_ledgers field in the server_info response from hyphen-separated ledger indices to list of * range of UnsignedLong values. diff --git a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/jackson/modules/NetworkIdDeserializer.java b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/jackson/modules/NetworkIdDeserializer.java new file mode 100644 index 000000000..f247d66a2 --- /dev/null +++ b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/jackson/modules/NetworkIdDeserializer.java @@ -0,0 +1,47 @@ +package org.xrpl.xrpl4j.model.jackson.modules; + +/*- + * ========================LICENSE_START================================= + * xrpl4j :: core + * %% + * Copyright (C) 2020 - 2023 XRPL Foundation and its contributors + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * =========================LICENSE_END================================== + */ + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.deser.std.StdDeserializer; +import com.google.common.primitives.UnsignedInteger; +import org.xrpl.xrpl4j.model.transactions.NetworkId; + +import java.io.IOException; + +/** + * Custom Jackson deserializer for {@link NetworkId}s. + */ +public class NetworkIdDeserializer extends StdDeserializer { + + /** + * No-args constructor. + */ + public NetworkIdDeserializer() { + super(NetworkId.class); + } + + @Override + public NetworkId deserialize(JsonParser jsonParser, DeserializationContext ctxt) throws IOException { + return NetworkId.of(UnsignedInteger.valueOf(jsonParser.getValueAsLong())); + } +} diff --git a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/jackson/modules/NetworkIdSerializer.java b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/jackson/modules/NetworkIdSerializer.java new file mode 100644 index 000000000..947301b46 --- /dev/null +++ b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/jackson/modules/NetworkIdSerializer.java @@ -0,0 +1,46 @@ +package org.xrpl.xrpl4j.model.jackson.modules; + +/*- + * ========================LICENSE_START================================= + * xrpl4j :: core + * %% + * Copyright (C) 2020 - 2023 XRPL Foundation and its contributors + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * =========================LICENSE_END================================== + */ + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.ser.std.StdScalarSerializer; +import org.xrpl.xrpl4j.model.transactions.NetworkId; + +import java.io.IOException; + +/** + * Custom Jackson serializer for {@link NetworkId}s. + */ +public class NetworkIdSerializer extends StdScalarSerializer { + + /** + * No-args constructor. + */ + public NetworkIdSerializer() { + super(NetworkId.class); + } + + @Override + public void serialize(NetworkId networkId, JsonGenerator gen, SerializerProvider provider) throws IOException { + gen.writeNumber(networkId.value().longValue()); + } +} diff --git a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/Transaction.java b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/Transaction.java index ccbdbbd36..3eca5a090 100644 --- a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/Transaction.java +++ b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/Transaction.java @@ -198,4 +198,7 @@ default PublicKey signingPublicKey() { @JsonProperty("TxnSignature") Optional transactionSignature(); + @JsonProperty("NetworkID") + Optional networkId(); + } diff --git a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/Wrappers.java b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/Wrappers.java index a36145cd2..a59cd5267 100644 --- a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/Wrappers.java +++ b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/Wrappers.java @@ -37,6 +37,8 @@ import org.xrpl.xrpl4j.model.jackson.modules.Hash256Serializer; import org.xrpl.xrpl4j.model.jackson.modules.MarkerDeserializer; import org.xrpl.xrpl4j.model.jackson.modules.MarkerSerializer; +import org.xrpl.xrpl4j.model.jackson.modules.NetworkIdDeserializer; +import org.xrpl.xrpl4j.model.jackson.modules.NetworkIdSerializer; import org.xrpl.xrpl4j.model.jackson.modules.NfTokenIdDeserializer; import org.xrpl.xrpl4j.model.jackson.modules.NfTokenIdSerializer; import org.xrpl.xrpl4j.model.jackson.modules.NfTokenUriSerializer; @@ -350,6 +352,7 @@ public String toString() { * Construct {@link TransferFee} as a percentage value. * * @param percent of type {@link BigDecimal} + * * @return {@link TransferFee} */ static TransferFee ofPercent(BigDecimal percent) { @@ -374,4 +377,30 @@ public void validateBounds() { } + /** + * A wrapped {@link com.google.common.primitives.UnsignedInteger} containing a Network ID. + */ + @Value.Immutable + @Wrapped + @JsonSerialize(as = NetworkId.class, using = NetworkIdSerializer.class) + @JsonDeserialize(as = NetworkId.class, using = NetworkIdDeserializer.class) + abstract static class _NetworkId extends Wrapper implements Serializable { + + @Override + public String toString() { + return this.value().toString(); + } + + /** + * Construct a {@link NetworkId} from a {@code long}. The supplied value must be less than or equal to + * 4,294,967,295, the largest unsigned 32-bit integer. + * + * @param networkId A {@code long}. + * + * @return A {@link NetworkId}. + */ + public static NetworkId of(long networkId) { + return NetworkId.of(UnsignedInteger.valueOf(networkId)); + } + } } diff --git a/xrpl4j-core/src/main/resources/definitions.json b/xrpl4j-core/src/main/resources/definitions.json index 768ae28b3..1911bd47a 100644 --- a/xrpl4j-core/src/main/resources/definitions.json +++ b/xrpl4j-core/src/main/resources/definitions.json @@ -1,29 +1,33 @@ { "TYPES": { - "Validation": 10003, "Done": -1, + "Unknown": -2, + "NotPresent": 0, + "UInt16": 1, + "UInt32": 2, + "UInt64": 3, "Hash128": 4, + "Hash256": 5, + "Amount": 6, "Blob": 7, "AccountID": 8, - "Amount": 6, - "Hash256": 5, - "UInt8": 16, - "Vector256": 19, "STObject": 14, - "Unknown": -2, - "Transaction": 10001, + "STArray": 15, + "UInt8": 16, "Hash160": 17, "PathSet": 18, + "Vector256": 19, + "UInt96": 20, + "UInt192": 21, + "UInt384": 22, + "UInt512": 23, + "Issue": 24, + "Transaction": 10001, "LedgerEntry": 10002, - "UInt16": 1, - "NotPresent": 0, - "UInt64": 3, - "UInt32": 2, - "STArray": 15 + "Validation": 10003, + "Metadata": 10004 }, "LEDGER_ENTRY_TYPES": { - "Any": -3, - "Child": -2, "Invalid": -1, "AccountRoot": 97, "DirectoryNode": 100, @@ -36,13 +40,17 @@ "FeeSettings": 115, "Escrow": 117, "PayChannel": 120, - "DepositPreauth": 112, "Check": 67, - "Nickname": 110, - "Contract": 99, + "DepositPreauth": 112, + "NegativeUNL": 78, "NFTokenPage": 80, "NFTokenOffer": 55, - "NegativeUNL": 78 + "AMM": 121, + "Any": -3, + "Child": -2, + "Nickname": 110, + "Contract": 99, + "GeneratorMap": 103 }, "FIELDS": [ [ @@ -66,279 +74,279 @@ } ], [ - "LedgerEntryType", + "ObjectEndMarker", { "nth": 1, "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "UInt16" + "type": "STObject" } ], [ - "TransactionType", + "ArrayEndMarker", { - "nth": 2, + "nth": 1, "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "UInt16" + "type": "STArray" } ], [ - "SignerWeight", + "hash", { - "nth": 3, + "nth": 257, "isVLEncoded": false, - "isSerialized": true, - "isSigningField": true, - "type": "UInt16" + "isSerialized": false, + "isSigningField": false, + "type": "Hash256" } ], [ - "TransferFee", + "index", { - "nth": 4, + "nth": 258, "isVLEncoded": false, - "isSerialized": true, - "isSigningField": true, - "type": "UInt16" + "isSerialized": false, + "isSigningField": false, + "type": "Hash256" } ], [ - "Flags", + "taker_gets_funded", { - "nth": 2, + "nth": 258, "isVLEncoded": false, - "isSerialized": true, - "isSigningField": true, - "type": "UInt32" + "isSerialized": false, + "isSigningField": false, + "type": "Amount" } ], [ - "SourceTag", + "taker_pays_funded", { - "nth": 3, + "nth": 259, "isVLEncoded": false, - "isSerialized": true, - "isSigningField": true, - "type": "UInt32" + "isSerialized": false, + "isSigningField": false, + "type": "Amount" } ], [ - "Sequence", + "LedgerEntry", { - "nth": 4, + "nth": 1, "isVLEncoded": false, - "isSerialized": true, + "isSerialized": false, "isSigningField": true, - "type": "UInt32" + "type": "LedgerEntry" } ], [ - "PreviousTxnLgrSeq", + "Transaction", { - "nth": 5, + "nth": 1, "isVLEncoded": false, - "isSerialized": true, + "isSerialized": false, "isSigningField": true, - "type": "UInt32" + "type": "Transaction" } ], [ - "LedgerSequence", + "Validation", { - "nth": 6, + "nth": 1, "isVLEncoded": false, - "isSerialized": true, + "isSerialized": false, "isSigningField": true, - "type": "UInt32" + "type": "Validation" } ], [ - "CloseTime", + "Metadata", { - "nth": 7, + "nth": 1, "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "UInt32" + "type": "Metadata" } ], [ - "ParentCloseTime", + "CloseResolution", { - "nth": 8, + "nth": 1, "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "UInt32" + "type": "UInt8" } ], [ - "SigningTime", + "Method", { - "nth": 9, + "nth": 2, "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "UInt32" + "type": "UInt8" } ], [ - "Expiration", + "TransactionResult", { - "nth": 10, + "nth": 3, "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "UInt32" + "type": "UInt8" } ], [ - "TransferRate", + "TickSize", { - "nth": 11, + "nth": 16, "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "UInt32" + "type": "UInt8" } ], [ - "WalletSize", + "UNLModifyDisabling", { - "nth": 12, + "nth": 17, "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "UInt32" + "type": "UInt8" } ], [ - "OwnerCount", + "HookResult", { - "nth": 13, + "nth": 18, "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "UInt32" + "type": "UInt8" } ], [ - "DestinationTag", + "LedgerEntryType", { - "nth": 14, + "nth": 1, "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "UInt32" + "type": "UInt16" } ], [ - "HighQualityIn", + "TransactionType", { - "nth": 16, + "nth": 2, "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "UInt32" + "type": "UInt16" } ], [ - "HighQualityOut", + "SignerWeight", { - "nth": 17, + "nth": 3, "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "UInt32" + "type": "UInt16" } ], [ - "LowQualityIn", + "TransferFee", { - "nth": 18, + "nth": 4, "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "UInt32" + "type": "UInt16" } ], [ - "LowQualityOut", + "TradingFee", { - "nth": 19, + "nth": 5, "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "UInt32" + "type": "UInt16" } ], [ - "QualityIn", + "DiscountedFee", { - "nth": 20, + "nth": 6, "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "UInt32" + "type": "UInt16" } ], [ - "QualityOut", + "Version", { - "nth": 21, + "nth": 16, "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "UInt32" + "type": "UInt16" } ], [ - "StampEscrow", + "HookStateChangeCount", { - "nth": 22, + "nth": 17, "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "UInt32" + "type": "UInt16" } ], [ - "BondAmount", + "HookEmitCount", { - "nth": 23, + "nth": 18, "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "UInt32" + "type": "UInt16" } ], [ - "LoadFee", + "HookExecutionIndex", { - "nth": 24, + "nth": 19, "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "UInt32" + "type": "UInt16" } ], [ - "OfferSequence", + "HookApiVersion", { - "nth": 25, + "nth": 20, "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "UInt32" + "type": "UInt16" } ], [ - "FirstLedgerSequence", + "NetworkID", { - "nth": 26, + "nth": 1, "isVLEncoded": false, "isSerialized": true, "isSigningField": true, @@ -346,9 +354,9 @@ } ], [ - "LastLedgerSequence", + "Flags", { - "nth": 27, + "nth": 2, "isVLEncoded": false, "isSerialized": true, "isSigningField": true, @@ -356,9 +364,9 @@ } ], [ - "TransactionIndex", + "SourceTag", { - "nth": 28, + "nth": 3, "isVLEncoded": false, "isSerialized": true, "isSigningField": true, @@ -366,9 +374,9 @@ } ], [ - "OperationLimit", + "Sequence", { - "nth": 29, + "nth": 4, "isVLEncoded": false, "isSerialized": true, "isSigningField": true, @@ -376,9 +384,9 @@ } ], [ - "ReferenceFeeUnits", + "PreviousTxnLgrSeq", { - "nth": 30, + "nth": 5, "isVLEncoded": false, "isSerialized": true, "isSigningField": true, @@ -386,9 +394,9 @@ } ], [ - "ReserveBase", + "LedgerSequence", { - "nth": 31, + "nth": 6, "isVLEncoded": false, "isSerialized": true, "isSigningField": true, @@ -396,9 +404,9 @@ } ], [ - "ReserveIncrement", + "CloseTime", { - "nth": 32, + "nth": 7, "isVLEncoded": false, "isSerialized": true, "isSigningField": true, @@ -406,9 +414,9 @@ } ], [ - "SetFlag", + "ParentCloseTime", { - "nth": 33, + "nth": 8, "isVLEncoded": false, "isSerialized": true, "isSigningField": true, @@ -416,9 +424,9 @@ } ], [ - "ClearFlag", + "SigningTime", { - "nth": 34, + "nth": 9, "isVLEncoded": false, "isSerialized": true, "isSigningField": true, @@ -426,9 +434,9 @@ } ], [ - "SignerQuorum", + "Expiration", { - "nth": 35, + "nth": 10, "isVLEncoded": false, "isSerialized": true, "isSigningField": true, @@ -436,9 +444,9 @@ } ], [ - "CancelAfter", + "TransferRate", { - "nth": 36, + "nth": 11, "isVLEncoded": false, "isSerialized": true, "isSigningField": true, @@ -446,9 +454,9 @@ } ], [ - "FinishAfter", + "WalletSize", { - "nth": 37, + "nth": 12, "isVLEncoded": false, "isSerialized": true, "isSigningField": true, @@ -456,99 +464,669 @@ } ], [ - "IndexNext", + "OwnerCount", { - "nth": 1, + "nth": 13, "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "UInt64" + "type": "UInt32" + } + ], + [ + "DestinationTag", + { + "nth": 14, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt32" + } + ], + [ + "HighQualityIn", + { + "nth": 16, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt32" + } + ], + [ + "HighQualityOut", + { + "nth": 17, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt32" + } + ], + [ + "LowQualityIn", + { + "nth": 18, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt32" + } + ], + [ + "LowQualityOut", + { + "nth": 19, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt32" + } + ], + [ + "QualityIn", + { + "nth": 20, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt32" + } + ], + [ + "QualityOut", + { + "nth": 21, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt32" + } + ], + [ + "StampEscrow", + { + "nth": 22, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt32" + } + ], + [ + "BondAmount", + { + "nth": 23, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt32" + } + ], + [ + "LoadFee", + { + "nth": 24, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt32" + } + ], + [ + "OfferSequence", + { + "nth": 25, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt32" + } + ], + [ + "FirstLedgerSequence", + { + "nth": 26, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt32" + } + ], + [ + "LastLedgerSequence", + { + "nth": 27, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt32" + } + ], + [ + "TransactionIndex", + { + "nth": 28, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt32" + } + ], + [ + "OperationLimit", + { + "nth": 29, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt32" + } + ], + [ + "ReferenceFeeUnits", + { + "nth": 30, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt32" + } + ], + [ + "ReserveBase", + { + "nth": 31, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt32" + } + ], + [ + "ReserveIncrement", + { + "nth": 32, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt32" + } + ], + [ + "SetFlag", + { + "nth": 33, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt32" + } + ], + [ + "ClearFlag", + { + "nth": 34, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt32" + } + ], + [ + "SignerQuorum", + { + "nth": 35, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt32" + } + ], + [ + "CancelAfter", + { + "nth": 36, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt32" + } + ], + [ + "FinishAfter", + { + "nth": 37, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt32" + } + ], + [ + "SignerListID", + { + "nth": 38, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt32" + } + ], + [ + "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" + } + ], + [ + "IndexNext", + { + "nth": 1, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt64" + } + ], + [ + "IndexPrevious", + { + "nth": 2, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt64" + } + ], + [ + "BookNode", + { + "nth": 3, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt64" + } + ], + [ + "OwnerNode", + { + "nth": 4, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt64" + } + ], + [ + "BaseFee", + { + "nth": 5, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt64" + } + ], + [ + "ExchangeRate", + { + "nth": 6, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt64" + } + ], + [ + "LowNode", + { + "nth": 7, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt64" + } + ], + [ + "HighNode", + { + "nth": 8, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt64" + } + ], + [ + "DestinationNode", + { + "nth": 9, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt64" + } + ], + [ + "Cookie", + { + "nth": 10, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt64" + } + ], + [ + "ServerVersion", + { + "nth": 11, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt64" + } + ], + [ + "NFTokenOfferNode", + { + "nth": 12, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt64" + } + ], + [ + "EmitBurden", + { + "nth": 13, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt64" + } + ], + [ + "HookOn", + { + "nth": 16, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt64" + } + ], + [ + "HookInstructionCount", + { + "nth": 17, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt64" + } + ], + [ + "HookReturnCode", + { + "nth": 18, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt64" + } + ], + [ + "ReferenceCount", + { + "nth": 19, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt64" + } + ], + [ + "EmailHash", + { + "nth": 1, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Hash128" + } + ], + [ + "TakerPaysCurrency", + { + "nth": 1, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Hash160" + } + ], + [ + "TakerPaysIssuer", + { + "nth": 2, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Hash160" + } + ], + [ + "TakerGetsCurrency", + { + "nth": 3, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Hash160" } ], [ - "IndexPrevious", + "TakerGetsIssuer", + { + "nth": 4, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Hash160" + } + ], + [ + "LedgerHash", + { + "nth": 1, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Hash256" + } + ], + [ + "ParentHash", { "nth": 2, "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "UInt64" + "type": "Hash256" } ], [ - "BookNode", + "TransactionHash", { "nth": 3, "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "UInt64" + "type": "Hash256" } ], [ - "OwnerNode", + "AccountHash", { "nth": 4, "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "UInt64" + "type": "Hash256" } ], [ - "BaseFee", + "PreviousTxnID", { "nth": 5, "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "UInt64" + "type": "Hash256" } ], [ - "ExchangeRate", + "LedgerIndex", { "nth": 6, "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "UInt64" + "type": "Hash256" } ], [ - "LowNode", + "WalletLocator", { "nth": 7, "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "UInt64" + "type": "Hash256" } ], [ - "HighNode", + "RootIndex", { "nth": 8, "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "UInt64" + "type": "Hash256" } ], [ - "EmailHash", + "AccountTxnID", { - "nth": 1, + "nth": 9, "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "Hash128" + "type": "Hash256" } ], [ - "LedgerHash", + "NFTokenID", { - "nth": 1, + "nth": 10, "isVLEncoded": false, "isSerialized": true, "isSigningField": true, @@ -556,9 +1134,9 @@ } ], [ - "ParentHash", + "EmitParentTxnID", { - "nth": 2, + "nth": 11, "isVLEncoded": false, "isSerialized": true, "isSigningField": true, @@ -566,9 +1144,9 @@ } ], [ - "TransactionHash", + "EmitNonce", { - "nth": 3, + "nth": 12, "isVLEncoded": false, "isSerialized": true, "isSigningField": true, @@ -576,9 +1154,9 @@ } ], [ - "AccountHash", + "EmitHookHash", { - "nth": 4, + "nth": 13, "isVLEncoded": false, "isSerialized": true, "isSigningField": true, @@ -586,9 +1164,9 @@ } ], [ - "PreviousTxnID", + "AMMID", { - "nth": 5, + "nth": 14, "isVLEncoded": false, "isSerialized": true, "isSigningField": true, @@ -596,9 +1174,9 @@ } ], [ - "LedgerIndex", + "BookDirectory", { - "nth": 6, + "nth": 16, "isVLEncoded": false, "isSerialized": true, "isSigningField": true, @@ -606,9 +1184,9 @@ } ], [ - "WalletLocator", + "InvoiceID", { - "nth": 7, + "nth": 17, "isVLEncoded": false, "isSerialized": true, "isSigningField": true, @@ -616,9 +1194,9 @@ } ], [ - "RootIndex", + "Nickname", { - "nth": 8, + "nth": 18, "isVLEncoded": false, "isSerialized": true, "isSigningField": true, @@ -626,9 +1204,9 @@ } ], [ - "AccountTxnID", + "Amendment", { - "nth": 9, + "nth": 19, "isVLEncoded": false, "isSerialized": true, "isSigningField": true, @@ -636,9 +1214,9 @@ } ], [ - "NFTokenID", + "Digest", { - "nth": 10, + "nth": 21, "isVLEncoded": false, "isSerialized": true, "isSigningField": true, @@ -646,9 +1224,9 @@ } ], [ - "BookDirectory", + "Channel", { - "nth": 16, + "nth": 22, "isVLEncoded": false, "isSerialized": true, "isSigningField": true, @@ -656,9 +1234,9 @@ } ], [ - "InvoiceID", + "ConsensusHash", { - "nth": 17, + "nth": 23, "isVLEncoded": false, "isSerialized": true, "isSigningField": true, @@ -666,9 +1244,9 @@ } ], [ - "Nickname", + "CheckID", { - "nth": 18, + "nth": 24, "isVLEncoded": false, "isSerialized": true, "isSigningField": true, @@ -676,9 +1254,9 @@ } ], [ - "Amendment", + "ValidatedHash", { - "nth": 19, + "nth": 25, "isVLEncoded": false, "isSerialized": true, "isSigningField": true, @@ -686,9 +1264,9 @@ } ], [ - "TicketID", + "PreviousPageMin", { - "nth": 20, + "nth": 26, "isVLEncoded": false, "isSerialized": true, "isSigningField": true, @@ -696,9 +1274,9 @@ } ], [ - "Digest", + "NextPageMin", { - "nth": 21, + "nth": 27, "isVLEncoded": false, "isSerialized": true, "isSigningField": true, @@ -706,22 +1284,62 @@ } ], [ - "hash", + "NFTokenBuyOffer", { - "nth": 257, + "nth": 28, "isVLEncoded": false, - "isSerialized": false, - "isSigningField": false, + "isSerialized": true, + "isSigningField": true, "type": "Hash256" } ], [ - "index", + "NFTokenSellOffer", { - "nth": 258, + "nth": 29, "isVLEncoded": false, - "isSerialized": false, - "isSigningField": false, + "isSerialized": true, + "isSigningField": true, + "type": "Hash256" + } + ], + [ + "HookStateKey", + { + "nth": 30, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Hash256" + } + ], + [ + "HookHash", + { + "nth": 31, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Hash256" + } + ], + [ + "HookNamespace", + { + "nth": 32, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Hash256" + } + ], + [ + "HookSetTxnID", + { + "nth": 33, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, "type": "Hash256" } ], @@ -736,9 +1354,99 @@ } ], [ - "Balance", + "Balance", + { + "nth": 2, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Amount" + } + ], + [ + "LimitAmount", + { + "nth": 3, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Amount" + } + ], + [ + "TakerPays", + { + "nth": 4, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Amount" + } + ], + [ + "TakerGets", + { + "nth": 5, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Amount" + } + ], + [ + "LowLimit", + { + "nth": 6, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Amount" + } + ], + [ + "HighLimit", + { + "nth": 7, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Amount" + } + ], + [ + "Fee", + { + "nth": 8, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Amount" + } + ], + [ + "SendMax", + { + "nth": 9, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Amount" + } + ], + [ + "DeliverMin", + { + "nth": 10, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Amount" + } + ], + [ + "Amount2", { - "nth": 2, + "nth": 11, "isVLEncoded": false, "isSerialized": true, "isSigningField": true, @@ -746,9 +1454,9 @@ } ], [ - "LimitAmount", + "BidMin", { - "nth": 3, + "nth": 12, "isVLEncoded": false, "isSerialized": true, "isSigningField": true, @@ -756,9 +1464,9 @@ } ], [ - "TakerPays", + "BidMax", { - "nth": 4, + "nth": 13, "isVLEncoded": false, "isSerialized": true, "isSigningField": true, @@ -766,9 +1474,9 @@ } ], [ - "TakerGets", + "MinimumOffer", { - "nth": 5, + "nth": 16, "isVLEncoded": false, "isSerialized": true, "isSigningField": true, @@ -776,9 +1484,9 @@ } ], [ - "LowLimit", + "RippleEscrow", { - "nth": 6, + "nth": 17, "isVLEncoded": false, "isSerialized": true, "isSigningField": true, @@ -786,9 +1494,9 @@ } ], [ - "HighLimit", + "DeliveredAmount", { - "nth": 7, + "nth": 18, "isVLEncoded": false, "isSerialized": true, "isSigningField": true, @@ -796,9 +1504,9 @@ } ], [ - "Fee", + "NFTokenBrokerFee", { - "nth": 8, + "nth": 19, "isVLEncoded": false, "isSerialized": true, "isSigningField": true, @@ -806,9 +1514,9 @@ } ], [ - "SendMax", + "BaseFeeDrops", { - "nth": 9, + "nth": 22, "isVLEncoded": false, "isSerialized": true, "isSigningField": true, @@ -816,9 +1524,9 @@ } ], [ - "DeliverMin", + "ReserveBaseDrops", { - "nth": 10, + "nth": 23, "isVLEncoded": false, "isSerialized": true, "isSigningField": true, @@ -826,9 +1534,9 @@ } ], [ - "MinimumOffer", + "ReserveIncrementDrops", { - "nth": 16, + "nth": 24, "isVLEncoded": false, "isSerialized": true, "isSigningField": true, @@ -836,9 +1544,9 @@ } ], [ - "RippleEscrow", + "LPTokenOut", { - "nth": 17, + "nth": 25, "isVLEncoded": false, "isSerialized": true, "isSigningField": true, @@ -846,9 +1554,9 @@ } ], [ - "DeliveredAmount", + "LPTokenIn", { - "nth": 18, + "nth": 26, "isVLEncoded": false, "isSerialized": true, "isSigningField": true, @@ -856,9 +1564,9 @@ } ], [ - "NFTokenBrokerFee", + "EPrice", { - "nth": 19, + "nth": 27, "isVLEncoded": false, "isSerialized": true, "isSigningField": true, @@ -866,22 +1574,22 @@ } ], [ - "taker_gets_funded", + "Price", { - "nth": 258, + "nth": 28, "isVLEncoded": false, - "isSerialized": false, - "isSigningField": false, + "isSerialized": true, + "isSigningField": true, "type": "Amount" } ], [ - "taker_pays_funded", + "LPTokenBalance", { - "nth": 259, + "nth": 31, "isVLEncoded": false, - "isSerialized": false, - "isSigningField": false, + "isSerialized": true, + "isSigningField": true, "type": "Amount" } ], @@ -1086,49 +1794,49 @@ } ], [ - "Account", + "HookStateData", { - "nth": 1, + "nth": 22, "isVLEncoded": true, "isSerialized": true, "isSigningField": true, - "type": "AccountID" + "type": "Blob" } ], [ - "Owner", + "HookReturnString", { - "nth": 2, + "nth": 23, "isVLEncoded": true, "isSerialized": true, "isSigningField": true, - "type": "AccountID" + "type": "Blob" } ], [ - "Destination", + "HookParameterName", { - "nth": 3, + "nth": 24, "isVLEncoded": true, "isSerialized": true, "isSigningField": true, - "type": "AccountID" + "type": "Blob" } ], [ - "Issuer", + "HookParameterValue", { - "nth": 4, + "nth": 25, "isVLEncoded": true, "isSerialized": true, "isSigningField": true, - "type": "AccountID" + "type": "Blob" } ], [ - "Authorize", + "Account", { - "nth": 5, + "nth": 1, "isVLEncoded": true, "isSerialized": true, "isSigningField": true, @@ -1136,9 +1844,9 @@ } ], [ - "Unauthorize", + "Owner", { - "nth": 6, + "nth": 2, "isVLEncoded": true, "isSerialized": true, "isSigningField": true, @@ -1146,9 +1854,9 @@ } ], [ - "Target", + "Destination", { - "nth": 7, + "nth": 3, "isVLEncoded": true, "isSerialized": true, "isSigningField": true, @@ -1156,9 +1864,9 @@ } ], [ - "RegularKey", + "Issuer", { - "nth": 8, + "nth": 4, "isVLEncoded": true, "isSerialized": true, "isSigningField": true, @@ -1166,9 +1874,9 @@ } ], [ - "NFTokenMinter", + "Authorize", { - "nth": 9, + "nth": 5, "isVLEncoded": true, "isSerialized": true, "isSigningField": true, @@ -1176,623 +1884,523 @@ } ], [ - "ObjectEndMarker", - { - "nth": 1, - "isVLEncoded": false, - "isSerialized": true, - "isSigningField": true, - "type": "STObject" - } - ], - [ - "TransactionMetaData", - { - "nth": 2, - "isVLEncoded": false, - "isSerialized": true, - "isSigningField": true, - "type": "STObject" - } - ], - [ - "CreatedNode", - { - "nth": 3, - "isVLEncoded": false, - "isSerialized": true, - "isSigningField": true, - "type": "STObject" - } - ], - [ - "DeletedNode", - { - "nth": 4, - "isVLEncoded": false, - "isSerialized": true, - "isSigningField": true, - "type": "STObject" - } - ], - [ - "ModifiedNode", - { - "nth": 5, - "isVLEncoded": false, - "isSerialized": true, - "isSigningField": true, - "type": "STObject" - } - ], - [ - "PreviousFields", + "Unauthorize", { "nth": 6, - "isVLEncoded": false, - "isSerialized": true, - "isSigningField": true, - "type": "STObject" - } - ], - [ - "FinalFields", - { - "nth": 7, - "isVLEncoded": false, + "isVLEncoded": true, "isSerialized": true, "isSigningField": true, - "type": "STObject" + "type": "AccountID" } ], [ - "NewFields", + "RegularKey", { "nth": 8, - "isVLEncoded": false, + "isVLEncoded": true, "isSerialized": true, "isSigningField": true, - "type": "STObject" + "type": "AccountID" } ], [ - "TemplateEntry", + "NFTokenMinter", { "nth": 9, - "isVLEncoded": false, + "isVLEncoded": true, "isSerialized": true, "isSigningField": true, - "type": "STObject" + "type": "AccountID" } ], [ - "Memo", + "EmitCallback", { "nth": 10, - "isVLEncoded": false, - "isSerialized": true, - "isSigningField": true, - "type": "STObject" - } - ], - [ - "SignerEntry", - { - "nth": 11, - "isVLEncoded": false, - "isSerialized": true, - "isSigningField": true, - "type": "STObject" - } - ], - [ - "NFToken", - { - "nth": 12, - "isVLEncoded": false, + "isVLEncoded": true, "isSerialized": true, "isSigningField": true, - "type": "STObject" + "type": "AccountID" } ], [ - "Signer", + "HookAccount", { "nth": 16, - "isVLEncoded": false, - "isSerialized": true, - "isSigningField": true, - "type": "STObject" - } - ], - [ - "Majority", - { - "nth": 18, - "isVLEncoded": false, + "isVLEncoded": true, "isSerialized": true, "isSigningField": true, - "type": "STObject" + "type": "AccountID" } ], [ - "DisabledValidator", + "Indexes", { - "nth": 19, - "isVLEncoded": false, + "nth": 1, + "isVLEncoded": true, "isSerialized": true, "isSigningField": true, - "type": "STObject" + "type": "Vector256" } ], [ - "ArrayEndMarker", + "Hashes", { - "nth": 1, - "isVLEncoded": false, + "nth": 2, + "isVLEncoded": true, "isSerialized": true, "isSigningField": true, - "type": "STArray" + "type": "Vector256" } ], [ - "Signers", + "Amendments", { "nth": 3, - "isVLEncoded": false, - "isSerialized": true, - "isSigningField": false, - "type": "STArray" - } - ], - [ - "SignerEntries", - { - "nth": 4, - "isVLEncoded": false, + "isVLEncoded": true, "isSerialized": true, "isSigningField": true, - "type": "STArray" + "type": "Vector256" } ], [ - "Template", + "NFTokenOffers", { - "nth": 5, - "isVLEncoded": false, + "nth": 4, + "isVLEncoded": true, "isSerialized": true, "isSigningField": true, - "type": "STArray" + "type": "Vector256" } ], [ - "Necessary", + "Paths", { - "nth": 6, + "nth": 1, "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "STArray" + "type": "PathSet" } ], [ - "Sufficient", + "Asset", { - "nth": 7, + "nth": 3, "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "STArray" + "type": "Issue" } ], [ - "AffectedNodes", + "Asset2", { - "nth": 8, + "nth": 4, "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "STArray" + "type": "Issue" } ], [ - "Memos", + "TransactionMetaData", { - "nth": 9, + "nth": 2, "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "STArray" + "type": "STObject" } ], [ - "NFTokens", + "CreatedNode", { - "nth": 10, + "nth": 3, "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "STArray" + "type": "STObject" } ], [ - "Majorities", + "DeletedNode", { - "nth": 16, + "nth": 4, "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "STArray" + "type": "STObject" } ], [ - "DisabledValidators", + "ModifiedNode", { - "nth": 17, + "nth": 5, "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "STArray" + "type": "STObject" } ], [ - "CloseResolution", + "PreviousFields", { - "nth": 1, + "nth": 6, "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "UInt8" + "type": "STObject" } ], [ - "Method", + "FinalFields", { - "nth": 2, + "nth": 7, "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "UInt8" + "type": "STObject" } ], [ - "TransactionResult", + "NewFields", { - "nth": 3, + "nth": 8, "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "UInt8" + "type": "STObject" } ], [ - "TakerPaysCurrency", + "TemplateEntry", { - "nth": 1, + "nth": 9, "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "Hash160" + "type": "STObject" } ], [ - "TakerPaysIssuer", + "Memo", { - "nth": 2, + "nth": 10, "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "Hash160" + "type": "STObject" } ], [ - "TakerGetsCurrency", + "SignerEntry", { - "nth": 3, + "nth": 11, "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "Hash160" + "type": "STObject" } ], [ - "TakerGetsIssuer", + "NFToken", { - "nth": 4, + "nth": 12, "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "Hash160" + "type": "STObject" } ], [ - "Paths", + "EmitDetails", { - "nth": 1, + "nth": 13, "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "PathSet" + "type": "STObject" } ], [ - "Indexes", + "Hook", { - "nth": 1, - "isVLEncoded": true, + "nth": 14, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "Vector256" + "type": "STObject" } ], [ - "Hashes", + "Signer", { - "nth": 2, - "isVLEncoded": true, + "nth": 16, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "Vector256" + "type": "STObject" } ], [ - "Amendments", + "Majority", { - "nth": 3, - "isVLEncoded": true, + "nth": 18, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "Vector256" + "type": "STObject" } ], [ - "NFTokenOffers", + "DisabledValidator", { - "nth": 4, - "isVLEncoded": true, + "nth": 19, + "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "Vector256" + "type": "STObject" } ], [ - "Transaction", + "EmittedTxn", { - "nth": 1, + "nth": 20, "isVLEncoded": false, - "isSerialized": false, - "isSigningField": false, - "type": "Transaction" + "isSerialized": true, + "isSigningField": true, + "type": "STObject" } ], [ - "LedgerEntry", + "HookExecution", { - "nth": 1, + "nth": 21, "isVLEncoded": false, - "isSerialized": false, - "isSigningField": false, - "type": "LedgerEntry" + "isSerialized": true, + "isSigningField": true, + "type": "STObject" } ], [ - "Validation", + "HookDefinition", { - "nth": 1, + "nth": 22, "isVLEncoded": false, - "isSerialized": false, - "isSigningField": false, - "type": "Validation" + "isSerialized": true, + "isSigningField": true, + "type": "STObject" } ], [ - "SignerListID", + "HookParameter", { - "nth": 38, + "nth": 23, "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "UInt32" + "type": "STObject" } ], [ - "SettleDelay", + "HookGrant", { - "nth": 39, + "nth": 24, "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "UInt32" + "type": "STObject" } ], [ - "TicketCount", + "VoteEntry", { - "nth": 40, + "nth": 25, "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "UInt32" + "type": "STObject" } ], [ - "TicketSequence", + "AuctionSlot", { - "nth": 41, + "nth": 26, "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "UInt32" + "type": "STObject" } ], [ - "NFTokenTaxon", + "AuthAccount", { - "nth": 42, + "nth": 27, "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "UInt32" + "type": "STObject" } ], [ - "MintedNFTokens", + "Signers", { - "nth": 43, + "nth": 3, "isVLEncoded": false, "isSerialized": true, - "isSigningField": true, - "type": "UInt32" + "isSigningField": false, + "type": "STArray" } ], [ - "BurnedNFTokens", + "SignerEntries", { - "nth": 44, + "nth": 4, "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "UInt32" + "type": "STArray" } ], [ - "Channel", + "Template", { - "nth": 22, + "nth": 5, "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "Hash256" + "type": "STArray" } ], [ - "ConsensusHash", + "Necessary", { - "nth": 23, + "nth": 6, "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "Hash256" + "type": "STArray" } ], [ - "CheckID", + "Sufficient", { - "nth": 24, + "nth": 7, "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "Hash256" + "type": "STArray" } ], [ - "ValidatedHash", + "AffectedNodes", { - "nth": 25, + "nth": 8, "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "Hash256" + "type": "STArray" } ], [ - "PreviousPageMin", + "Memos", { - "nth": 26, + "nth": 9, "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "Hash256" + "type": "STArray" } ], [ - "NextPageMin", + "NFTokens", { - "nth": 27, + "nth": 10, "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "Hash256" + "type": "STArray" } ], [ - "NFTokenBuyOffer", + "Hooks", { - "nth": 28, + "nth": 11, "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "Hash256" + "type": "STArray" } ], [ - "NFTokenSellOffer", + "VoteSlots", { - "nth": 29, + "nth": 12, "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "Hash256" + "type": "STArray" } ], [ - "TickSize", + "Majorities", { "nth": 16, "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "UInt8" + "type": "STArray" } ], [ - "UNLModifyDisabling", + "DisabledValidators", { "nth": 17, "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "UInt8" + "type": "STArray" } ], [ - "DestinationNode", + "HookExecutions", { - "nth": 9, + "nth": 18, "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "UInt64" + "type": "STArray" } ], [ - "Cookie", + "HookParameters", { - "nth": 10, + "nth": 19, "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "UInt64" + "type": "STArray" } ], [ - "ServerVersion", + "HookGrants", { - "nth": 11, + "nth": 20, "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "UInt64" + "type": "STArray" } ], [ - "NFTokenOfferNode", + "AuthAccounts", { - "nth": 12, + "nth": 25, "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "UInt64" + "type": "STArray" } ] ], @@ -1810,6 +2418,9 @@ "telCAN_NOT_QUEUE_BLOCKED": -389, "telCAN_NOT_QUEUE_FEE": -388, "telCAN_NOT_QUEUE_FULL": -387, + "telWRONG_NETWORK": -386, + "telREQUIRES_NETWORK_ID": -385, + "telNETWORK_ID_MAKES_TX_NON_CANONICAL": -384, "temMALFORMED": -299, "temBAD_AMOUNT": -298, @@ -1844,8 +2455,12 @@ "temBAD_TICK_SIZE": -269, "temINVALID_ACCOUNT_ID": -268, "temCANNOT_PREAUTH_SELF": -267, - "temUNCERTAIN": -266, - "temUNKNOWN": -265, + "temINVALID_COUNT": -266, + "temUNCERTAIN": -265, + "temUNKNOWN": -264, + "temSEQ_AND_TICKET": -263, + "temBAD_NFTOKEN_TRANSFER_FEE": -262, + "temBAD_AMM_TOKENS": -261, "tefFAILURE": -199, "tefALREADY": -198, @@ -1867,7 +2482,8 @@ "tefINVARIANT_FAILED": -182, "tefTOO_BIG": -181, "tefNO_TICKET": -180, - "tefTOKEN_IS_NOT_TRANSFERABLE": -179, + "tefNFTOKEN_IS_NOT_TRANSFERABLE": -179, + "terRETRY": -99, "terFUNDS_SPENT": -98, "terINSUF_FEE_B": -97, @@ -1879,6 +2495,8 @@ "terLAST": -91, "terNO_RIPPLE": -90, "terQUEUED": -89, + "terPRE_TICKET": -88, + "terNO_AMM": -87, "tesSUCCESS": 0, @@ -1921,19 +2539,20 @@ "tecHAS_OBLIGATIONS": 151, "tecTOO_SOON": 152, "tecMAX_SEQUENCE_REACHED": 154, - "tecNO_SUITABLE_PAGE": 155, - "tecBUY_SELL_MISMATCH": 156, - "tecOFFER_TYPE_MISMATCH": 157, - "tecCANT_ACCEPT_OWN_OFFER": 158, + "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, - "tecINCORRECT_ASSET": 162, - "tecTOO_MANY": 163 + "tecUNFUNDED_AMM": 162, + "tecAMM_BALANCE": 163, + "tecAMM_FAILED": 164, + "tecAMM_INVALID_TOKENS": 165 }, "TRANSACTION_TYPES": { "Invalid": -1, - "Payment": 0, "EscrowCreate": 1, "EscrowFinish": 2, @@ -1956,11 +2575,18 @@ "DepositPreauth": 19, "TrustSet": 20, "AccountDelete": 21, + "SetHook": 22, "NFTokenMint": 25, "NFTokenBurn": 26, "NFTokenCreateOffer": 27, "NFTokenCancelOffer": 28, "NFTokenAcceptOffer": 29, + "Clawback": 30, + "AMMCreate": 35, + "AMMDeposit": 36, + "AMMWithdraw": 37, + "AMMVote": 38, + "AMMBid": 39, "EnableAmendment": 100, "SetFee": 101, "UNLModify": 102 diff --git a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/codec/binary/BinarySerializationTests.java b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/codec/binary/BinarySerializationTests.java index 8054640a5..8520ac601 100644 --- a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/codec/binary/BinarySerializationTests.java +++ b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/codec/binary/BinarySerializationTests.java @@ -58,6 +58,7 @@ import org.xrpl.xrpl4j.model.transactions.EscrowFinish; import org.xrpl.xrpl4j.model.transactions.Hash256; import org.xrpl.xrpl4j.model.transactions.IssuedCurrencyAmount; +import org.xrpl.xrpl4j.model.transactions.NetworkId; import org.xrpl.xrpl4j.model.transactions.OfferCancel; import org.xrpl.xrpl4j.model.transactions.OfferCreate; import org.xrpl.xrpl4j.model.transactions.Payment; @@ -85,6 +86,20 @@ private static IssuedCurrencyAmount currencyAmount(int amount) { .build(); } + @Test + public void serializeAccountSetTransactionWithNetworkId() throws JsonProcessingException { + AccountSet accountSet = AccountSet.builder() + .account(Address.of("rpP2GdsQwenNnFPefbXFgiTvEgJWQpq8Rw")) + .fee(XrpCurrencyAmount.ofDrops(10)) + .sequence(UnsignedInteger.valueOf(10598)) + .networkId(NetworkId.of(UnsignedInteger.MAX_VALUE)) + .build(); + + String expectedBinary = "12000321FFFFFFFF240000296668400000000000000A730081140F3D0C7D2CFAB2EC8295451F0B3CA03" + + "8E8E9CDCD"; + assertSerializesAndDeserializes(accountSet, expectedBinary); + } + @Test public void serializeAccountSetTransactionWithEmptyFlags() throws JsonProcessingException { AccountSet accountSet = AccountSet.builder() @@ -134,9 +149,10 @@ public void serializeAccountDelete() throws JsonProcessingException { .sequence(UnsignedInteger.valueOf(2470665)) .destination(Address.of("rPT1Sjq2YGrBMTttX4GZHjKu9dyfzbpAYe")) .destinationTag(UnsignedInteger.valueOf(13)) + .networkId(NetworkId.of(UnsignedInteger.MAX_VALUE)) .build(); - String expectedBinary = "120015240025B3092E0000000D6840000000004C4B40730081140596915CFDEEE" + + String expectedBinary = "12001521FFFFFFFF240025B3092E0000000D6840000000004C4B40730081140596915CFDEEE" + "3A695B3EFD6BDA9AC788A368B7B8314F667B0CA50CC7709A220B0561B85E53A48461FA8"; assertSerializesAndDeserializes(accountDelete, expectedBinary); } @@ -180,9 +196,10 @@ public void serializeCheckCancel() throws JsonProcessingException { .checkId(Hash256.of("49647F0D748DC3FE26BDACBC57F251AADEFFF391403EC9BF87C97F67E9977FB0")) .sequence(UnsignedInteger.valueOf(12)) .fee(XrpCurrencyAmount.ofDrops(12)) + .networkId(NetworkId.of(UnsignedInteger.MAX_VALUE)) .build(); - String expectedBinary = "120012240000000C501849647F0D748DC3FE26BDACBC57F251AADEFFF391403EC9BF87C97" + + String expectedBinary = "12001221FFFFFFFF240000000C501849647F0D748DC3FE26BDACBC57F251AADEFFF391403EC9BF87C97" + "F67E9977FB068400000000000000C730081147990EC5D1D8DF69E070A968D4B186986FDF06ED0"; assertSerializesAndDeserializes(checkCancel, expectedBinary); } @@ -257,9 +274,10 @@ public void serializeCheckCashWithXrpAmount() throws JsonProcessingException { .sequence(UnsignedInteger.ONE) .fee(XrpCurrencyAmount.ofDrops(12)) .amount(XrpCurrencyAmount.ofDrops(100)) + .networkId(NetworkId.of(UnsignedInteger.MAX_VALUE)) .build(); - String expectedBinary = "12001124000000015018838766BA2B995C00744175F69A1B11E32C3DBC40E64801A4" + + String expectedBinary = "12001121FFFFFFFF24000000015018838766BA2B995C00744175F69A1B11E32C3DBC40E64801A4" + "056FCBD657F5733461400000000000006468400000000000000C7300811449FF0C73CA6AF9733DA805F76CA2C37776B7C46B"; assertSerializesAndDeserializes(checkCash, expectedBinary); } @@ -310,9 +328,10 @@ void serializeCheckCreate() throws JsonProcessingException { .sendMax(XrpCurrencyAmount.ofDrops(100000000)) .expiration(UnsignedInteger.valueOf(570113521)) .invoiceId(Hash256.of("6F1DFD1D0FE8A32E40E1F2C05CF1C15545BAB56B617F9C6C2D63A6B704BEF59B")) + .networkId(NetworkId.of(UnsignedInteger.MAX_VALUE)) .build(); - String expectedBinary = "12001024000000012A21FB3DF12E0000000150116F1DFD1D0FE8A32E40E1F2C05CF1C" + + String expectedBinary = "12001021FFFFFFFF24000000012A21FB3DF12E0000000150116F1DFD1D0FE8A32E40E1F2C05CF1C" + "15545BAB56B617F9C6C2D63A6B704BEF59B68400000000000000C694000000005F5E100730081147990EC5D1D8DF69E070A96" + "8D4B186986FDF06ED0831449FF0C73CA6AF9733DA805F76CA2C37776B7C46B"; assertSerializesAndDeserializes(checkCreate, expectedBinary); @@ -365,9 +384,10 @@ void serializeDepositPreAuth() throws JsonProcessingException { .authorize(Address.of("rDJFnv5sEfp42LMFiX3mVQKczpFTdxYDzM")) .fee(XrpCurrencyAmount.ofDrops(10)) .sequence(UnsignedInteger.valueOf(65)) + .networkId(NetworkId.of(UnsignedInteger.MAX_VALUE)) .build(); - String expectedBinary = "120013240000004168400000000000000A730081148A928D14A643F388AC0D26B" + + String expectedBinary = "12001321FFFFFFFF240000004168400000000000000A730081148A928D14A643F388AC0D26B" + "AF9755B07EB0A2B44851486FFE2A17E861BA0FE9A3ED8352F895D80E789E0"; assertSerializesAndDeserializes(preAuth, expectedBinary); } @@ -416,9 +436,10 @@ void serializeEscrowCreate() throws JsonProcessingException, DerEncodingExceptio .condition(CryptoConditionReader.readCondition(BaseEncoding.base16().decode( "A0258020E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855810100")) ) + .networkId(NetworkId.of(UnsignedInteger.MAX_VALUE)) .build(); - String expectedBinary = "12000124000000012E00005BB82024258D09812025258D0980614000000000000064684" + + String expectedBinary = "12000121FFFFFFFF24000000012E00005BB82024258D09812025258D0980614000000000000064684" + "00000000000000C7300701127A0258020E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855810100" + "8114EE5F7CF61504C7CF7E0C22562EB19CC7ACB0FCBA8314B5F762798A53D543A014CAF8B297CFF8F2F937E8"; assertSerializesAndDeserializes(checkCreate, expectedBinary); @@ -478,9 +499,10 @@ void serializeEscrowCancel() throws JsonProcessingException { .sequence(UnsignedInteger.ONE) .owner(Address.of("r4jQDHCUvgcBAa5EzcB1D8BHGcjYP9eBC2")) .offerSequence(UnsignedInteger.valueOf(25)) + .networkId(NetworkId.of(UnsignedInteger.MAX_VALUE)) .build(); - String expectedBinary = "120004240000000120190000001968400000000000000C73008114EE5F7CF61504C7" + + String expectedBinary = "12000421FFFFFFFF240000000120190000001968400000000000000C73008114EE5F7CF61504C7" + "CF7E0C22562EB19CC7ACB0FCBA8214EE5F7CF61504C7CF7E0C22562EB19CC7ACB0FCBA"; assertSerializesAndDeserializes(escrowCancel, expectedBinary); } @@ -529,9 +551,10 @@ void serializeEscrowFinish() throws JsonProcessingException, DerEncodingExceptio "A0258020E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855810100") )) .fulfillment(CryptoConditionReader.readFulfillment(BaseEncoding.base16().decode("A0028000"))) + .networkId(NetworkId.of(UnsignedInteger.MAX_VALUE)) .build(); - String expectedBinary = "120002240000000320190000001968400000000000014A7300701004A0028000701127A02" + + String expectedBinary = "12000221FFFFFFFF240000000320190000001968400000000000014A7300701004A0028000701127A02" + "58020E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B8558101008114E151CA3207BAB5B91D2F0E4D35" + "ECDFD4551C69A18214E151CA3207BAB5B91D2F0E4D35ECDFD4551C69A1"; assertSerializesAndDeserializes(escrowFinish, expectedBinary); @@ -597,9 +620,10 @@ void serializeXrpPaymentWithEmptyFlags() throws JsonProcessingException { .amount(XrpCurrencyAmount.ofDrops(12345)) .fee(XrpCurrencyAmount.ofDrops(789)) .sequence(UnsignedInteger.valueOf(56565656)) + .networkId(NetworkId.of(UnsignedInteger.MAX_VALUE)) .build(); - String expectedBinary = "120000230000000124035F1F982E0000000261400000000000303968" + + String expectedBinary = "12000021FFFFFFFF230000000124035F1F982E0000000261400000000000303968" + "400000000000031573008114EE39E6D05CFD6A90DAB700A1D70149ECEE29DFEC83140000000000000000000000000000000000000001"; assertSerializesAndDeserializes(payment, expectedBinary); } @@ -708,9 +732,10 @@ void serializePaymentChannelCreateWithEmptyFlags() throws JsonProcessingExceptio .settleDelay(UnsignedInteger.ONE) .publicKey("32D2471DB72B27E3310F355BB33E339BF26F8392D5A93D3BC0FC3B566612DA0F0A") .cancelAfter(UnsignedLong.valueOf(533171558)) + .networkId(NetworkId.of(UnsignedInteger.MAX_VALUE)) .build(); - String expectedBinary = "12000D230000000124000000012E0000000220241FC78D66202700000001614000" + + String expectedBinary = "12000D21FFFFFFFF230000000124000000012E0000000220241FC78D66202700000001614000" + "000000002710684000000000000064712132D2471DB72B27E3310F355BB33E339BF26F8392D5A93D3BC0FC3B566612DA0" + "F0A730081144B4E9C06F24296074F7BC48F92A97916C6DC5EA983144B4E9C06F24296074F7BC48F92A97916C6DC5EA9"; assertSerializesAndDeserializes(create, expectedBinary); @@ -772,9 +797,10 @@ void serializePaymentChannelClaimWithEmptyFlags() throws JsonProcessingException .signature("30440220718D264EF05CAED7C781FF6DE298DCAC68D002562C9BF3A07C1E721B420C0DAB02203A5A4779E" + "F4D2CCC7BC3EF886676D803A9981B928D3B8ACA483B80ECA3CD7B9B") .publicKey("32D2471DB72B27E3310F355BB33E339BF26F8392D5A93D3BC0FC3B566612DA0F0A") + .networkId(NetworkId.of(UnsignedInteger.MAX_VALUE)) .build(); - String expectedBinary = "12000F24000000015016C1AE6DDDEEC05CF2978C0BAD6FE302948E9533691DC749D" + + String expectedBinary = "12000F21FFFFFFFF24000000015016C1AE6DDDEEC05CF2978C0BAD6FE302948E9533691DC749D" + "CDD3B9E5992CA61986140000000000F42406240000000000F424068400000000000000A712132D2471DB72B27E3310F355B" + "B33E339BF26F8392D5A93D3BC0FC3B566612DA0F0A7300764630440220718D264EF05CAED7C781FF6DE298DCAC68D002562" + "C9BF3A07C1E721B420C0DAB02203A5A4779EF4D2CCC7BC3EF886676D803A9981B928D3B8ACA483B80ECA3CD7B9B81142042" + @@ -840,9 +866,10 @@ void serializePaymentChannelFund() throws JsonProcessingException { .channel(Hash256.of("C1AE6DDDEEC05CF2978C0BAD6FE302948E9533691DC749DCDD3B9E5992CA6198")) .amount(XrpCurrencyAmount.ofDrops(200000)) .expiration(UnsignedLong.valueOf(543171558)) + .networkId(NetworkId.of(UnsignedInteger.MAX_VALUE)) .build(); - String expectedJson = "12000E24000000012A206023E65016C1AE6DDDEEC05CF2978C0BAD6FE302948E9533691DC7" + + String expectedJson = "12000E21FFFFFFFF24000000012A206023E65016C1AE6DDDEEC05CF2978C0BAD6FE302948E9533691DC7" + "49DCDD3B9E5992CA6198614000000000030D4068400000000000000A730081144B4E9C06F24296074F7BC48F92A97916C6DC5EA9"; assertSerializesAndDeserializes(fund, expectedJson); @@ -895,10 +922,11 @@ void serializeTrustSetWithEmptyFlags() throws JsonProcessingException { .issuer(Address.of("rUx4xgE7bNWCCgGcXv1CCoQyTcCeZ275YG")) .value("10000000") .build()) + .networkId(NetworkId.of(UnsignedInteger.MAX_VALUE)) .build(); - String expectedBinary = "120014240000002C63D6438D7EA4C68000000000000000000000000000574347000000" + + String expectedBinary = "12001421FFFFFFFF240000002C63D6438D7EA4C68000000000000000000000000000574347000000" + "0000832297BEF589D59F9C03A84F920F8D9128CC1CE468400000000000000C73008114BE6C30732AE33CF2AF3344CE8172A6B9" + "300183E3"; assertSerializesAndDeserializes(trustSet, expectedBinary); @@ -1411,9 +1439,10 @@ public void serializeOfferCreateWithEmptyFlags() throws JsonProcessingException .sequence(UnsignedInteger.valueOf(11223344)) .offerSequence(UnsignedInteger.valueOf(123)) .expiration(UnsignedInteger.valueOf(456)) + .networkId(NetworkId.of(UnsignedInteger.MAX_VALUE)) .build(); - String expectedBinary = "1200072400AB41302A000001C820190000007B64D5071AFD498D0000000000000000000" + + String expectedBinary = "12000721FFFFFFFF2400AB41302A000001C820190000007B64D5071AFD498D0000000000000000000" + "0000000005743470000000000832297BEF589D59F9C03A84F920F8D9128CC1CE465D5038D7EA4C6800000000000000000000000" + "00005743470000000000832297BEF589D59F9C03A84F920F8D9128CC1CE468400000000000012C73008114832297BEF589D59F9" + "C03A84F920F8D9128CC1CE4"; @@ -1467,9 +1496,10 @@ public void serializeOfferCancel() throws JsonProcessingException { .account(Address.of("rUx4xgE7bNWCCgGcXv1CCoQyTcCeZ275YG")) .sequence(UnsignedInteger.valueOf(11223344)) .offerSequence(UnsignedInteger.valueOf(123)) + .networkId(NetworkId.of(UnsignedInteger.MAX_VALUE)) .build(); - String expectedBinary = "1200082400AB413020190000007B68400000000000012C73008114832297BEF589" + + String expectedBinary = "12000821FFFFFFFF2400AB413020190000007B68400000000000012C73008114832297BEF589" + "D59F9C03A84F920F8D9128CC1CE4"; assertSerializesAndDeserializes(offerCreate, expectedBinary); } @@ -1511,9 +1541,10 @@ public void serializeSetRegularKey() throws JsonProcessingException { .fee(XrpCurrencyAmount.ofDrops(12)) .sequence(UnsignedInteger.ONE) .regularKey(Address.of("rAR8rR8sUkBoCZFawhkWzY4Y5YoyuznwD")) + .networkId(NetworkId.of(UnsignedInteger.MAX_VALUE)) .build(); - String expectedBinary = "120005240000000168400000000000000C730081144B4E9C06F24296074F7BC48F92" + + String expectedBinary = "12000521FFFFFFFF240000000168400000000000000C730081144B4E9C06F24296074F7BC48F92" + "A97916C6DC5EA988140A4B24D606281E6E5A78D9F80E039F5E66FA5AC5"; assertSerializesAndDeserializes(setRegularKey, expectedBinary); @@ -1578,9 +1609,10 @@ void serializeSignerListSet() throws JsonProcessingException { .build() ) ) + .networkId(NetworkId.of(UnsignedInteger.MAX_VALUE)) .build(); - String expectedBinary = "12000C240000000120230000000368400000000000000C730081144B4E9C06F24296074" + + String expectedBinary = "12000C21FFFFFFFF240000000120230000000368400000000000000C730081144B4E9C06F24296074" + "F7BC48F92A97916C6DC5EA9F4EB1300028114204288D2E47F8EF6C99BCC457966320D12409711E1EB13000181147908A7F0EDD4" + "8EA896C3580A399F0EE78611C8E3E1EB13000181143A4C02EA95AD6AC3BED92FA036E0BBFB712C030CE1F1"; diff --git a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/codec/binary/FieldHeaderCodecTest.java b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/codec/binary/FieldHeaderCodecTest.java index 82cd1927d..dc476683d 100644 --- a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/codec/binary/FieldHeaderCodecTest.java +++ b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/codec/binary/FieldHeaderCodecTest.java @@ -43,7 +43,7 @@ public void loadFixtures() throws IOException { ObjectMapper objectMapper = BinaryCodecObjectMapperFactory.getObjectMapper(); fieldHeaderCodec = new FieldHeaderCodec(new DefaultDefinitionsProvider(objectMapper).get(), objectMapper); fieldTests = FixtureUtils.getDataDrivenFixtures().fieldTests(); - assertThat(fieldTests).hasSize(125); + assertThat(fieldTests).hasSize(123); } diff --git a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/client/serverinfo/ServerInfoResultTest.java b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/client/serverinfo/ServerInfoResultTest.java index 40edaae57..f7a1da78f 100644 --- a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/client/serverinfo/ServerInfoResultTest.java +++ b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/client/serverinfo/ServerInfoResultTest.java @@ -33,6 +33,7 @@ import org.xrpl.xrpl4j.model.client.serverinfo.ServerInfo.LastClose; import org.xrpl.xrpl4j.model.client.serverinfo.ServerInfo.ValidatedLedger; import org.xrpl.xrpl4j.model.transactions.Hash256; +import org.xrpl.xrpl4j.model.transactions.NetworkId; import org.xrpl.xrpl4j.model.transactions.XrpCurrencyAmount; import java.math.BigDecimal; @@ -47,7 +48,83 @@ public class ServerInfoResultTest extends AbstractJsonTest { @Test - void serverInfoResultTest() throws JSONException, JsonProcessingException { + void testJsonWithNetworkId() throws JsonProcessingException { + String json = "{\n" + + " \"info\": {\n" + + " \"build_version\": \"1.7.0\",\n" + + " \"amendment_blocked\": false,\n" + + " \"complete_ledgers\": \"61881385-62562429\",\n" + + " \"hostid\": \"LARD\",\n" + + " \"io_latency_ms\": 2,\n" + + " \"jq_trans_overflow\": \"0\",\n" + + " \"last_close\": {\n" + + " \"converge_time_s\": 3.002,\n" + + " \"proposers\": 38\n" + + " },\n" + + " \"load_factor\": 511.83203125,\n" + + " \"network_id\": 25,\n" + + " \"load_factor_server\": 1,\n" + + " \"peers\": 261,\n" + + " \"pubkey_node\": \"n9MozjnGB3tpULewtTsVtuudg5JqYFyV3QFdAtVLzJaxHcBaxuXD\",\n" + + " \"server_state\": \"full\",\n" + + " \"server_state_duration_us\": \"2274468435925\",\n" + + " \"time\": \"2021-Mar-30 15:37:51.486384 UTC\",\n" + + " \"uptime\": 2274704,\n" + + " \"validated_ledger\": {\n" + + " \"age\": 4,\n" + + " \"base_fee_xrp\": 0.00001,\n" + + " \"hash\": \"E5A958048D98D4EFEEDD2BC3F36D23893BBC1D9354CB3E739068D2DFDE3D1AA3\",\n" + + " \"reserve_base_xrp\": 20.1,\n" + + " \"reserve_inc_xrp\": 5.0,\n" + + " \"seq\": 62562429\n" + + " },\n" + + " \"validation_quorum\": 31\n" + + " },\n" + + " \"status\": \"success\"\n" + + "}"; + + ServerInfo serverInfo = RippledServerInfo.builder() + .buildVersion("1.7.0") + .completeLedgers(LedgerRangeUtils.completeLedgersToListOfRange("61881385-62562429")) + .hostId("LARD") + .ioLatencyMs(UnsignedLong.valueOf(2)) + .jqTransOverflow("0") + .lastClose(LastClose.builder() + .convergeTimeSeconds(BigDecimal.valueOf(3.002)) + .proposers(UnsignedInteger.valueOf(38)) + .build()) + .loadFactor(new BigDecimal("511.83203125")) + .networkId(NetworkId.of(25)) + .loadFactorServer(BigDecimal.ONE) + .peers(UnsignedInteger.valueOf(261)) + .publicKeyNode("n9MozjnGB3tpULewtTsVtuudg5JqYFyV3QFdAtVLzJaxHcBaxuXD") + .serverState("full") + .serverStateDurationUs("2274468435925") + .time(ZonedDateTime.parse("2021-Mar-30 15:37:51.486384 UTC", + DateTimeFormatter.ofPattern("yyyy-MMM-dd HH:mm:ss.SSSSSS z", Locale.US)).withZoneSameLocal(ZoneId.of("UTC"))) + .upTime(UnsignedLong.valueOf(2274704)) + .validatedLedger(ValidatedLedger.builder() + .age(UnsignedInteger.valueOf(4)) + .hash(Hash256.of("E5A958048D98D4EFEEDD2BC3F36D23893BBC1D9354CB3E739068D2DFDE3D1AA3")) + .reserveBaseXrp(XrpCurrencyAmount.ofDrops(20100000)) + .reserveIncXrp(XrpCurrencyAmount.ofDrops(5000000)) + .sequence(LedgerIndex.of(UnsignedInteger.valueOf(62562429))) + .baseFeeXrp(new BigDecimal("0.000010")) + .build()) + .validationQuorum(UnsignedInteger.valueOf(31)) + .build(); + ImmutableServerInfoResult.Builder resultBuilder = ServerInfoResult.builder() + .info(serverInfo) + .status("success"); + + ServerInfoResult result = Assertions.assertDoesNotThrow(() -> resultBuilder.build()); + + assertCanDeserialize(json, result); + assertThat(result.info()).isEqualTo(serverInfo); + } + + @Test + void testJsonWithoutNetworkId() throws JsonProcessingException { String json = "{\n" + " \"info\": {\n" + " \"build_version\": \"1.7.0\",\n" + diff --git a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/NetworkIdTest.java b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/NetworkIdTest.java new file mode 100644 index 000000000..23a68032d --- /dev/null +++ b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/NetworkIdTest.java @@ -0,0 +1,90 @@ +package org.xrpl.xrpl4j.model.transactions; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.google.common.primitives.UnsignedInteger; +import org.assertj.core.api.Assertions; +import org.immutables.value.Value; +import org.json.JSONException; +import org.junit.jupiter.api.Test; +import org.skyscreamer.jsonassert.JSONAssert; +import org.skyscreamer.jsonassert.JSONCompareMode; +import org.xrpl.xrpl4j.model.jackson.ObjectMapperFactory; + +public class NetworkIdTest { + + ObjectMapper objectMapper = ObjectMapperFactory.create(); + + @Test + void testBounds() { + NetworkId networkId = NetworkId.of(UnsignedInteger.ZERO); + assertThat(networkId.value()).isEqualTo(UnsignedInteger.ZERO); + + networkId = NetworkId.of(UnsignedInteger.MAX_VALUE); + assertThat(networkId.value()).isEqualTo(UnsignedInteger.MAX_VALUE); + } + + @Test + void testToString() { + NetworkId networkId = NetworkId.of(UnsignedInteger.valueOf(1024)); + assertThat(networkId.toString()).isEqualTo("1024"); + } + + @Test + void testBoundsWithLongConstructor() { + NetworkId networkId = NetworkId.of(0); + assertThat(networkId.value()).isEqualTo(UnsignedInteger.ZERO); + + networkId = NetworkId.of(1); + assertThat(networkId.value()).isEqualTo(UnsignedInteger.ONE); + + networkId = NetworkId.of(4_294_967_295L); + assertThat(networkId.value()).isEqualTo(UnsignedInteger.MAX_VALUE); + + assertThatThrownBy(() -> NetworkId.of(4_294_967_296L)) + .isInstanceOf(IllegalArgumentException.class); + + assertThatThrownBy(() -> NetworkId.of(-1)) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + void testJson() throws JsonProcessingException, JSONException { + NetworkId networkId = NetworkId.of(UnsignedInteger.valueOf(1000)); + NetworkIdWrapper wrapper = NetworkIdWrapper.of(networkId); + + String json = "{\"networkId\": 1000}"; + assertSerializesAndDeserializes(wrapper, json); + } + + private void assertSerializesAndDeserializes( + NetworkIdWrapper wrapper, + String json + ) throws JsonProcessingException, JSONException { + String serialized = objectMapper.writeValueAsString(wrapper); + JSONAssert.assertEquals(json, serialized, JSONCompareMode.STRICT); + NetworkIdWrapper deserialized = objectMapper.readValue( + serialized, NetworkIdWrapper.class + ); + Assertions.assertThat(deserialized).isEqualTo(wrapper); + } + + + @Value.Immutable + @JsonSerialize(as = ImmutableNetworkIdWrapper.class) + @JsonDeserialize(as = ImmutableNetworkIdWrapper.class) + interface NetworkIdWrapper { + + static NetworkIdWrapper of(NetworkId networkId) { + return ImmutableNetworkIdWrapper.builder().networkId(networkId).build(); + } + + NetworkId networkId(); + + } +} diff --git a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/json/AccountDeleteJsonTests.java b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/json/AccountDeleteJsonTests.java index 83425d25c..8177a0b61 100644 --- a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/json/AccountDeleteJsonTests.java +++ b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/json/AccountDeleteJsonTests.java @@ -29,6 +29,7 @@ import org.xrpl.xrpl4j.model.flags.TransactionFlags; import org.xrpl.xrpl4j.model.transactions.AccountDelete; import org.xrpl.xrpl4j.model.transactions.Address; +import org.xrpl.xrpl4j.model.transactions.NetworkId; import org.xrpl.xrpl4j.model.transactions.XrpCurrencyAmount; public class AccountDeleteJsonTests extends AbstractJsonTest { @@ -44,6 +45,7 @@ public void testJson() throws JsonProcessingException, JSONException { .signingPublicKey( PublicKey.fromBase16EncodedPublicKey("02356E89059A75438887F9FEE2056A2890DB82A68353BE9C0C0C8F89C0018B37FC") ) + .networkId(NetworkId.of(1024)) .build(); String json = "{\n" + @@ -53,6 +55,7 @@ public void testJson() throws JsonProcessingException, JSONException { " \"DestinationTag\": 13,\n" + " \"Fee\": \"5000000\",\n" + " \"Sequence\": 2470665,\n" + + " \"NetworkID\": 1024,\n" + " \"SigningPubKey\" : \"02356E89059A75438887F9FEE2056A2890DB82A68353BE9C0C0C8F89C0018B37FC\"\n" + "}"; diff --git a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/json/AccountSetJsonTests.java b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/json/AccountSetJsonTests.java index 25ce81b17..764e91430 100644 --- a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/json/AccountSetJsonTests.java +++ b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/json/AccountSetJsonTests.java @@ -32,6 +32,7 @@ import org.xrpl.xrpl4j.model.transactions.AccountSet; import org.xrpl.xrpl4j.model.transactions.AccountSet.AccountSetFlag; import org.xrpl.xrpl4j.model.transactions.Address; +import org.xrpl.xrpl4j.model.transactions.NetworkId; import org.xrpl.xrpl4j.model.transactions.XrpCurrencyAmount; public class AccountSetJsonTests extends AbstractJsonTest { @@ -54,6 +55,7 @@ public void fullyPopulatedAccountSet() throws JSONException, JsonProcessingExcep ) .flags(AccountSetTransactionFlags.of(TransactionFlags.FULLY_CANONICAL_SIG.getValue())) .mintAccount(Address.of("rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn")) + .networkId(NetworkId.of(1024)) .build(); String json = "{\n" + @@ -70,6 +72,7 @@ public void fullyPopulatedAccountSet() throws JSONException, JsonProcessingExcep " \"ClearFlag\":8,\n" + " \"SigningPubKey\" : \"02356E89059A75438887F9FEE2056A2890DB82A68353BE9C0C0C8F89C0018B37FC\",\n" + " \"NFTokenMinter\" : \"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn\",\n" + + " \"NetworkID\": 1024,\n" + " \"EmailHash\":\"f9879d71855b5ff21e4963273a886bfc\"\n" + "}"; diff --git a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/json/CheckJsonTests.java b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/json/CheckJsonTests.java index 268dd0a76..b20288ed3 100644 --- a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/json/CheckJsonTests.java +++ b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/json/CheckJsonTests.java @@ -32,6 +32,7 @@ import org.xrpl.xrpl4j.model.transactions.CheckCash; import org.xrpl.xrpl4j.model.transactions.CheckCreate; import org.xrpl.xrpl4j.model.transactions.Hash256; +import org.xrpl.xrpl4j.model.transactions.NetworkId; import org.xrpl.xrpl4j.model.transactions.XrpCurrencyAmount; public class CheckJsonTests extends AbstractJsonTest { @@ -46,6 +47,7 @@ public void testCheckCancelJson() throws JsonProcessingException, JSONException .signingPublicKey( PublicKey.fromBase16EncodedPublicKey( "02356E89059A75438887F9FEE2056A2890DB82A68353BE9C0C0C8F89C0018B37FC") ) + .networkId(NetworkId.of(1024)) .build(); String json = "{\n" + @@ -54,6 +56,7 @@ public void testCheckCancelJson() throws JsonProcessingException, JSONException " \"CheckID\": \"49647F0D748DC3FE26BDACBC57F251AADEFFF391403EC9BF87C97F67E9977FB0\",\n" + " \"Sequence\": 12,\n" + " \"SigningPubKey\" : \"02356E89059A75438887F9FEE2056A2890DB82A68353BE9C0C0C8F89C0018B37FC\",\n" + + " \"NetworkID\": 1024,\n" + " \"Fee\": \"12\"\n" + "}"; @@ -123,6 +126,7 @@ public void testCheckCashJsonWithDeliverMin() throws JsonProcessingException, JS .signingPublicKey( PublicKey.fromBase16EncodedPublicKey("02356E89059A75438887F9FEE2056A2890DB82A68353BE9C0C0C8F89C0018B37FC") ) + .networkId(NetworkId.of(1024)) .build(); String json = "{\n" + @@ -132,6 +136,7 @@ public void testCheckCashJsonWithDeliverMin() throws JsonProcessingException, JS " \"CheckID\": \"838766BA2B995C00744175F69A1B11E32C3DBC40E64801A4056FCBD657F57334\",\n" + " \"Sequence\": 1,\n" + " \"SigningPubKey\" : \"02356E89059A75438887F9FEE2056A2890DB82A68353BE9C0C0C8F89C0018B37FC\",\n" + + " \"NetworkID\": 1024,\n" + " \"Fee\": \"12\"\n" + "}"; @@ -234,6 +239,7 @@ public void testCheckCreateJson() throws JsonProcessingException, JSONException .signingPublicKey( PublicKey.fromBase16EncodedPublicKey("02356E89059A75438887F9FEE2056A2890DB82A68353BE9C0C0C8F89C0018B37FC") ) + .networkId(NetworkId.of(1024)) .build(); String json = "{\n" + @@ -246,6 +252,7 @@ public void testCheckCreateJson() throws JsonProcessingException, JSONException " \"DestinationTag\": 1,\n" + " \"Sequence\": 1,\n" + " \"SigningPubKey\" : \"02356E89059A75438887F9FEE2056A2890DB82A68353BE9C0C0C8F89C0018B37FC\",\n" + + " \"NetworkID\": 1024,\n" + " \"Fee\": \"12\"\n" + "}"; diff --git a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/json/EscrowJsonTests.java b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/json/EscrowJsonTests.java index cc97a2ef9..df7609290 100644 --- a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/json/EscrowJsonTests.java +++ b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/json/EscrowJsonTests.java @@ -38,6 +38,7 @@ import org.xrpl.xrpl4j.model.transactions.EscrowCancel; import org.xrpl.xrpl4j.model.transactions.EscrowCreate; import org.xrpl.xrpl4j.model.transactions.EscrowFinish; +import org.xrpl.xrpl4j.model.transactions.NetworkId; import org.xrpl.xrpl4j.model.transactions.XrpCurrencyAmount; public class EscrowJsonTests extends AbstractJsonTest { @@ -61,6 +62,7 @@ public void testEscrowCreateJson() throws JsonProcessingException, JSONException .signingPublicKey( PublicKey.fromBase16EncodedPublicKey("02356E89059A75438887F9FEE2056A2890DB82A68353BE9C0C0C8F89C0018B37FC") ) + .networkId(NetworkId.of(1024)) .build(); String json = "{\n" + @@ -75,6 +77,7 @@ public void testEscrowCreateJson() throws JsonProcessingException, JSONException " \"SourceTag\": 11747,\n" + " \"Sequence\": 1,\n" + " \"SigningPubKey\" : \"02356E89059A75438887F9FEE2056A2890DB82A68353BE9C0C0C8F89C0018B37FC\",\n" + + " \"NetworkID\": 1024,\n" + " \"Fee\": \"12\"\n" + "}"; @@ -175,6 +178,7 @@ public void testEscrowCancelJson() throws JsonProcessingException, JSONException .signingPublicKey( PublicKey.fromBase16EncodedPublicKey("02356E89059A75438887F9FEE2056A2890DB82A68353BE9C0C0C8F89C0018B37FC") ) + .networkId(NetworkId.of(1024)) .build(); String json = "{\n" + @@ -184,6 +188,7 @@ public void testEscrowCancelJson() throws JsonProcessingException, JSONException " \"OfferSequence\": 7,\n" + " \"Sequence\": 1,\n" + " \"SigningPubKey\" : \"02356E89059A75438887F9FEE2056A2890DB82A68353BE9C0C0C8F89C0018B37FC\",\n" + + " \"NetworkID\": 1024,\n" + " \"Fee\": \"12\"\n" + "}"; assertCanSerializeAndDeserialize(escrowCancel, json); @@ -258,6 +263,7 @@ public void testEscrowFinishJson() throws JsonProcessingException, JSONException .signingPublicKey( PublicKey.fromBase16EncodedPublicKey("02356E89059A75438887F9FEE2056A2890DB82A68353BE9C0C0C8F89C0018B37FC") ) + .networkId(NetworkId.of(1024)) .build(); String json = "{\n" + @@ -269,6 +275,7 @@ public void testEscrowFinishJson() throws JsonProcessingException, JSONException " \"Fulfillment\": \"A0028000\",\n" + " \"Sequence\": 1,\n" + " \"SigningPubKey\" : \"02356E89059A75438887F9FEE2056A2890DB82A68353BE9C0C0C8F89C0018B37FC\",\n" + + " \"NetworkID\": 1024,\n" + " \"Fee\": \"330\"\n" + "}"; diff --git a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/json/NfTokenAcceptOfferJsonTests.java b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/json/NfTokenAcceptOfferJsonTests.java index 939f60fc2..0c4b82073 100644 --- a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/json/NfTokenAcceptOfferJsonTests.java +++ b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/json/NfTokenAcceptOfferJsonTests.java @@ -29,6 +29,7 @@ import org.xrpl.xrpl4j.model.flags.TransactionFlags; import org.xrpl.xrpl4j.model.transactions.Address; import org.xrpl.xrpl4j.model.transactions.Hash256; +import org.xrpl.xrpl4j.model.transactions.NetworkId; import org.xrpl.xrpl4j.model.transactions.NfTokenAcceptOffer; import org.xrpl.xrpl4j.model.transactions.XrpCurrencyAmount; @@ -48,6 +49,7 @@ public void testMinimalNfTokenAcceptOfferJson() throws JsonProcessingException, .signingPublicKey( PublicKey.fromBase16EncodedPublicKey("02356E89059A75438887F9FEE2056A2890DB82A68353BE9C0C0C8F89C0018B37FC") ) + .networkId(NetworkId.of(1024)) .build(); String json = "{\n" + @@ -58,6 +60,7 @@ public void testMinimalNfTokenAcceptOfferJson() throws JsonProcessingException, " \"NFTokenBuyOffer\": \"000B013A95F14B0044F78A264E41713C64B5F89242540EE208C3098E00000D65\",\n" + " \"NFTokenSellOffer\": \"000B013A95F14B0044F78A264E41713C64B5F89242540EE208C3098E00000D65\",\n" + " \"SigningPubKey\" : \"02356E89059A75438887F9FEE2056A2890DB82A68353BE9C0C0C8F89C0018B37FC\",\n" + + " \"NetworkID\": 1024,\n" + " \"NFTokenBrokerFee\": \"10\"\n" + "}"; diff --git a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/json/NfTokenBurnJsonTests.java b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/json/NfTokenBurnJsonTests.java index e759ce14e..51d734f08 100644 --- a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/json/NfTokenBurnJsonTests.java +++ b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/json/NfTokenBurnJsonTests.java @@ -28,6 +28,7 @@ import org.xrpl.xrpl4j.model.AbstractJsonTest; import org.xrpl.xrpl4j.model.flags.TransactionFlags; import org.xrpl.xrpl4j.model.transactions.Address; +import org.xrpl.xrpl4j.model.transactions.NetworkId; import org.xrpl.xrpl4j.model.transactions.NfTokenBurn; import org.xrpl.xrpl4j.model.transactions.NfTokenId; import org.xrpl.xrpl4j.model.transactions.XrpCurrencyAmount; @@ -46,6 +47,7 @@ public void testMinimalNfTokenBurnJson() throws JsonProcessingException, JSONExc .signingPublicKey( PublicKey.fromBase16EncodedPublicKey("02356E89059A75438887F9FEE2056A2890DB82A68353BE9C0C0C8F89C0018B37FC") ) + .networkId(NetworkId.of(1024)) .build(); String json = "{\n" + @@ -54,6 +56,7 @@ public void testMinimalNfTokenBurnJson() throws JsonProcessingException, JSONExc " \"Fee\": \"12\",\n" + " \"Sequence\": 12,\n" + " \"NFTokenID\": \"000B013A95F14B0044F78A264E41713C64B5F89242540EE208C3098E00000D65\",\n" + + " \"NetworkID\": 1024,\n" + " \"SigningPubKey\" : \"02356E89059A75438887F9FEE2056A2890DB82A68353BE9C0C0C8F89C0018B37FC\"\n" + "}"; diff --git a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/json/NfTokenCancelOfferJsonTests.java b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/json/NfTokenCancelOfferJsonTests.java index 4b1bab7ac..5bc21ef08 100644 --- a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/json/NfTokenCancelOfferJsonTests.java +++ b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/json/NfTokenCancelOfferJsonTests.java @@ -29,6 +29,7 @@ import org.xrpl.xrpl4j.model.flags.TransactionFlags; import org.xrpl.xrpl4j.model.transactions.Address; import org.xrpl.xrpl4j.model.transactions.Hash256; +import org.xrpl.xrpl4j.model.transactions.NetworkId; import org.xrpl.xrpl4j.model.transactions.NfTokenCancelOffer; import org.xrpl.xrpl4j.model.transactions.XrpCurrencyAmount; @@ -50,6 +51,7 @@ public void testMinimalNfTokenCancelOfferJson() throws JsonProcessingException, .signingPublicKey( PublicKey.fromBase16EncodedPublicKey("02356E89059A75438887F9FEE2056A2890DB82A68353BE9C0C0C8F89C0018B37FC") ) + .networkId(NetworkId.of(1024)) .build(); String json = "{\n" + @@ -57,6 +59,7 @@ public void testMinimalNfTokenCancelOfferJson() throws JsonProcessingException, " \"Account\": \"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn\",\n" + " \"Fee\": \"12\",\n" + " \"Sequence\": 12,\n" + + " \"NetworkID\": 1024,\n" + " \"NFTokenOffers\": [" + " \"000B013A95F14B0044F78A264E41713C64B5F89242540EE208C3098E00000D65\"" + " ],\n" + diff --git a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/json/NfTokenCreateOfferJsonTests.java b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/json/NfTokenCreateOfferJsonTests.java index 2c4303ba0..880cfa734 100644 --- a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/json/NfTokenCreateOfferJsonTests.java +++ b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/json/NfTokenCreateOfferJsonTests.java @@ -28,6 +28,7 @@ import org.xrpl.xrpl4j.model.AbstractJsonTest; import org.xrpl.xrpl4j.model.flags.NfTokenCreateOfferFlags; import org.xrpl.xrpl4j.model.transactions.Address; +import org.xrpl.xrpl4j.model.transactions.NetworkId; import org.xrpl.xrpl4j.model.transactions.NfTokenCreateOffer; import org.xrpl.xrpl4j.model.transactions.NfTokenId; import org.xrpl.xrpl4j.model.transactions.XrpCurrencyAmount; @@ -47,6 +48,7 @@ public void testMinimalNfTokenCreateOfferJson() throws JsonProcessingException, .signingPublicKey( PublicKey.fromBase16EncodedPublicKey("02356E89059A75438887F9FEE2056A2890DB82A68353BE9C0C0C8F89C0018B37FC") ) + .networkId(NetworkId.of(1024)) .build(); String json = "{\n" + @@ -55,6 +57,7 @@ public void testMinimalNfTokenCreateOfferJson() throws JsonProcessingException, " \"Fee\": \"12\",\n" + " \"Sequence\": 12,\n" + " \"Amount\": \"2000\",\n" + + " \"NetworkID\": 1024,\n" + " \"NFTokenID\": \"000B013A95F14B0044F78A264E41713C64B5F89242540EE208C3098E00000D65\",\n" + " \"SigningPubKey\" : \"02356E89059A75438887F9FEE2056A2890DB82A68353BE9C0C0C8F89C0018B37FC\"\n" + "}"; diff --git a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/json/NfTokenMintJsonTests.java b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/json/NfTokenMintJsonTests.java index 4852ee03f..662baeb6c 100644 --- a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/json/NfTokenMintJsonTests.java +++ b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/json/NfTokenMintJsonTests.java @@ -29,6 +29,7 @@ import org.xrpl.xrpl4j.model.AbstractJsonTest; import org.xrpl.xrpl4j.model.flags.NfTokenMintFlags; import org.xrpl.xrpl4j.model.transactions.Address; +import org.xrpl.xrpl4j.model.transactions.NetworkId; import org.xrpl.xrpl4j.model.transactions.NfTokenMint; import org.xrpl.xrpl4j.model.transactions.NfTokenUri; import org.xrpl.xrpl4j.model.transactions.TransferFee; @@ -50,6 +51,7 @@ public void testMinimalNfTokenMintJson() throws JsonProcessingException, JSONExc .signingPublicKey( PublicKey.fromBase16EncodedPublicKey("02356E89059A75438887F9FEE2056A2890DB82A68353BE9C0C0C8F89C0018B37FC") ) + .networkId(NetworkId.of(1024)) .build(); String json = "{\n" + @@ -59,6 +61,7 @@ public void testMinimalNfTokenMintJson() throws JsonProcessingException, JSONExc " \"Flags\": 2147483656,\n" + " \"Sequence\": 12,\n" + " \"TransferFee\": 1000,\n" + + " \"NetworkID\": 1024,\n" + " \"SigningPubKey\" : \"02356E89059A75438887F9FEE2056A2890DB82A68353BE9C0C0C8F89C0018B37FC\",\n" + " \"NFTokenTaxon\": 146999694\n" + "}"; diff --git a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/json/OfferJsonTests.java b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/json/OfferJsonTests.java index cc0afa282..6f7ec73b8 100644 --- a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/json/OfferJsonTests.java +++ b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/json/OfferJsonTests.java @@ -29,6 +29,7 @@ import org.xrpl.xrpl4j.model.flags.OfferCreateFlags; import org.xrpl.xrpl4j.model.flags.TransactionFlags; import org.xrpl.xrpl4j.model.transactions.Address; +import org.xrpl.xrpl4j.model.transactions.NetworkId; import org.xrpl.xrpl4j.model.transactions.OfferCancel; import org.xrpl.xrpl4j.model.transactions.OfferCreate; import org.xrpl.xrpl4j.model.transactions.XrpCurrencyAmount; @@ -45,6 +46,7 @@ public void testOfferCancelJson() throws JsonProcessingException, JSONException .signingPublicKey( PublicKey.fromBase16EncodedPublicKey("02356E89059A75438887F9FEE2056A2890DB82A68353BE9C0C0C8F89C0018B37FC") ) + .networkId(NetworkId.of(1024)) .build(); String json = "{\n" + @@ -53,6 +55,7 @@ public void testOfferCancelJson() throws JsonProcessingException, JSONException " \"Sequence\": 12,\n" + " \"OfferSequence\": 13,\n" + " \"SigningPubKey\" : \"02356E89059A75438887F9FEE2056A2890DB82A68353BE9C0C0C8F89C0018B37FC\",\n" + + " \"NetworkID\": 1024,\n" + " \"Fee\": \"14\"\n" + "}"; @@ -124,6 +127,7 @@ public void testOfferCreateJson() throws JsonProcessingException, JSONException .signingPublicKey( PublicKey.fromBase16EncodedPublicKey("02356E89059A75438887F9FEE2056A2890DB82A68353BE9C0C0C8F89C0018B37FC") ) + .networkId(NetworkId.of(1024)) .build(); String json = "{\n" + @@ -135,6 +139,7 @@ public void testOfferCreateJson() throws JsonProcessingException, JSONException " \"TakerGets\": \"15\",\n" + " \"Fee\": \"12\",\n" + " \"SigningPubKey\" : \"02356E89059A75438887F9FEE2056A2890DB82A68353BE9C0C0C8F89C0018B37FC\",\n" + + " \"NetworkID\": 1024,\n" + " \"Expiration\": 16\n" + "}"; diff --git a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/json/PaymentChannelJsonTests.java b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/json/PaymentChannelJsonTests.java index 558f09a99..c0541fd10 100644 --- a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/json/PaymentChannelJsonTests.java +++ b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/json/PaymentChannelJsonTests.java @@ -31,6 +31,7 @@ import org.xrpl.xrpl4j.model.flags.TransactionFlags; import org.xrpl.xrpl4j.model.transactions.Address; import org.xrpl.xrpl4j.model.transactions.Hash256; +import org.xrpl.xrpl4j.model.transactions.NetworkId; import org.xrpl.xrpl4j.model.transactions.PaymentChannelClaim; import org.xrpl.xrpl4j.model.transactions.PaymentChannelCreate; import org.xrpl.xrpl4j.model.transactions.PaymentChannelFund; @@ -54,6 +55,7 @@ public void testPaymentChannelCreateJson() throws JsonProcessingException, JSONE .signingPublicKey( PublicKey.fromBase16EncodedPublicKey("02356E89059A75438887F9FEE2056A2890DB82A68353BE9C0C0C8F89C0018B37FC") ) + .networkId(NetworkId.of(1024)) .build(); String json = "{\n" + @@ -68,6 +70,7 @@ public void testPaymentChannelCreateJson() throws JsonProcessingException, JSONE " \"CancelAfter\": 533171558,\n" + " \"DestinationTag\": 23480,\n" + " \"SigningPubKey\" : \"02356E89059A75438887F9FEE2056A2890DB82A68353BE9C0C0C8F89C0018B37FC\",\n" + + " \"NetworkID\": 1024,\n" + " \"SourceTag\": 11747\n" + "}"; @@ -165,6 +168,7 @@ public void testPaymentChannelClaimJson() throws JsonProcessingException, JSONEx .signingPublicKey( PublicKey.fromBase16EncodedPublicKey("02356E89059A75438887F9FEE2056A2890DB82A68353BE9C0C0C8F89C0018B37FC") ) + .networkId(NetworkId.of(1024)) .build(); String json = "{\n" + @@ -175,6 +179,7 @@ public void testPaymentChannelClaimJson() throws JsonProcessingException, JSONEx " \"Channel\": \"C1AE6DDDEEC05CF2978C0BAD6FE302948E9533691DC749DCDD3B9E5992CA6198\",\n" + " \"Balance\": \"1000000\",\n" + " \"Amount\": \"1000000\",\n" + + " \"NetworkID\": 1024,\n" + " \"Signature\": \"30440220718D264EF05CAED7C781FF6DE298DCAC68D002562C9BF3A07C1E721B420C0DAB02203A5A4" + "779EF4D2CCC7BC3EF886676D803A9981B928D3B8ACA483B80ECA3CD7B9B\",\n" + " \"SigningPubKey\" : \"02356E89059A75438887F9FEE2056A2890DB82A68353BE9C0C0C8F89C0018B37FC\",\n" + @@ -230,6 +235,7 @@ public void testPaymentChannelFundJson() throws JsonProcessingException, JSONExc .signingPublicKey( PublicKey.fromBase16EncodedPublicKey("02356E89059A75438887F9FEE2056A2890DB82A68353BE9C0C0C8F89C0018B37FC") ) + .networkId(NetworkId.of(1024)) .build(); String json = "{\n" + @@ -239,6 +245,7 @@ public void testPaymentChannelFundJson() throws JsonProcessingException, JSONExc " \"TransactionType\": \"PaymentChannelFund\",\n" + " \"Channel\": \"C1AE6DDDEEC05CF2978C0BAD6FE302948E9533691DC749DCDD3B9E5992CA6198\",\n" + " \"Amount\": \"200000\",\n" + + " \"NetworkID\": 1024,\n" + " \"SigningPubKey\" : \"02356E89059A75438887F9FEE2056A2890DB82A68353BE9C0C0C8F89C0018B37FC\",\n" + " \"Expiration\": 543171558\n" + "}"; diff --git a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/json/PaymentJsonTests.java b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/json/PaymentJsonTests.java index d55c2322e..a5713e920 100644 --- a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/json/PaymentJsonTests.java +++ b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/json/PaymentJsonTests.java @@ -31,6 +31,7 @@ import org.xrpl.xrpl4j.model.flags.TransactionFlags; import org.xrpl.xrpl4j.model.transactions.Address; import org.xrpl.xrpl4j.model.transactions.IssuedCurrencyAmount; +import org.xrpl.xrpl4j.model.transactions.NetworkId; import org.xrpl.xrpl4j.model.transactions.PathStep; import org.xrpl.xrpl4j.model.transactions.Payment; import org.xrpl.xrpl4j.model.transactions.XrpCurrencyAmount; @@ -51,6 +52,7 @@ public void testJson() throws JsonProcessingException, JSONException { .signingPublicKey( PublicKey.fromBase16EncodedPublicKey("02356E89059A75438887F9FEE2056A2890DB82A68353BE9C0C0C8F89C0018B37FC") ) + .networkId(NetworkId.of(1024)) .build(); String json = "{\n" + @@ -60,6 +62,7 @@ public void testJson() throws JsonProcessingException, JSONException { " \"Amount\": \"25000000\",\n" + " \"Fee\": \"10\",\n" + " \"Flags\": 0,\n" + + " \"NetworkID\": 1024,\n" + " \"SigningPubKey\" : \"02356E89059A75438887F9FEE2056A2890DB82A68353BE9C0C0C8F89C0018B37FC\",\n" + " \"Sequence\": 2\n" + " }"; diff --git a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/json/SetRegularKeyJsonTest.java b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/json/SetRegularKeyJsonTest.java index 4c8514e64..54b8e5cd5 100644 --- a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/json/SetRegularKeyJsonTest.java +++ b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/json/SetRegularKeyJsonTest.java @@ -28,6 +28,7 @@ import org.xrpl.xrpl4j.model.AbstractJsonTest; import org.xrpl.xrpl4j.model.flags.TransactionFlags; import org.xrpl.xrpl4j.model.transactions.Address; +import org.xrpl.xrpl4j.model.transactions.NetworkId; import org.xrpl.xrpl4j.model.transactions.SetRegularKey; import org.xrpl.xrpl4j.model.transactions.XrpCurrencyAmount; @@ -43,6 +44,7 @@ public void testSetRegularKeyJson() throws JsonProcessingException, JSONExceptio .signingPublicKey( PublicKey.fromBase16EncodedPublicKey("02356E89059A75438887F9FEE2056A2890DB82A68353BE9C0C0C8F89C0018B37FC") ) + .networkId(NetworkId.of(1024)) .build(); String json = "{\n" + @@ -51,6 +53,7 @@ public void testSetRegularKeyJson() throws JsonProcessingException, JSONExceptio " \"Account\": \"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn\",\n" + " \"Fee\": \"12\",\n" + " \"SigningPubKey\" : \"02356E89059A75438887F9FEE2056A2890DB82A68353BE9C0C0C8F89C0018B37FC\",\n" + + " \"NetworkID\": 1024,\n" + " \"RegularKey\": \"rAR8rR8sUkBoCZFawhkWzY4Y5YoyuznwD\"\n" + "}"; diff --git a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/json/SignerListSetJsonTests.java b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/json/SignerListSetJsonTests.java index 03f5d636e..749f60883 100644 --- a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/json/SignerListSetJsonTests.java +++ b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/json/SignerListSetJsonTests.java @@ -30,6 +30,7 @@ import org.xrpl.xrpl4j.model.ledger.SignerEntry; import org.xrpl.xrpl4j.model.ledger.SignerEntryWrapper; import org.xrpl.xrpl4j.model.transactions.Address; +import org.xrpl.xrpl4j.model.transactions.NetworkId; import org.xrpl.xrpl4j.model.transactions.SignerListSet; import org.xrpl.xrpl4j.model.transactions.XrpCurrencyAmount; @@ -65,6 +66,7 @@ public void testSignerListSetJson() throws JsonProcessingException, JSONExceptio .signingPublicKey( PublicKey.fromBase16EncodedPublicKey("02356E89059A75438887F9FEE2056A2890DB82A68353BE9C0C0C8F89C0018B37FC") ) + .networkId(NetworkId.of(1024)) .build(); String json = "{\n" + @@ -73,6 +75,7 @@ public void testSignerListSetJson() throws JsonProcessingException, JSONExceptio " \"Account\": \"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn\",\n" + " \"Fee\": \"12\",\n" + " \"SignerQuorum\": 3,\n" + + " \"NetworkID\": 1024,\n" + " \"SigningPubKey\" : \"02356E89059A75438887F9FEE2056A2890DB82A68353BE9C0C0C8F89C0018B37FC\",\n" + " \"SignerEntries\": [\n" + " {\n" + diff --git a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/json/TicketCreateJsonTest.java b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/json/TicketCreateJsonTest.java index 7276ba43f..23403cc91 100644 --- a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/json/TicketCreateJsonTest.java +++ b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/json/TicketCreateJsonTest.java @@ -28,6 +28,7 @@ import org.xrpl.xrpl4j.model.AbstractJsonTest; import org.xrpl.xrpl4j.model.flags.TransactionFlags; import org.xrpl.xrpl4j.model.transactions.Address; +import org.xrpl.xrpl4j.model.transactions.NetworkId; import org.xrpl.xrpl4j.model.transactions.TicketCreate; import org.xrpl.xrpl4j.model.transactions.XrpCurrencyAmount; @@ -43,6 +44,7 @@ void testJson() throws JSONException, JsonProcessingException { .signingPublicKey( PublicKey.fromBase16EncodedPublicKey("02356E89059A75438887F9FEE2056A2890DB82A68353BE9C0C0C8F89C0018B37FC") ) + .networkId(NetworkId.of(1024)) .build(); String json = "{\n" + @@ -50,6 +52,7 @@ void testJson() throws JSONException, JsonProcessingException { " \"Account\": \"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn\",\n" + " \"Fee\": \"12\",\n" + " \"Sequence\": 1,\n" + + " \"NetworkID\": 1024,\n" + " \"SigningPubKey\" : \"02356E89059A75438887F9FEE2056A2890DB82A68353BE9C0C0C8F89C0018B37FC\",\n" + " \"TicketCount\": 200\n" + "}"; diff --git a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/json/TrustSetJsonTests.java b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/json/TrustSetJsonTests.java index 07fdb99b8..157d93be4 100644 --- a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/json/TrustSetJsonTests.java +++ b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/json/TrustSetJsonTests.java @@ -29,6 +29,7 @@ import org.xrpl.xrpl4j.model.flags.TrustSetFlags; import org.xrpl.xrpl4j.model.transactions.Address; import org.xrpl.xrpl4j.model.transactions.IssuedCurrencyAmount; +import org.xrpl.xrpl4j.model.transactions.NetworkId; import org.xrpl.xrpl4j.model.transactions.TrustSet; import org.xrpl.xrpl4j.model.transactions.XrpCurrencyAmount; @@ -51,6 +52,7 @@ public void testMinimalTrustSetJson() throws JsonProcessingException, JSONExcept .signingPublicKey( PublicKey.fromBase16EncodedPublicKey("02356E89059A75438887F9FEE2056A2890DB82A68353BE9C0C0C8F89C0018B37FC") ) + .networkId(NetworkId.of(1024)) .build(); String json = "{\n" + @@ -64,6 +66,7 @@ public void testMinimalTrustSetJson() throws JsonProcessingException, JSONExcept " \"issuer\": \"rsP3mgGb2tcYUrxiLFiHJiQXhsziegtwBc\",\n" + " \"value\": \"100\"\n" + " },\n" + + " \"NetworkID\": 1024,\n" + " \"Sequence\": 12\n" + "}"; diff --git a/xrpl4j-core/src/test/resources/data-driven-tests.json b/xrpl4j-core/src/test/resources/data-driven-tests.json index 4eae1bfab..ed7647ea0 100644 --- a/xrpl4j-core/src/test/resources/data-driven-tests.json +++ b/xrpl4j-core/src/test/resources/data-driven-tests.json @@ -483,13 +483,6 @@ "type": 5, "expected_hex": "5013" }, - { - "type_name": "Hash256", - "name": "TicketID", - "nth_of_type": 20, - "type": 5, - "expected_hex": "5014" - }, { "type_name": "Hash256", "name": "Digest", @@ -721,13 +714,6 @@ "type": 8, "expected_hex": "84" }, - { - "type_name": "AccountID", - "name": "Target", - "nth_of_type": 7, - "type": 8, - "expected_hex": "87" - }, { "type_name": "AccountID", "name": "RegularKey", From 75dd383d7d0793aefed140e2db70a8838c9c6fb9 Mon Sep 17 00:00:00 2001 From: nkramer44 Date: Fri, 4 Aug 2023 13:58:08 -0400 Subject: [PATCH 07/10] Add PreviousFields to DeletedNode (#462) add PreviousFields to DeletedNode --- .../transactions/metadata/DeletedNode.java | 11 +++++++++++ .../tx_meta_manual_fixtures.json.zip | Bin 340041 -> 341596 bytes 2 files changed, 11 insertions(+) diff --git a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/metadata/DeletedNode.java b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/metadata/DeletedNode.java index d84c0cca0..b9f36497c 100644 --- a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/metadata/DeletedNode.java +++ b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/metadata/DeletedNode.java @@ -5,6 +5,8 @@ import com.fasterxml.jackson.databind.annotation.JsonSerialize; import org.immutables.value.Value; +import java.util.Optional; + /** * A {@link DeletedNode} in transaction metadata indicates that the transaction removed an object from the ledger. * @@ -23,4 +25,13 @@ public interface DeletedNode extends AffectedNode { @JsonProperty("FinalFields") T finalFields(); + /** + * The {@link T} containing the fields of the ledger object if it was modified before it was removed in the same + * transaction. This field will rarely be present, but may be present under certain conditions. + * + * @return An {@link Optional} {@link T}. + */ + @JsonProperty("PreviousFields") + Optional previousFields(); + } diff --git a/xrpl4j-core/src/test/resources/tx_meta_manual_fixtures.json.zip b/xrpl4j-core/src/test/resources/tx_meta_manual_fixtures.json.zip index 6013a430c166b9aa0c19eabbfa2aafa7263f057d..155b0b7e5b9090de9f921285816efed4d82cd306 100644 GIT binary patch delta 12750 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 IhW*d~FMkG&kN^Mx delta 11150 zcmY+KQ*fqT(5|C56Jz2`Y}dp18v67i@IKdgS5!&v_`|Mv#VhRZ^R z%R+_ALWj%3q{@O|0fj`Wk+jQ4tMS{-+UZ?Z3Wm{XTL*i_8TeTFyVbWxK}C(Js(B+Z zOHmIk4{ieyu|b@j-R6i_7l?#yF{BLWQ8GBO-#gc;X+`2GCTON|)PwB$J|$O2PLk7~ zYt^(HFBE;A5AAMinmBgZ3B)MOcXOIY#@vL}KNj?gnNTJl`S^{x0IE7|+(bjfR&cF~ zo|U}DCKgSpNn%N<7-_ORQjOU!*9%E5NpZrAoV=&gQ(Lss4BJgrpkEja4+&xikg7k1 zG@jklxILNqYbtgc8jtwSvm(y*+S%tz$X!mlWG@SAaB<*Q*U z#Hv%65S%LiWBP&FSd@4$YIE)%m$Swf${VSsI~^m-thg)Y$M}=j{;3bVblx&>e-v71 z=F?uqb#Zs=;)>c=;ZGnFxhJRvZi57oe`^ZcpFcG+Ns7+Apvu&yv~s z3VwzJaniv;Z+)9~en{cvqz=&b722)PL(IB(b~Ji@t%_Lq?OL~gb}PBw(aK|J57veP z(L=(5!=ONP%xkSN&&8<`OJHxU!XD$)5WADpLHb0+$KNmRb z2kT}Ap^z7=S0_(Sd>^yz1M zk|tSuaGyyOdvZ2X&I-&@GUb7C@)7i9=8%${3G`#B6*azQpwvke)IJ;?)w1cO8r)YU z4E05p4OVLhG)*3F=~9XE*&>?IyMZfAEaEc5j`JtyKGUM#0rR0%n-neXk1q}GfmDu7 zXBY3QrhuTbo6RSs{Y2Ml?Bt!GHf4W$WKN#e0s#9XZ!d%nP6t`LERX1qZb~D=m+GR1 zOjwCidvsq+jDnt3JduRBS!rqi%L##WNeMRfTqJHd0l@D!718)~JE;aj49tIJQDPCk1?<-Iz$=ik`N*;U_mq(&*6%##49Np3L3eCr}!-q5p#@u+Jz0YH>|6&Bkmh&`N5tK z|LvkE`q=d~p3qX!fvX;fZ>uI(vMQY0wae>JS#@-<6g~$6)nYSJnO;U%e-JvFaTXpI z&pG-7}ru4`}2bcFcYG-8Js=grXAi56U%jT?KPo_FZdv(WlsOtqKhmUC}=GZW*ZhF*pqAj*1TCaPgiJ z;tb<{@n)kM!d>Ad?n(5`pT?eolkewW4(_()cTw0V5F$Q$mPkI(8Psr67C0@@G406} zRL${wkLu6Y%C~q6yG-+T3R{VxfNQmC6L{XM$I&sv`Ne4`DC+#pwSt5h@r<)=jEMG zO%!9tX|qNV0j-)F@LVzKHA*IKG;9cV4;1WhFGevky-wv>*uCr+0pqzbpegxXOX6VU zw`s-oO8<2f$`r)hc}!L@wY}CmtDS^+8y5GaNSQs1*nQ;WTSG)C#hzMi?JkG@yebul z;a8y)F3i5d83viRDQ_u5SjqYq7USAgD+e?0tt(8E^St-tm8k#3BOR`|zL<(8kj-7S z%Px6q$o;K~L;CeAQdd{Y6zDCN7*6!Tge<^wTW~an*SgHkQ8$J4@-@H_2m2M**9X>R zJ#C6I{un|$dwMGwnTqR=MpPmP~oOCFxt+@c>kSlE2OnDwh0C1K>aPJ3+{2k=| z@rPtRjkLL-*1yn{L*EjFH!~_TfZ$$EmKnx$Doajh`X^eoMM2gJU1WT@lJOGXE3hbc zGR<>Ji}_NKcp(3LwHoejjNgykyvq*!|MJjgBjmq* zt8JB-w}~!7PZL1XiH2|aj;OATP|=>5enmbA)2C0M@33VzXFOp=Vq=6ei_1rVC|
FN&6a@|dYmo{%?Sn@zDT`&#Nem{GVhRLiFmU4%cHVs5d}ySsv!R3WQJ z{Hu6`X?b|P$WdQzClEhE_fb$v3E|Htkr^8I!kUl$ITI6;VC#>i9~;FLsaa+-nQd4V z%%?w4mmWQ;D&rVNcZKNVNhmPR<@1BRfORQ2uP&}tYAY5{2LSDn$4I-__d(Y^lT5WmO*4Bg9!)zxL;Fm z=jt0z>q}$bex4`l^DYat`WYBy>KR>*XD_dnZlYW!XXRG4p=5ixv=MPwANU=Vwj-Y zAI}s|#=H}~&Tn@HA6l&?2o|g+lxgNjn&yptE$J(=^^U z@E7zz`1=t@&=UWp{9Le-u5Tni%ZwIoFAt_J!;!dOdq7&w-Luc?#;ni9q3pVo!>hd> z&sOmf`m~tbHWM`SB)b)rgi?UN^Jz*TBFLM2E}`QugZTagp1}scPCeO$FBt`5Z-chU-eAxL1*Oo)F&ypCcy7ddUWt`UrpfR$pdR>s*XOY4?Pz%oR$&!pRZ;6 zaC_bESIqSn-Ew@_{u(%ZK!vH3V@Jo8_rCw( zlw20L^z>xka{ogQCAH_J*o*6GKRL3%)|cieeTrjF{czhXEUT%~(ufzMQ4`N0BkDrl zN)-6E*`t3zFptcw_R^unk1Gz&AalArOasQbK2>vHh=wFd6Hg zoz<}KO%`c=X?o8&5-JPhSa#?G&Q_@&(L7ldIPE+;^lngFMSAp_b?j^hDV2!4dfiZO zK@K86Lm0jM?^xPUOYs-6Pfivb{}N0H6NOPWIqN@Y61f|YjPaki2Ffs_IxVnhvK-r3 z_U^l1`ae~%kt9xkw#epcf=!S?48RBJv!1Q@zJgI-fwaWTd=FWqhpweXw3_Sl;oBdZP@Wl|p8*V@dg>jX4WXzH>mRj*NmM!CS%)a25 ztQ$_Wxe6Y3Bv$8sQepMF*$5Xm`SAYa__B3XT5=Va7k|}r>f**l&sg=QY{VKV;r07_ z{n4}#A>DUX468rK2Ql_ZM1Y1kXap(?fy+UZj)4!Bv1vfrD8A4YM$%s-hnjY$vr2>~ z13@0A#hM_0Vu58C3zZMK=z!P=ZV%S+U#&p8m?3ty(l5Ln0LK)|)M*J($Cd_D&ucSR zsdajoVub6+>iB7r*d<61jczZ3Hw)+|2B+2iIRyER8B%DSYh@#xQD*09Ac*ZhYjW=+ zrS7G`xTqj%)UM99HY6dhGQJLEf7Y%!up%mm3b-HXO|#~O>=^jxGdZ4DY-kzGKJON9 z2H$6Zk98}rj95FiZ>wg1{OyQ5s{(ywDgv+lsRb`d2ZgVvviW_Q(onE{wFz32MC$N+ zse0g_Ugh6bxYVb%FEtW5UlVvNwEQI_&Auj3E~W8;){i}68|8)JB`gra;U--YGI9Sa z2g>kH3n1rbV5-CNZIJP2FSjXd>>137FQ9W@*-D1C3sLeL)emzN?5gWGc=-iaQoh1$uoT zzmG&O#q>f&=GN<4pcl(RY0tW{J@xYGacaPDl0AEE^0KbS(Nv*ty_GOpn@Y#T2gPJo zFLLH&s0r#Z)=-UiMPofAA~ojCg5LK_4n6{ERE8)^{78n4XW zF(}34eqEGazzkYOtK<2MMOVQmhdfSFTFm13;Zh;-K7?C9`V9jAIwRCWp7tAZv+A$g z5tr`o+A?N^W%3Yq@Tm?#Gce81VR{A!i`BVcCML%Q4jLgk+6d-z@ghfrEEO;O28kiHLoS~b>}z{%*6tmt0BpVI1^O8c#x_(ovjEf@3dDCwRxy@zFP?2oHd3j=&;^MNli>HQB_JekV5a>q zdP_g))CzH!#F0*_{EIs0)H+DIASrzDH)pSSXNVR-ubQA{d_sR494b~V1c7RD11A}p zQ0u&_rlxLu?QAI5RaR3eh45n!}X_B+>>7?SYE z4%3UQ8>_w8diO64sRa^26<{GxV&S67HQ$4Qdi2}hWoJsFZ8<{}$cN@P{V~gjmJnNf+&x|0r-fv1hko!=gY91a z4n#ya3w!8~Rs&Z_c!*a4G}rf5u5iQx;)GTolx~Cy zl6qlAaI>rB`#|7@06{~G0MC{`+XG+eWB@=mCqKs`;D9}VnThIz!{rBJn*H$40mz7hU z-0`nT)e~0HGkYccRr?MrXj>-{T@z*(mjdPTOhJa>uc?Xi-&}J+k4x(tP~`V2v%rwL zG9!~uUXcvy$eK_1$^z;8n8$T>wd`^Y_6d5=Ys|^5r30=mp*8-;Q)VshMB%F3lxf{o z;jIoHUG>~cl>%b=s7Qx?T6W&VCW4s=x|V52=ZXDSH*yUXM?D=pvPX%Db90&5h<#`g zHUljZ%}Myfb{O(dHWf5zcZME1$1To)SK>vo7>$m5!OPLu{w8oc7KjZ{;*n_M57kBH zVw5UD7nZqz!kLjlM^mK{mAGJgNh&V#^GUNH@m1Si2_nhx8GFBTBmXxlyX;J_nZ~Ja zuJvzgK{(7W`~G5fFcB_-;X@_X;~~f(vh~vGAzrR05H|ZNCyZ|_wFVz)D)l?7EA0h` zCkIbBm$FV@yp`Nj1oW~WnLQk7?s{xwK64B~n+l5Uf&MR7(x-sq^^?Nai^;xKv1B_X>%)SP)o^(iL{om zR{J~!%(RHL#@6^fpri0SRFScE;LqwdD^q~#->$|!lGQTry9E8{O&G>lXvcpej4W*m zO?-(5jvKx?G<(3Yif+=4a!`&`%qa2-$yjBHg3x1)ucd?WQSN9=pemOj=i=I@smAb# zYpL&0(hi0bL#32W zyR3P-;dcVOk_0HV)zY_4#V^%Jg)V?23=}sqKOvo%P7OdRuChiF9jhG}{5B90pYYhL zlR`$mj};CtkUJ=+R=mV|y3uW$Aq0bxeS*#D|AzCvEzEVPtChXD6Y%Xni zlpP_wX@2t2Twj~2y0ZK<1at^4HZi_Pi@*U-g3yxuk3eVF2C7)rMVwx!t9qysP#6$B zp>OMT0r*WzKVH)>qu3-BU<_txg0=r6I?RhCfJ2e!?f+g=xLabWV0L4j^tG|sB|~57 z%i!qsTzCDa=pFLH=;KqTlU?yaQybC8l<;TG(QP%c&#_<4CG+vGd;YFX-RAZ^w+PqU z<6xun^UJ8`(!TJcjpv2MW7taJE@c3&4H4^q2`e`X6j{bRF)=0j+*5BU#_B z4=?=BO44Q9qe7%jy+>D8U`sPj+MWwf;v7J$q<+9U z=Ov1J?r&GzztcG@CMLAW^OL`G_Fijb?#No~Vz&R=dFNGijaxT2|E(tVh}ZQ*9fM1| zuz8g7n?^`!LeS`1V=rRWVhah&Fl`GgFxU99$RTSbcLA`Hx56Veb9eTF$TF*z!GB#d=oJDgJ?Njc)q$rpa|11UlN+!fS4 zRPTC7k4m6NOQ)4Y^WHAMo^}=rd|5XT()V8(w>7IyRzj^-+U?yv+`PdCjP{P?_$$2l zztNNLTtwgq)L!Q-EPgS8QQ~A_5`RFN(O8Is-Qk-gHQ=C=mfaz*7)3iu>bG-P+;vDG zNVz7|6ZU$vKSXI7J6Uf{U?z0YiK~7D&ujR7*f>14=aeMseeQsOxUHYsn}NEfy}Nx3 zZmh61&dMPl<-P#A8-$9j9x0oT@}p&}BcwO?et$HX*TLjKDa; z7jT0+_7^&Tg{UKv7WQ=+0<5K;5jb2EafJ5o-_Y%FGj`D38su$&g=KmF?^ZW1M??rR zzjp%C4gn)U!zR@DE<)Nc<=5VyC^?7%)C@xRs?A7N^n%Yi`FNGGJveZ9z17rF>>AGJ zEraXvtJQI7dRxY!3AO~kPE)%^W>TJfbrP$CeUft&bK|-!QZ_9xhE6{0?RV|BcZckE zhsJaIPri0rqXhp2di@s)%&W=RkKb{^9slJx$tEDoUIJ~`Blg*=CK#fpV=c?P7@2b8xT~psvU@! zv6J^Q)BrB6fMl_9B}G1HdTQ;?VZek*a*@d#uVX|SEHKpJfX?bB)(JAKh_WJ2IZ(`;x?lmrE-? z22q|`G*2VCFolkKHgexX{^MjBgl5Q1N`zJ=|8CA{o%UmT02D6L<+F2#$}e1g=jrq= zjU#ZqIQ3VxQ}gH!1o%IONdT zD$D}2R&)wYwS6b=iqYgA7MdACQTk6dgPxCGi7KQlP4J`gj4%~BMyZhhBS^NEylt3P zmnl5;Bx%LQ}zq{ZnX{wy!OQ{L1E%ByP(_0*O0)A3$es0=Bu6Qk2 zZFR9lrX%NMTP}2m!KY01f6I-?E6pu6NgI^&4HuTEl|LsLcrpz+IH=lZV9n9tn2}92 zFs1vYDw)L977`Gsm2*hJ6nQk3l||m^Qt#jT$8c;yf5l+c?e%u)^#0Am$<*8;4+Q=7Yqpi1PoKm{sQxX^so{Ox-GPBdULY33fv@z-+$XV#vjD2tYHiXzTXC-r|=rE-F8J zdUB0R#j5Q~Nzh)!If$8>#GHeJo~)$|;N)YGx!9NdujR~0% z;-i{syN479kw374`g}&s9}6ioujStyh4cyvJm6_&B9{H~7ipBZYB#-1no*g(e;w{Z zpQLPmGu8}<1HX`hP=QOSe9WKWo>USFd|xiDY;S_wcA_dTW2K&44mHNTo#C_2vEV_Srw{_Z`62VMl-nFc`3Ki?_^*m>V>o?(zo z+t(?nx+-?07Hr4zn1+%FkQ38<$G1SIi=+e9 zWZUGh9{{o4bvUqniK%w=FCUFyqL}rCYO2PGg_=;(mXwU?YHp7EM%9`-0t{AGwmJ-S zvv8wOTYFPG$}5G?1id_b8+{+09hwYLF7;SPUeEjJJCadz)>Io0eOC-NQ41uUT5eNB z+|jCC>BB;A=GMC@EQTj7hD>0iDOUI8F^T_+)?_gCyZ{tr^-0lY@N1u7NLCdJzA1<_ znCL`rBZ(osX;sBc^4aB+U=ovtA#7qYS|ALNNPH!$gW|ve8Q^F>A2)?#$SHx#RU?sO zj)In8`7R|f4WoM9U3zD}vuZ(Vnw5=%eu-1w7>2u>D>&)oCftSmrb%|M z6d09Q7XoH9LqU3299Un}%k?erOuW9TB1+}stLfmDqIoUt=#=Bp=&@_OQq1Hm3wbpe z-js#bCQ6rn^%_6MuUPsIvasD_!<@3>Y7<)rnT+X7?Mk+U##;3^{($gj{^D3UqupV8 z!0tQod@%cuMY*KODEtQet=WpO%RF(|*~PmuG}^r)t^x^QjnVglC(FKeM_VVyNXyRlxeX{-dYQr!RY@enU0( zBfnl7|Bd}oSNcg^3 zR2Z18=OcZ>6vZ+Q-46Gn(&;Ok=7iB!Z7mK%v%`1$g{Z)(C;~4JIoa})1B-lReQAMD zKOr`||BwSBmmt%^6;4~#z2_AwT|BheEkR0!SjdfwM@a^X46<^b!mj*D%36xNKd^*3 zU3niUpl87tfKf1j{IjtVeOoq?UWP_p6a&Dm!Wahcm8B@}s_V0mT`S+MvB1ODD2l`7 zC*8~$^uXC=zYgyQ=10Cq0 zDQ^5OYWHSksmUc!Wf3r&FMn3pCcSyh5MF~nn}x8efNyfg<1oOB$b|`lM|pS zGDT#0ko&|Ao3k_6>(&R~9)mITCHqQX5~#8@`8?o`)qb^|vCQ z%v69y|DA2JCg0U2r07KR8+X%M&kOK;`dqwr@V<0=zJ}bjY-sIpo(5`L*3l=XwI?>b zitkJ{MEE3~7TQ@#Wrq|Ea~NtkR+XuTCTfq>C%ZZpoL&8GAN8N0uPyk?{; ze^waFm`Sef18oTb!jTlVq@hqM45n~VRrOZJE95#RaoX0xq8y){fdSzsD?TbMzH6_X ztOS>;j(jwG7{Lhi_*aAp>_h-g4quTqdRHCRGOE?q;3_lk8=@0V$7@mAUc|P~aIq!u zbcQw`*XEg_9j{4@@?lLJm^&yfXh25gO+se9Z5XDSl1!J5=`lKskHhOiKxvULko&jeO}fp8d>B-uoS-;8*wOHyNXD{G<^>59VAdwNw1RI7M;z)-%BIO4ogbwyM8 zPr5*^9{roB<@s&UlKZeZbt6uP)jQ+{ zE;Dq!NkL{*e6}lSi~`s-f^PqR1b>lYrIlN!F-l%s= z{djn`=y+88;@B`iE~-kNKZ*;GOIW1mn|!7$N<@-)TX#kiB46T96>@%rfh0LdZuSiChv&zzpY)&Cw|_`?%*TD3H=?Y@%c;PVGq-W2F)oYVDPI{L)-mI#e# znG*5M(&Fy7tvck3?$_oFuiaMoZ@t$vg)W;U2ND|Yl}lI((@YR`kbRQy7W?Ei?&QCy z^FS?+zJHCd?Da{3;0nubA%)B3BlfLUwZxYk&ypGmdA89}dql}fTwDz6AD4Sqz5wHAe(qV2Mjc%ib{v{>yv-Vc_DYs4bHahPU# zjWo>EYPY@U)bMY8-dEB5A=!hl>2YvMB-$MPGDh1FF;y+2XXdU7ul4{Kni0f|lBr$e zn|>qQyj_FOFnIzNVFWR--<&*$Gg%gUrrmX_OP?N{HjUPRTW53=hoq%($d#V@5I+dK zSg*yEFiW{J{P|YxXiBWWxs3ndMIG=MBZv>UD9kL-+28=pdp^ddr@r5`Y<`wRe*ZERYX&Zfw zM%jRdhLPv&!c8Ex<3*P{fl?{Iv|9u<{a975_3P)U(I>I8+!wfs33CEmCJ@9w@)ZL4 z|AS(|1sov3|3zWHSpNi{aDbvv{|A@-e?EajI6)l$tNI7bf_peY1knFf;B8LOH>eOM Z@H;1n>3 Date: Mon, 7 Aug 2023 12:45:26 -0400 Subject: [PATCH 08/10] Add nftoken_id, nftoken_ids, and offer_id fields to TransactionMetadata (#463) * add nftoken_id, nftoken_ids, and offer_id to TransactionMetadata. * add tx metadata fixtures for nft transactions with new metadata fields. * merge manual fixtures --- .../model/ledger/NfTokenOfferObject.java | 7 +++ .../transactions/TransactionMetadata.java | 46 ++++++++++++++---- .../nft/NfTokenOfferObjectJsonTests.java | 4 ++ .../transactions/TransactionMetadataTest.java | 4 -- .../tx_meta_manual_fixtures.json.zip | Bin 341596 -> 344760 bytes .../java/org/xrpl/xrpl4j/tests/NfTokenIT.java | 37 +++++++++++--- 6 files changed, 77 insertions(+), 21 deletions(-) diff --git a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/ledger/NfTokenOfferObject.java b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/ledger/NfTokenOfferObject.java index 0f8dada23..07ecc3f1f 100644 --- a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/ledger/NfTokenOfferObject.java +++ b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/ledger/NfTokenOfferObject.java @@ -147,4 +147,11 @@ default LedgerEntryType ledgerEntryType() { */ @JsonProperty("Flags") NfTokenOfferFlags flags(); + + /** + * The unique ID of this {@link NfTokenOfferObject}. + * + * @return A {@link Hash256} containing the ID. + */ + Hash256 index(); } diff --git a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/TransactionMetadata.java b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/TransactionMetadata.java index d804e97b9..d815a6f09 100644 --- a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/TransactionMetadata.java +++ b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/TransactionMetadata.java @@ -9,9 +9,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -31,9 +31,9 @@ import java.util.Optional; /** - * Transaction metadata is a section of data that gets added to a transaction after it is processed. - * Any transaction that gets included in a ledger has metadata, regardless of whether it is successful. - * The transaction metadata describes the outcome of the transaction in detail. + * Transaction metadata is a section of data that gets added to a transaction after it is processed. Any transaction + * that gets included in a ledger has metadata, regardless of whether it is successful. The transaction metadata + * describes the outcome of the transaction in detail. * * @see "https://xrpl.org/transaction-metadata.html" */ @@ -52,8 +52,8 @@ static ImmutableTransactionMetadata.Builder builder() { } /** - * The transaction's position within the ledger that included it. This is zero-indexed. - * For example, the value 2 means it was the 3rd transaction in that ledger. + * The transaction's position within the ledger that included it. This is zero-indexed. For example, the value 2 means + * it was the 3rd transaction in that ledger. * * @return index of transaction within ledger. */ @@ -69,15 +69,41 @@ static ImmutableTransactionMetadata.Builder builder() { String transactionResult(); /** - * The Currency Amount actually received by the Destination account. - * Use this field to determine how much was delivered, regardless of whether the transaction is a partial payment. - * Omitted for non-Payment transactions. + * The Currency Amount actually received by the Destination account. Use this field to determine how much was + * delivered, regardless of whether the transaction is a partial payment. Omitted for non-Payment transactions. * * @return delivered amount for payments, otherwise empty for non-payments. */ @JsonProperty("delivered_amount") Optional deliveredAmount(); + /** + * The {@link NfTokenId} of the {@link org.xrpl.xrpl4j.model.ledger.NfToken} that was changed as a result of this + * transaction. Only present in metadata for {@link NfTokenMint} and {@link NfTokenAcceptOffer} transactions. + * + * @return An optionally-present {@link NfTokenId}. + */ + @JsonProperty("nftoken_id") + Optional nfTokenId(); + + /** + * The {@link NfTokenId}s of the {@link org.xrpl.xrpl4j.model.ledger.NfToken}s changed by a {@link NfTokenCancelOffer} + * transaction. Only present in metadata for {@link NfTokenCancelOffer} transactions. + * + * @return A {@link List} of {@link NfTokenId}s. + */ + @JsonProperty("nftoken_ids") + List nfTokenIds(); + + /** + * The ID of the offer created by {@link NfTokenCreateOffer} transactions. Only present in metadata for + * {@link NfTokenCreateOffer} transactions. + * + * @return An optionally-present {@link Hash256} denoting the offer ID. + */ + @JsonProperty("offer_id") + Optional offerId(); + @JsonProperty("AffectedNodes") List affectedNodes(); } diff --git a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/client/nft/NfTokenOfferObjectJsonTests.java b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/client/nft/NfTokenOfferObjectJsonTests.java index 95cbbe378..461ae4541 100644 --- a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/client/nft/NfTokenOfferObjectJsonTests.java +++ b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/client/nft/NfTokenOfferObjectJsonTests.java @@ -45,6 +45,7 @@ public void testJsonWithXrpAmount() throws JsonProcessingException, JSONExceptio .previousTransactionLedgerSequence(UnsignedInteger.valueOf(14090896)) .nfTokenId(NfTokenId.of("000B013A95F14B0044F78A264E41713C64B5F89242540EE208C3098E00000D65")) .flags(NfTokenOfferFlags.BUY_TOKEN) + .index(Hash256.of("AEBABA4FAC212BF28E0F9A9C3788A47B085557EC5D1429E7A8266FB859C863B3")) .build(); String json = "{\n" + @@ -55,6 +56,7 @@ public void testJsonWithXrpAmount() throws JsonProcessingException, JSONExceptio " \"Destination\": \"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn\",\n" + " \"PreviousTxnID\": \"E3FE6EA3D48F0C2B639448020EA4F03D4F4F8FFDB243A852A0F59177921B4879\",\n" + " \"PreviousTxnLgrSeq\": 14090896,\n" + + " \"index\": \"AEBABA4FAC212BF28E0F9A9C3788A47B085557EC5D1429E7A8266FB859C863B3\",\n" + " \"LedgerEntryType\": \"NFTokenOffer\"\n" + "}"; @@ -77,6 +79,7 @@ public void testJsonWithIssuedCurrencyAmount() throws JsonProcessingException, J .previousTransactionLedgerSequence(UnsignedInteger.valueOf(14090896)) .nfTokenId(NfTokenId.of("000B013A95F14B0044F78A264E41713C64B5F89242540EE208C3098E00000D65")) .flags(NfTokenOfferFlags.BUY_TOKEN) + .index(Hash256.of("AEBABA4FAC212BF28E0F9A9C3788A47B085557EC5D1429E7A8266FB859C863B3")) .build(); String json = "{\n" + @@ -91,6 +94,7 @@ public void testJsonWithIssuedCurrencyAmount() throws JsonProcessingException, J " \"Destination\": \"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn\",\n" + " \"PreviousTxnID\": \"E3FE6EA3D48F0C2B639448020EA4F03D4F4F8FFDB243A852A0F59177921B4879\",\n" + " \"PreviousTxnLgrSeq\": 14090896,\n" + + " \"index\": \"AEBABA4FAC212BF28E0F9A9C3788A47B085557EC5D1429E7A8266FB859C863B3\",\n" + " \"LedgerEntryType\": \"NFTokenOffer\"\n" + "}"; diff --git a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/TransactionMetadataTest.java b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/TransactionMetadataTest.java index f6567d73d..d572a6f42 100644 --- a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/TransactionMetadataTest.java +++ b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/TransactionMetadataTest.java @@ -18,10 +18,8 @@ import org.xrpl.xrpl4j.model.transactions.metadata.AffectedNode; import org.xrpl.xrpl4j.model.transactions.metadata.CreatedNode; import org.xrpl.xrpl4j.model.transactions.metadata.DeletedNode; -import org.xrpl.xrpl4j.model.transactions.metadata.ImmutableCreatedNode; import org.xrpl.xrpl4j.model.transactions.metadata.ImmutableDeletedNode; import org.xrpl.xrpl4j.model.transactions.metadata.ImmutableMetaNfTokenOfferObject; -import org.xrpl.xrpl4j.model.transactions.metadata.ImmutableModifiedNode; import org.xrpl.xrpl4j.model.transactions.metadata.MetaAccountRootObject; import org.xrpl.xrpl4j.model.transactions.metadata.MetaCheckObject; import org.xrpl.xrpl4j.model.transactions.metadata.MetaDepositPreAuthObject; @@ -39,8 +37,6 @@ import org.xrpl.xrpl4j.model.transactions.metadata.ModifiedNode; import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.nio.file.Files; diff --git a/xrpl4j-core/src/test/resources/tx_meta_manual_fixtures.json.zip b/xrpl4j-core/src/test/resources/tx_meta_manual_fixtures.json.zip index 155b0b7e5b9090de9f921285816efed4d82cd306..c8fba08e40b0fc3a0c160c0bb07c4c019285e705 100644 GIT binary patch delta 16817 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 U-2b3eQ2*)9f6;~;_CNo>01hQ%T>t<8 diff --git a/xrpl4j-integration-tests/src/test/java/org/xrpl/xrpl4j/tests/NfTokenIT.java b/xrpl4j-integration-tests/src/test/java/org/xrpl/xrpl4j/tests/NfTokenIT.java index 16255ab3a..cc0a143d2 100644 --- a/xrpl4j-integration-tests/src/test/java/org/xrpl/xrpl4j/tests/NfTokenIT.java +++ b/xrpl4j-integration-tests/src/test/java/org/xrpl/xrpl4j/tests/NfTokenIT.java @@ -23,6 +23,7 @@ import static org.assertj.core.api.Assertions.assertThat; import com.fasterxml.jackson.core.JsonProcessingException; +import com.google.common.collect.Lists; import com.google.common.primitives.UnsignedInteger; import com.google.common.primitives.UnsignedLong; import org.junit.jupiter.api.Test; @@ -58,6 +59,7 @@ import org.xrpl.xrpl4j.model.transactions.NfTokenId; import org.xrpl.xrpl4j.model.transactions.NfTokenMint; import org.xrpl.xrpl4j.model.transactions.NfTokenUri; +import org.xrpl.xrpl4j.model.transactions.TransactionMetadata; import org.xrpl.xrpl4j.model.transactions.TransactionResultCodes; import org.xrpl.xrpl4j.model.transactions.XrpCurrencyAmount; @@ -93,6 +95,13 @@ void mint() throws JsonRpcClientErrorException, JsonProcessingException { assertThat(mintSubmitResult.engineResult()).isEqualTo(TransactionResultCodes.TES_SUCCESS); assertThat(signedMint.hash()).isEqualTo(mintSubmitResult.transactionResult().hash()); + TransactionResult validatedMint = this.scanForResult( + () -> this.getValidatedTransaction( + mintSubmitResult.transactionResult().hash(), + NfTokenMint.class + ) + ); + NfTokenObject nfToken = this.scanForResult( () -> { try { @@ -103,11 +112,15 @@ void mint() throws JsonRpcClientErrorException, JsonProcessingException { }, result -> result.accountNfts().stream() .anyMatch(nft -> nft.uri().get().equals(uri)) - ).accountNfts() + ) + .accountNfts() .stream().filter(nft -> nft.uri().get().equals(uri)) .findFirst() .get(); + assertThat(validatedMint.metadata().flatMap(TransactionMetadata::nfTokenId)) + .isNotEmpty().get().isEqualTo(nfToken.nfTokenId()); + Optional maybeNfTokenPage = xrplClient.accountObjects( AccountObjectsRequestParams.of(nfTokenMint.account()) ).accountObjects().stream() @@ -143,7 +156,6 @@ void mintFromOtherMinterAccount() throws JsonRpcClientErrorException, JsonProces () -> this.getValidatedAccountInfo(keyPair.publicKey().deriveAddress()) ); - AccountSet accountSet = AccountSet.builder() .account(keyPair.publicKey().deriveAddress()) .sequence(accountInfoResult.accountData().sequence()) @@ -360,7 +372,7 @@ void mintAndCreateOffer() throws JsonRpcClientErrorException, JsonProcessingExce assertThat(signedOffer.hash()).isEqualTo(nfTokenCreateOfferSubmitResult.transactionResult().hash()); //verify the offer was created - this.scanForResult( + TransactionResult validatedOfferCreate = this.scanForResult( () -> this.getValidatedTransaction( nfTokenCreateOfferSubmitResult.transactionResult().hash(), NfTokenCreateOffer.class @@ -368,14 +380,22 @@ void mintAndCreateOffer() throws JsonRpcClientErrorException, JsonProcessingExce ); logger.info("NFT Create Offer (Sell) transaction was validated successfully."); - this.scanForResult( + NfTokenOfferObject nfTokenOffer = (NfTokenOfferObject) this.scanForResult( () -> this.getValidatedAccountObjects(keyPair.publicKey().deriveAddress()), objectsResult -> objectsResult.accountObjects().stream() .anyMatch(object -> NfTokenOfferObject.class.isAssignableFrom(object.getClass()) && ((NfTokenOfferObject) object).owner().equals(keyPair.publicKey().deriveAddress()) ) - ); + ).accountObjects() + .stream() + .filter(object -> NfTokenOfferObject.class.isAssignableFrom(object.getClass()) && + ((NfTokenOfferObject) object).owner().equals(keyPair.publicKey().deriveAddress())) + .findFirst() + .get(); + + assertThat(validatedOfferCreate.metadata().flatMap(TransactionMetadata::offerId)).isNotEmpty().get() + .isEqualTo(nfTokenOffer.index()); logger.info("NFTokenOffer object was found in account's objects."); } @@ -605,14 +625,17 @@ void mintAndCreateOfferThenCancelOffer() throws JsonRpcClientErrorException, Jso assertThat(signedCancel.hash()).isEqualTo(nfTokenCancelOfferSubmitResult.transactionResult().hash()); //verify the offer was created - this.scanForResult( + TransactionResult validatedOfferCancel = this.scanForResult( () -> this.getValidatedTransaction( nfTokenCancelOfferSubmitResult.transactionResult().hash(), - NfTokenCreateOffer.class + NfTokenCancelOffer.class ) ); logger.info("NFT Cancel Offer transaction was validated successfully."); + assertThat(validatedOfferCancel.metadata().map(TransactionMetadata::nfTokenIds)).isNotEmpty().get() + .isEqualTo(Lists.newArrayList(tokenId)); + this.scanForResult( () -> this.getValidatedAccountObjects(wallet.publicKey().deriveAddress()), objectsResult -> objectsResult.accountObjects().stream() From d8b08f29d06891aada6fac1ca8b51fbcd0add503 Mon Sep 17 00:00:00 2001 From: nkramer44 Date: Mon, 7 Aug 2023 13:54:02 -0400 Subject: [PATCH 09/10] Fix NfTokenBuyOffers and NfTokenSellOffers RequestParams/Results (#465) * fix NftBuyOffersRequestParams and NftBuyOffersResult * fix NftSellOffersRequestParams and NftSellOffersResult --- .../xrpl4j/model/client/nft/BuyOffer.java | 2 - .../client/nft/NftBuyOffersRequestParams.java | 17 +++++ .../model/client/nft/NftBuyOffersResult.java | 18 +++++ .../nft/NftSellOffersRequestParams.java | 17 +++++ .../model/client/nft/NftSellOffersResult.java | 18 +++++ .../xrpl4j/model/client/nft/SellOffer.java | 2 - .../nft/NfTokenSellOffersResultTest.java | 75 +++++++++++-------- .../nft/NftBuyOffersRequestParamsTest.java | 41 +++++++++- .../client/nft/NftBuyOffersResultTest.java | 73 ++++++++++-------- .../nft/NftSellOffersRequestParamsTest.java | 53 ++++++++++++- 10 files changed, 247 insertions(+), 69 deletions(-) diff --git a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/client/nft/BuyOffer.java b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/client/nft/BuyOffer.java index 6e9b8d65d..37ff8a9d2 100644 --- a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/client/nft/BuyOffer.java +++ b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/client/nft/BuyOffer.java @@ -53,7 +53,6 @@ static ImmutableBuyOffer.Builder builder() { * * @return The {@link CurrencyAmount}. */ - @JsonProperty("Amount") CurrencyAmount amount(); /** @@ -62,7 +61,6 @@ static ImmutableBuyOffer.Builder builder() { * * @return The {@link NfTokenOfferFlags} for this object. */ - @JsonProperty("Flags") NfTokenOfferFlags flags(); /** diff --git a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/client/nft/NftBuyOffersRequestParams.java b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/client/nft/NftBuyOffersRequestParams.java index 878f9759e..7c82f738f 100644 --- a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/client/nft/NftBuyOffersRequestParams.java +++ b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/client/nft/NftBuyOffersRequestParams.java @@ -21,11 +21,13 @@ */ import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonUnwrapped; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize; import com.google.common.primitives.UnsignedInteger; import org.immutables.value.Value; import org.xrpl.xrpl4j.model.client.XrplRequestParams; +import org.xrpl.xrpl4j.model.client.common.LedgerSpecifier; import org.xrpl.xrpl4j.model.transactions.Marker; import org.xrpl.xrpl4j.model.transactions.NfTokenId; @@ -56,6 +58,21 @@ static ImmutableNftBuyOffersRequestParams.Builder builder() { @JsonProperty("nft_id") NfTokenId nfTokenId(); + /** + * Specifies the ledger version to request. A ledger version can be specified by ledger hash, + * numerical ledger index, or a shortcut value. + * + * @return A {@link LedgerSpecifier} specifying the ledger version to request. + */ + @JsonUnwrapped + @Value.Default + // This field was missing in xrpl4j <= 3.1.2. Normally, this would be a required field, but in order + // to not make a breaking change, this needs to be defaulted. rippled will default to "validated" for you, + // so defaulting to LedgerSpecifier.VALIDATED preserves the existing 3.x.x behavior. + default LedgerSpecifier ledgerSpecifier() { + return LedgerSpecifier.VALIDATED; + } + /** * Limit the number of buy offers for the {@link NfTokenId}. The server is not required to honor * this value. Must be within the inclusive range 10 to 400. diff --git a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/client/nft/NftBuyOffersResult.java b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/client/nft/NftBuyOffersResult.java index 7b3fa483b..b42fb2b18 100644 --- a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/client/nft/NftBuyOffersResult.java +++ b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/client/nft/NftBuyOffersResult.java @@ -23,11 +23,14 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.google.common.primitives.UnsignedInteger; import org.immutables.value.Value; import org.xrpl.xrpl4j.model.client.XrplResult; +import org.xrpl.xrpl4j.model.transactions.Marker; import org.xrpl.xrpl4j.model.transactions.NfTokenId; import java.util.List; +import java.util.Optional; /** * The result of an "nft_buy_offers" rippled API method call. @@ -59,4 +62,19 @@ static ImmutableNftBuyOffersResult.Builder builder() { * @return {@link List} of all {@link BuyOffer}s owned by an account. */ List offers(); + + /** + * The limit, as specified in the {@link NftBuyOffersRequestParams}. + * + * @return An optionally-present {@link UnsignedInteger}. + */ + Optional limit(); + + /** + * Server-defined value indicating the response is paginated. Pass this to the next call to resume where this + * call left off. Omitted when there are no additional pages after this one. + * + * @return An optionally-present {@link Marker} containing a marker. + */ + Optional marker(); } diff --git a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/client/nft/NftSellOffersRequestParams.java b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/client/nft/NftSellOffersRequestParams.java index a7003561f..c01e5544a 100644 --- a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/client/nft/NftSellOffersRequestParams.java +++ b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/client/nft/NftSellOffersRequestParams.java @@ -21,11 +21,13 @@ */ import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonUnwrapped; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize; import com.google.common.primitives.UnsignedInteger; import org.immutables.value.Value; import org.xrpl.xrpl4j.model.client.XrplRequestParams; +import org.xrpl.xrpl4j.model.client.common.LedgerSpecifier; import org.xrpl.xrpl4j.model.transactions.Marker; import org.xrpl.xrpl4j.model.transactions.NfTokenId; @@ -56,6 +58,21 @@ static ImmutableNftSellOffersRequestParams.Builder builder() { @JsonProperty("nft_id") NfTokenId nfTokenId(); + /** + * Specifies the ledger version to request. A ledger version can be specified by ledger hash, + * numerical ledger index, or a shortcut value. + * + * @return A {@link LedgerSpecifier} specifying the ledger version to request. + */ + @JsonUnwrapped + @Value.Default + // This field was missing in xrpl4j <= 3.1.2. Normally, this would be a required field, but in order + // to not make a breaking change, this needs to be defaulted. rippled will default to "validated" for you, + // so defaulting to LedgerSpecifier.VALIDATED preserves the existing 3.x.x behavior. + default LedgerSpecifier ledgerSpecifier() { + return LedgerSpecifier.VALIDATED; + } + /** * Limit the number of sell offers for the {@link NfTokenId}. The server is not required to honor * this value. Must be within the inclusive range 10 to 400. diff --git a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/client/nft/NftSellOffersResult.java b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/client/nft/NftSellOffersResult.java index a1aca21d9..e5bfadebe 100644 --- a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/client/nft/NftSellOffersResult.java +++ b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/client/nft/NftSellOffersResult.java @@ -23,11 +23,14 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.google.common.primitives.UnsignedInteger; import org.immutables.value.Value; import org.xrpl.xrpl4j.model.client.XrplResult; +import org.xrpl.xrpl4j.model.transactions.Marker; import org.xrpl.xrpl4j.model.transactions.NfTokenId; import java.util.List; +import java.util.Optional; /** * The result of an "nft_sell_offers" rippled API method call. @@ -60,4 +63,19 @@ static ImmutableNftSellOffersResult.Builder builder() { * @return {@link List} of all {@link SellOffer}s owned by an account. */ List offers(); + + /** + * The limit, as specified in the {@link NftSellOffersRequestParams}. + * + * @return An optionally-present {@link UnsignedInteger}. + */ + Optional limit(); + + /** + * Server-defined value indicating the response is paginated. Pass this to the next call to resume where this + * call left off. Omitted when there are no additional pages after this one. + * + * @return An optionally-present {@link Marker} containing a marker. + */ + Optional marker(); } diff --git a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/client/nft/SellOffer.java b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/client/nft/SellOffer.java index 6fd2fcb10..3dd869832 100644 --- a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/client/nft/SellOffer.java +++ b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/client/nft/SellOffer.java @@ -51,7 +51,6 @@ static ImmutableSellOffer.Builder builder() { * * @return The {@link CurrencyAmount}. */ - @JsonProperty("Amount") CurrencyAmount amount(); /** @@ -59,7 +58,6 @@ static ImmutableSellOffer.Builder builder() { * * @return The {@link NfTokenOfferFlags} for this object. */ - @JsonProperty("Flags") NfTokenOfferFlags flags(); /** diff --git a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/client/nft/NfTokenSellOffersResultTest.java b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/client/nft/NfTokenSellOffersResultTest.java index fa9dfc38e..da11b0c48 100644 --- a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/client/nft/NfTokenSellOffersResultTest.java +++ b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/client/nft/NfTokenSellOffersResultTest.java @@ -21,6 +21,7 @@ */ import com.fasterxml.jackson.core.JsonProcessingException; +import com.google.common.primitives.UnsignedInteger; import org.json.JSONException; import org.junit.jupiter.api.Test; import org.xrpl.xrpl4j.model.AbstractJsonTest; @@ -28,6 +29,7 @@ import org.xrpl.xrpl4j.model.transactions.Address; import org.xrpl.xrpl4j.model.transactions.Hash256; import org.xrpl.xrpl4j.model.transactions.IssuedCurrencyAmount; +import org.xrpl.xrpl4j.model.transactions.Marker; import org.xrpl.xrpl4j.model.transactions.NfTokenId; import org.xrpl.xrpl4j.model.transactions.XrpCurrencyAmount; @@ -38,24 +40,21 @@ public class NfTokenSellOffersResultTest extends AbstractJsonTest { @Test public void testWithXrpAmount() throws JsonProcessingException, JSONException { - SellOffer sellOffer = SellOffer.builder() - .amount(XrpCurrencyAmount.ofDrops(1000)) - .owner(Address.of("rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW")) - .flags(NfTokenOfferFlags.AUTHORIZED) - .nftOfferIndex(Hash256.of("000100001E962F495F07A990F4ED55ACCFEEF365DBAA76B6A048C0A200000007")) - .build(); - - List list = new ArrayList<>(); - list.add(sellOffer); - NftSellOffersResult params = NftSellOffersResult.builder() .nfTokenId(NfTokenId.of("000100001E962F495F07A990F4ED55ACCFEEF365DBAA76B6A048C0A200000007")) - .offers(list) + .addOffers( + SellOffer.builder() + .amount(XrpCurrencyAmount.ofDrops(1000)) + .owner(Address.of("rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW")) + .flags(NfTokenOfferFlags.AUTHORIZED) + .nftOfferIndex(Hash256.of("000100001E962F495F07A990F4ED55ACCFEEF365DBAA76B6A048C0A200000007")) + .build() + ) .build(); String offer = "{\n" + - " \"Flags\": 2,\n" + - " \"Amount\": \"1000\",\n" + + " \"flags\": 2,\n" + + " \"amount\": \"1000\",\n" + " \"owner\": \"rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW\",\n" + " \"nft_offer_index\": \"000100001E962F495F07A990F4ED55ACCFEEF365DBAA76B6A048C0A200000007\"\n" + "}"; @@ -70,29 +69,26 @@ public void testWithXrpAmount() throws JsonProcessingException, JSONException { @Test public void testWithIssuedCurrencyAmount() throws JsonProcessingException, JSONException { - SellOffer sellOffer = SellOffer.builder() - .amount(IssuedCurrencyAmount.builder() - .issuer(Address.of("rsjYGpMWQeNBXbUTkVz4ZKzHefgZSr6rys")) - .currency("USD") - .value("100") - .build() - ) - .owner(Address.of("rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW")) - .flags(NfTokenOfferFlags.AUTHORIZED) - .nftOfferIndex(Hash256.of("000100001E962F495F07A990F4ED55ACCFEEF365DBAA76B6A048C0A200000007")) - .build(); - - List list = new ArrayList<>(); - list.add(sellOffer); - NftSellOffersResult params = NftSellOffersResult.builder() .nfTokenId(NfTokenId.of("000100001E962F495F07A990F4ED55ACCFEEF365DBAA76B6A048C0A200000007")) - .offers(list) + .addOffers( + SellOffer.builder() + .amount(IssuedCurrencyAmount.builder() + .issuer(Address.of("rsjYGpMWQeNBXbUTkVz4ZKzHefgZSr6rys")) + .currency("USD") + .value("100") + .build() + ) + .owner(Address.of("rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW")) + .flags(NfTokenOfferFlags.AUTHORIZED) + .nftOfferIndex(Hash256.of("000100001E962F495F07A990F4ED55ACCFEEF365DBAA76B6A048C0A200000007")) + .build() + ) .build(); String offer = "{\n" + - " \"Flags\": 2,\n" + - " \"Amount\": {\n" + + " \"flags\": 2,\n" + + " \"amount\": {\n" + " \"issuer\": \"rsjYGpMWQeNBXbUTkVz4ZKzHefgZSr6rys\",\n" + " \"currency\": \"USD\",\n" + " \"value\": \"100\"\n" + @@ -108,4 +104,21 @@ public void testWithIssuedCurrencyAmount() throws JsonProcessingException, JSONE assertCanSerializeAndDeserialize(params, json); } + + @Test + public void testWithLimitAndMarker() throws JsonProcessingException, JSONException { + NftSellOffersResult params = NftSellOffersResult.builder() + .nfTokenId(NfTokenId.of("000100001E962F495F07A990F4ED55ACCFEEF365DBAA76B6A048C0A200000007")) + .limit(UnsignedInteger.ONE) + .marker(Marker.of("123")) + .build(); + + String json = "{\n" + + " \"nft_id\": \"000100001E962F495F07A990F4ED55ACCFEEF365DBAA76B6A048C0A200000007\",\n" + + " \"limit\": 1,\n" + + " \"marker\": \"123\"\n" + + "}"; + + assertCanSerializeAndDeserialize(params, json); + } } diff --git a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/client/nft/NftBuyOffersRequestParamsTest.java b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/client/nft/NftBuyOffersRequestParamsTest.java index 87fbd8b79..e334eab44 100644 --- a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/client/nft/NftBuyOffersRequestParamsTest.java +++ b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/client/nft/NftBuyOffersRequestParamsTest.java @@ -25,19 +25,55 @@ import org.json.JSONException; import org.junit.jupiter.api.Test; import org.xrpl.xrpl4j.model.AbstractJsonTest; +import org.xrpl.xrpl4j.model.client.common.LedgerSpecifier; +import org.xrpl.xrpl4j.model.transactions.Hash256; import org.xrpl.xrpl4j.model.transactions.Marker; import org.xrpl.xrpl4j.model.transactions.NfTokenId; public class NftBuyOffersRequestParamsTest extends AbstractJsonTest { @Test - public void testWithRequiredValue() throws JsonProcessingException, JSONException { + public void testWithLedgerIndexShortcut() throws JsonProcessingException, JSONException { NftBuyOffersRequestParams params = NftBuyOffersRequestParams.builder() .nfTokenId(NfTokenId.of("000100001E962F495F07A990F4ED55ACCFEEF365DBAA76B6A048C0A200000007")) + .ledgerSpecifier(LedgerSpecifier.CURRENT) .build(); String json = "{\n" + - " \"nft_id\": \"000100001E962F495F07A990F4ED55ACCFEEF365DBAA76B6A048C0A200000007\"\n" + + " \"nft_id\": \"000100001E962F495F07A990F4ED55ACCFEEF365DBAA76B6A048C0A200000007\",\n" + + " \"ledger_index\": \"current\"\n" + + " }"; + + assertCanSerializeAndDeserialize(params, json); + } + + @Test + public void testWithLedgerIndexNumber() throws JsonProcessingException, JSONException { + NftBuyOffersRequestParams params = NftBuyOffersRequestParams.builder() + .nfTokenId(NfTokenId.of("000100001E962F495F07A990F4ED55ACCFEEF365DBAA76B6A048C0A200000007")) + .ledgerSpecifier(LedgerSpecifier.of(100)) + .build(); + + String json = "{\n" + + " \"nft_id\": \"000100001E962F495F07A990F4ED55ACCFEEF365DBAA76B6A048C0A200000007\",\n" + + " \"ledger_index\": 100\n" + + " }"; + + assertCanSerializeAndDeserialize(params, json); + } + + @Test + public void testWithLedgerHash() throws JsonProcessingException, JSONException { + NftBuyOffersRequestParams params = NftBuyOffersRequestParams.builder() + .nfTokenId(NfTokenId.of("000100001E962F495F07A990F4ED55ACCFEEF365DBAA76B6A048C0A200000007")) + .ledgerSpecifier( + LedgerSpecifier.of(Hash256.of("000100001E962F495F07A990F4ED55ACCFEEF365DBAA76B6A048C0A200000007")) + ) + .build(); + + String json = "{\n" + + " \"nft_id\": \"000100001E962F495F07A990F4ED55ACCFEEF365DBAA76B6A048C0A200000007\",\n" + + " \"ledger_hash\": \"000100001E962F495F07A990F4ED55ACCFEEF365DBAA76B6A048C0A200000007\"\n" + " }"; assertCanSerializeAndDeserialize(params, json); @@ -54,6 +90,7 @@ public void testWithAllValues() throws JsonProcessingException, JSONException { String json = "{\n" + " \"nft_id\": \"000100001E962F495F07A990F4ED55ACCFEEF365DBAA76B6A048C0A200000007\",\n" + " \"limit\": 10,\n" + + " \"ledger_index\": \"validated\",\n" + " \"marker\": \"123\"\n" + " }"; diff --git a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/client/nft/NftBuyOffersResultTest.java b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/client/nft/NftBuyOffersResultTest.java index ac44bf75c..db1f2a9e2 100644 --- a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/client/nft/NftBuyOffersResultTest.java +++ b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/client/nft/NftBuyOffersResultTest.java @@ -21,6 +21,7 @@ */ import com.fasterxml.jackson.core.JsonProcessingException; +import com.google.common.primitives.UnsignedInteger; import org.json.JSONException; import org.junit.jupiter.api.Test; import org.xrpl.xrpl4j.model.AbstractJsonTest; @@ -28,6 +29,7 @@ import org.xrpl.xrpl4j.model.transactions.Address; import org.xrpl.xrpl4j.model.transactions.Hash256; import org.xrpl.xrpl4j.model.transactions.IssuedCurrencyAmount; +import org.xrpl.xrpl4j.model.transactions.Marker; import org.xrpl.xrpl4j.model.transactions.NfTokenId; import org.xrpl.xrpl4j.model.transactions.XrpCurrencyAmount; @@ -38,25 +40,21 @@ public class NftBuyOffersResultTest extends AbstractJsonTest { @Test public void testWithXrpCurrencyAmount() throws JsonProcessingException, JSONException { - - BuyOffer buyOffer = BuyOffer.builder() - .amount(XrpCurrencyAmount.ofDrops(1000)) - .owner(Address.of("rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW")) - .flags(NfTokenOfferFlags.BUY_TOKEN) - .nftOfferIndex(Hash256.of("000100001E962F495F07A990F4ED55ACCFEEF365DBAA76B6A048C0A200000007")) - .build(); - - List list = new ArrayList<>(); - list.add(buyOffer); - NftBuyOffersResult params = NftBuyOffersResult.builder() .nfTokenId(NfTokenId.of("000100001E962F495F07A990F4ED55ACCFEEF365DBAA76B6A048C0A200000007")) - .offers(list) + .addOffers( + BuyOffer.builder() + .amount(XrpCurrencyAmount.ofDrops(1000)) + .owner(Address.of("rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW")) + .flags(NfTokenOfferFlags.BUY_TOKEN) + .nftOfferIndex(Hash256.of("000100001E962F495F07A990F4ED55ACCFEEF365DBAA76B6A048C0A200000007")) + .build() + ) .build(); String offer = "{\n" + - " \"Flags\": 1,\n" + - " \"Amount\": \"1000\",\n" + + " \"flags\": 1,\n" + + " \"amount\": \"1000\",\n" + " \"owner\": \"rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW\",\n" + " \"nft_offer_index\": \"000100001E962F495F07A990F4ED55ACCFEEF365DBAA76B6A048C0A200000007\"\n" + "}"; @@ -70,31 +68,44 @@ public void testWithXrpCurrencyAmount() throws JsonProcessingException, JSONExce } @Test - public void testWithIssuedCurrencyAmount() throws JsonProcessingException, JSONException { - - BuyOffer buyOffer = BuyOffer.builder() - .amount(IssuedCurrencyAmount.builder() - .issuer(Address.of("rsjYGpMWQeNBXbUTkVz4ZKzHefgZSr6rys")) - .currency("USD") - .value("100") - .build() - ) - .owner(Address.of("rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW")) - .flags(NfTokenOfferFlags.BUY_TOKEN) - .nftOfferIndex(Hash256.of("000100001E962F495F07A990F4ED55ACCFEEF365DBAA76B6A048C0A200000007")) + public void testWithLimitAndMarker() throws JsonProcessingException, JSONException { + NftBuyOffersResult params = NftBuyOffersResult.builder() + .nfTokenId(NfTokenId.of("000100001E962F495F07A990F4ED55ACCFEEF365DBAA76B6A048C0A200000007")) + .limit(UnsignedInteger.ONE) + .marker(Marker.of("123")) .build(); - List list = new ArrayList<>(); - list.add(buyOffer); + String json = "{\n" + + " \"nft_id\": \"000100001E962F495F07A990F4ED55ACCFEEF365DBAA76B6A048C0A200000007\",\n" + + " \"limit\": 1,\n" + + " \"marker\": \"123\"\n" + + "}"; + assertCanSerializeAndDeserialize(params, json); + } + + @Test + public void testWithIssuedCurrencyAmount() throws JsonProcessingException, JSONException { NftBuyOffersResult params = NftBuyOffersResult.builder() .nfTokenId(NfTokenId.of("000100001E962F495F07A990F4ED55ACCFEEF365DBAA76B6A048C0A200000007")) - .offers(list) + .addOffers( + BuyOffer.builder() + .amount(IssuedCurrencyAmount.builder() + .issuer(Address.of("rsjYGpMWQeNBXbUTkVz4ZKzHefgZSr6rys")) + .currency("USD") + .value("100") + .build() + ) + .owner(Address.of("rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW")) + .flags(NfTokenOfferFlags.BUY_TOKEN) + .nftOfferIndex(Hash256.of("000100001E962F495F07A990F4ED55ACCFEEF365DBAA76B6A048C0A200000007")) + .build() + ) .build(); String offer = "{\n" + - " \"Flags\": 1,\n" + - " \"Amount\": {\n" + + " \"flags\": 1,\n" + + " \"amount\": {\n" + " \"issuer\": \"rsjYGpMWQeNBXbUTkVz4ZKzHefgZSr6rys\",\n" + " \"currency\": \"USD\",\n" + " \"value\": \"100\"\n" + diff --git a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/client/nft/NftSellOffersRequestParamsTest.java b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/client/nft/NftSellOffersRequestParamsTest.java index 67ed8fca5..e4277f8db 100644 --- a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/client/nft/NftSellOffersRequestParamsTest.java +++ b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/client/nft/NftSellOffersRequestParamsTest.java @@ -25,6 +25,8 @@ import org.json.JSONException; import org.junit.jupiter.api.Test; import org.xrpl.xrpl4j.model.AbstractJsonTest; +import org.xrpl.xrpl4j.model.client.common.LedgerSpecifier; +import org.xrpl.xrpl4j.model.transactions.Hash256; import org.xrpl.xrpl4j.model.transactions.Marker; import org.xrpl.xrpl4j.model.transactions.NfTokenId; @@ -37,7 +39,55 @@ public void testWithRequiredValue() throws JsonProcessingException, JSONExceptio .build(); String json = "{\n" + - " \"nft_id\": \"000100001E962F495F07A990F4ED55ACCFEEF365DBAA76B6A048C0A200000007\"\n" + + " \"nft_id\": \"000100001E962F495F07A990F4ED55ACCFEEF365DBAA76B6A048C0A200000007\",\n" + + " \"ledger_index\": \"validated\"\n" + + " }"; + + assertCanSerializeAndDeserialize(params, json); + } + + @Test + public void testWithLedgerIndexShortcut() throws JsonProcessingException, JSONException { + NftSellOffersRequestParams params = NftSellOffersRequestParams.builder() + .nfTokenId(NfTokenId.of("000100001E962F495F07A990F4ED55ACCFEEF365DBAA76B6A048C0A200000007")) + .ledgerSpecifier(LedgerSpecifier.CURRENT) + .build(); + + String json = "{\n" + + " \"nft_id\": \"000100001E962F495F07A990F4ED55ACCFEEF365DBAA76B6A048C0A200000007\",\n" + + " \"ledger_index\": \"current\"\n" + + " }"; + + assertCanSerializeAndDeserialize(params, json); + } + + @Test + public void testWithLedgerIndexNumber() throws JsonProcessingException, JSONException { + NftSellOffersRequestParams params = NftSellOffersRequestParams.builder() + .nfTokenId(NfTokenId.of("000100001E962F495F07A990F4ED55ACCFEEF365DBAA76B6A048C0A200000007")) + .ledgerSpecifier(LedgerSpecifier.of(100)) + .build(); + + String json = "{\n" + + " \"nft_id\": \"000100001E962F495F07A990F4ED55ACCFEEF365DBAA76B6A048C0A200000007\",\n" + + " \"ledger_index\": 100\n" + + " }"; + + assertCanSerializeAndDeserialize(params, json); + } + + @Test + public void testWithLedgerHash() throws JsonProcessingException, JSONException { + NftSellOffersRequestParams params = NftSellOffersRequestParams.builder() + .nfTokenId(NfTokenId.of("000100001E962F495F07A990F4ED55ACCFEEF365DBAA76B6A048C0A200000007")) + .ledgerSpecifier( + LedgerSpecifier.of(Hash256.of("000100001E962F495F07A990F4ED55ACCFEEF365DBAA76B6A048C0A200000007")) + ) + .build(); + + String json = "{\n" + + " \"nft_id\": \"000100001E962F495F07A990F4ED55ACCFEEF365DBAA76B6A048C0A200000007\",\n" + + " \"ledger_hash\": \"000100001E962F495F07A990F4ED55ACCFEEF365DBAA76B6A048C0A200000007\"\n" + " }"; assertCanSerializeAndDeserialize(params, json); @@ -53,6 +103,7 @@ public void testWithAllValues() throws JsonProcessingException, JSONException { String json = "{\n" + " \"nft_id\": \"000100001E962F495F07A990F4ED55ACCFEEF365DBAA76B6A048C0A200000007\",\n" + + " \"ledger_index\": \"validated\",\n" + " \"limit\": 10,\n" + " \"marker\": \"123\"\n" + " }"; From 42047e1aa3f48aae9740f8a9705c0d480a368b2b Mon Sep 17 00:00:00 2001 From: nkramer44 Date: Mon, 7 Aug 2023 16:21:35 -0400 Subject: [PATCH 10/10] Add nft_info support (#466) * add nft_info mappings and client method * add IT for calling nft_info on reporting mode server * javadoc * pr feedback --- .../org/xrpl/xrpl4j/client/XrplClient.java | 22 ++++ .../xrpl/xrpl4j/client/XrplClientTest.java | 22 ++++ .../xrpl/xrpl4j/model/client/XrplMethods.java | 5 + .../client/nft/NftInfoRequestParams.java | 50 ++++++++ .../model/client/nft/NftInfoResult.java | 118 ++++++++++++++++++ .../xrpl4j/model/transactions/Wrappers.java | 17 ++- .../client/nft/NftInfoRequestParamsTest.java | 59 +++++++++ .../model/client/nft/NftInfoResultTest.java | 81 ++++++++++++ .../model/transactions/NfTokenMintTest.java | 6 +- .../model/transactions/TransferFeeTest.java | 44 +++++-- .../java/org/xrpl/xrpl4j/tests/NftInfoIT.java | 65 ++++++++++ 11 files changed, 469 insertions(+), 20 deletions(-) create mode 100644 xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/client/nft/NftInfoRequestParams.java create mode 100644 xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/client/nft/NftInfoResult.java create mode 100644 xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/client/nft/NftInfoRequestParamsTest.java create mode 100644 xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/client/nft/NftInfoResultTest.java create mode 100644 xrpl4j-integration-tests/src/test/java/org/xrpl/xrpl4j/tests/NftInfoIT.java diff --git a/xrpl4j-client/src/main/java/org/xrpl/xrpl4j/client/XrplClient.java b/xrpl4j-client/src/main/java/org/xrpl/xrpl4j/client/XrplClient.java index e7928a663..a0c7d04f9 100644 --- a/xrpl4j-client/src/main/java/org/xrpl/xrpl4j/client/XrplClient.java +++ b/xrpl4j-client/src/main/java/org/xrpl/xrpl4j/client/XrplClient.java @@ -64,6 +64,8 @@ import org.xrpl.xrpl4j.model.client.ledger.LedgerResult; import org.xrpl.xrpl4j.model.client.nft.NftBuyOffersRequestParams; import org.xrpl.xrpl4j.model.client.nft.NftBuyOffersResult; +import org.xrpl.xrpl4j.model.client.nft.NftInfoRequestParams; +import org.xrpl.xrpl4j.model.client.nft.NftInfoResult; import org.xrpl.xrpl4j.model.client.nft.NftSellOffersRequestParams; import org.xrpl.xrpl4j.model.client.nft.NftSellOffersResult; import org.xrpl.xrpl4j.model.client.path.BookOffersRequestParams; @@ -511,6 +513,26 @@ public NftSellOffersResult nftSellOffers(NftSellOffersRequestParams params) thro return jsonRpcClient.send(request, NftSellOffersResult.class); } + /** + * Returns information about a given NFT. This method is only supported on Clio servers. Sending this request to a + * Reporting Mode or rippled node will result in an exception. + * + * @param params The {@link NftInfoRequestParams} to send in the request. + * + * @return The {@link NftInfoResult} returned by the {@code nft_info} method call. + * + * @throws JsonRpcClientErrorException If {@code jsonRpcClient} throws an error, or if the request was made to a + * non-Clio node. + */ + public NftInfoResult nftInfo(NftInfoRequestParams params) throws JsonRpcClientErrorException { + JsonRpcRequest request = JsonRpcRequest.builder() + .method(XrplMethods.NFT_INFO) + .addParams(params) + .build(); + + return jsonRpcClient.send(request, NftInfoResult.class); + } + /** * Get the {@link AccountObjectsResult} for the account specified in {@code params} by making an account_objects * method call. diff --git a/xrpl4j-client/src/test/java/org/xrpl/xrpl4j/client/XrplClientTest.java b/xrpl4j-client/src/test/java/org/xrpl/xrpl4j/client/XrplClientTest.java index f141f1a16..728ac8640 100644 --- a/xrpl4j-client/src/test/java/org/xrpl/xrpl4j/client/XrplClientTest.java +++ b/xrpl4j-client/src/test/java/org/xrpl/xrpl4j/client/XrplClientTest.java @@ -79,6 +79,8 @@ import org.xrpl.xrpl4j.model.client.ledger.LedgerResult; import org.xrpl.xrpl4j.model.client.nft.NftBuyOffersRequestParams; import org.xrpl.xrpl4j.model.client.nft.NftBuyOffersResult; +import org.xrpl.xrpl4j.model.client.nft.NftInfoRequestParams; +import org.xrpl.xrpl4j.model.client.nft.NftInfoResult; import org.xrpl.xrpl4j.model.client.nft.NftSellOffersRequestParams; import org.xrpl.xrpl4j.model.client.nft.NftSellOffersResult; import org.xrpl.xrpl4j.model.client.path.BookOffersRequestParams; @@ -1023,4 +1025,24 @@ public void nftSellOffers() throws JsonRpcClientErrorException { assertThat(jsonRpcRequestArgumentCaptor.getValue().params().get(0)).isEqualTo(nftSellOffersRequestParams); } + @Test + void nftInfo() throws JsonRpcClientErrorException { + NftInfoRequestParams params = NftInfoRequestParams.builder() + .nfTokenId(NfTokenId.of("000100001E962F495F07A990F4ED55ACCFEEF365DBAA76B6A048C0A200000007")) + .ledgerSpecifier(LedgerSpecifier.VALIDATED) + .build(); + + NftInfoResult mockResult = mock(NftInfoResult.class); + when(jsonRpcClientMock.send( + JsonRpcRequest.builder() + .method(XrplMethods.NFT_INFO) + .addParams(params) + .build(), + NftInfoResult.class + )).thenReturn(mockResult); + + NftInfoResult result = xrplClient.nftInfo(params); + + assertThat(result).isEqualTo(mockResult); + } } diff --git a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/client/XrplMethods.java b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/client/XrplMethods.java index 9077711f4..2c038ad4a 100644 --- a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/client/XrplMethods.java +++ b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/client/XrplMethods.java @@ -114,6 +114,11 @@ public class XrplMethods { */ public static final String NFT_SELL_OFFERS = "nft_sell_offers"; + /** + * Constant for the nft_info Clio API method. + */ + public static final String NFT_INFO = "nft_info"; + // Transaction methods /** * Constant for the sign rippled API method. diff --git a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/client/nft/NftInfoRequestParams.java b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/client/nft/NftInfoRequestParams.java new file mode 100644 index 000000000..7a6b495ac --- /dev/null +++ b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/client/nft/NftInfoRequestParams.java @@ -0,0 +1,50 @@ +package org.xrpl.xrpl4j.model.client.nft; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonUnwrapped; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import org.immutables.value.Value.Immutable; +import org.xrpl.xrpl4j.model.client.XrplRequestParams; +import org.xrpl.xrpl4j.model.client.common.LedgerSpecifier; +import org.xrpl.xrpl4j.model.transactions.NfTokenId; + +/** + * Request parameters for the {@code nft_info} RPC request. This request is only supported on Clio servers. + */ +@Immutable +@JsonSerialize(as = ImmutableNftInfoRequestParams.class) +@JsonDeserialize(as = ImmutableNftInfoRequestParams.class) +public interface NftInfoRequestParams extends XrplRequestParams { + + /** + * Construct a {@code NftInfoRequestParams} builder. + * + * @return An {@link ImmutableNftInfoRequestParams.Builder}. + */ + static ImmutableNftInfoRequestParams.Builder builder() { + return ImmutableNftInfoRequestParams.builder(); + } + + /** + * A unique identifier for the non-fungible token (NFT). + * + * @return An {@link NfTokenId}. + */ + @JsonProperty("nft_id") + NfTokenId nfTokenId(); + + /** + * Specifies the ledger version to request. A ledger version can be specified by ledger hash, numerical ledger index, + * or a shortcut value. + * + *

Because {@code nft_info} is only supported on Clio nodes, and because Clio does not have access to non-validated + * ledgers, specifying a ledger that has not yet been validated, or specifying a ledger index shortcut other than + * {@link LedgerSpecifier#VALIDATED} will result in Clio returning an error. + * + * @return A {@link LedgerSpecifier} specifying the ledger version to request. + */ + @JsonUnwrapped + LedgerSpecifier ledgerSpecifier(); + +} diff --git a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/client/nft/NftInfoResult.java b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/client/nft/NftInfoResult.java new file mode 100644 index 000000000..fea5eca55 --- /dev/null +++ b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/client/nft/NftInfoResult.java @@ -0,0 +1,118 @@ +package org.xrpl.xrpl4j.model.client.nft; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.google.common.primitives.UnsignedInteger; +import com.google.common.primitives.UnsignedLong; +import org.immutables.value.Value; +import org.xrpl.xrpl4j.model.client.XrplResult; +import org.xrpl.xrpl4j.model.client.common.LedgerIndex; +import org.xrpl.xrpl4j.model.flags.NfTokenFlags; +import org.xrpl.xrpl4j.model.transactions.Address; +import org.xrpl.xrpl4j.model.transactions.NfTokenId; +import org.xrpl.xrpl4j.model.transactions.NfTokenUri; +import org.xrpl.xrpl4j.model.transactions.TransferFee; + +import java.util.Optional; + +/** + * The result of an {@code nft_info} RPC call. + */ +@Value.Immutable +@JsonSerialize(as = ImmutableNftInfoResult.class) +@JsonDeserialize(as = ImmutableNftInfoResult.class) +public interface NftInfoResult extends XrplResult { + + /** + * Construct a {@code NftInfoResult} builder. + * + * @return An {@link ImmutableNftInfoResult.Builder}. + */ + static ImmutableNftInfoResult.Builder builder() { + return ImmutableNftInfoResult.builder(); + } + + /** + * A unique identifier for the non-fungible token (NFT). + * + * @return An {@link NfTokenId}. + */ + @JsonProperty("nft_id") + NfTokenId nftId(); + + /** + * The ledger index of the most recent ledger version where the state of this NFT was modified, as in the NFT was + * minted (created), changed ownership (traded), or burned (destroyed). The information returned contains whatever + * happened most recently compared to the requested ledger. + * + * @return A {@link LedgerIndex}. + */ + @JsonProperty("ledger_index") + LedgerIndex ledgerIndex(); + + /** + * The account ID of this NFT's owner at this ledger index. + * + * @return An {@link Address}. + */ + Address owner(); + + /** + * Whether the NFT is burned at the request ledger. + * + * @return {@code true} if the NFT is burned at this ledger, or {@code false} otherwise. + */ + @JsonProperty("is_burned") + boolean burned(); + + /** + * The flag set of this NFT. + * + * @return An {@link NfTokenFlags}. + */ + NfTokenFlags flags(); + + /** + * The transfer fee of this NFT. + * + * @return A {@link TransferFee}. + */ + @JsonProperty("transfer_fee") + TransferFee transferFee(); + + /** + * The account ID which denotes the issuer of this NFT. + * + * @return An {@link Address}. + */ + Address issuer(); + + /** + * The NFT’s taxon. + * + * @return An {@link UnsignedLong} denoting the taxon. + */ + @JsonProperty("nft_taxon") + UnsignedLong nftTaxon(); + + /** + * The NFT’s sequence number. + * + * @return An {@link UnsignedInteger}. + */ + @JsonProperty("nft_serial") + UnsignedInteger nftSerial(); + + /** + * This field is empty if the NFT is not burned at this ledger but does not have a URI. If the NFT is not burned at + * this ledger, and it does have a URI, this field is a string containing the decoded URI of the NFT. + * + *

NOTE: If you need to retrieve the URI of a burnt token, re-request nft_info for this token, specifying the + * ledger_index as the one previous to the index where this token was burned. + * + * @return An optionally-present {@link NfTokenUri}. + */ + Optional uri(); + +} diff --git a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/Wrappers.java b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/Wrappers.java index a59cd5267..3e887b29a 100644 --- a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/Wrappers.java +++ b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/Wrappers.java @@ -53,6 +53,7 @@ import java.nio.charset.StandardCharsets; import java.text.DecimalFormat; import java.util.Locale; +import java.util.Objects; /** * Wrapped immutable classes for providing type-safe objects. @@ -336,6 +337,10 @@ public boolean equals(Object obj) { /** * A wrapped {@link com.google.common.primitives.UnsignedInteger} containing the TransferFee. + * + *

Valid values for this field are between 0 and 50000 inclusive, allowing transfer rates of between 0.00% and + * 50.00% in increments of 0.001. If this field is provided in a {@link NfTokenMint} transaction, the transaction + * MUST have the {@code tfTransferable} flag enabled. */ @Value.Immutable @Wrapped @@ -351,16 +356,20 @@ public String toString() { /** * Construct {@link TransferFee} as a percentage value. * + *

The given percentage value must have at most 3 decimal places of precision, and must be + * between {@code 0} and {@code 50.000}.

+ * * @param percent of type {@link BigDecimal} * * @return {@link TransferFee} */ - static TransferFee ofPercent(BigDecimal percent) { + public static TransferFee ofPercent(BigDecimal percent) { + Objects.requireNonNull(percent); Preconditions.checkArgument( - Math.max(0, percent.stripTrailingZeros().scale()) <= 2, - "Percent value should have a maximum of 2 decimal places." + Math.max(0, percent.stripTrailingZeros().scale()) <= 3, + "Percent value should have a maximum of 3 decimal places." ); - return TransferFee.of(UnsignedInteger.valueOf(percent.scaleByPowerOfTen(2).toBigIntegerExact())); + return TransferFee.of(UnsignedInteger.valueOf(percent.scaleByPowerOfTen(3).toBigIntegerExact())); } diff --git a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/client/nft/NftInfoRequestParamsTest.java b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/client/nft/NftInfoRequestParamsTest.java new file mode 100644 index 000000000..1b5b26b0c --- /dev/null +++ b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/client/nft/NftInfoRequestParamsTest.java @@ -0,0 +1,59 @@ +package org.xrpl.xrpl4j.model.client.nft; + +import com.fasterxml.jackson.core.JsonProcessingException; +import org.json.JSONException; +import org.junit.jupiter.api.Test; +import org.xrpl.xrpl4j.model.AbstractJsonTest; +import org.xrpl.xrpl4j.model.client.common.LedgerSpecifier; +import org.xrpl.xrpl4j.model.transactions.Hash256; +import org.xrpl.xrpl4j.model.transactions.NfTokenId; + +class NftInfoRequestParamsTest extends AbstractJsonTest { + + @Test + void testWithLedgerIndexShortcut() throws JSONException, JsonProcessingException { + NftInfoRequestParams params = NftInfoRequestParams.builder() + .nfTokenId(NfTokenId.of("000100001E962F495F07A990F4ED55ACCFEEF365DBAA76B6A048C0A200000007")) + .ledgerSpecifier(LedgerSpecifier.VALIDATED) + .build(); + + String json = "{\n" + + " \"nft_id\": \"000100001E962F495F07A990F4ED55ACCFEEF365DBAA76B6A048C0A200000007\",\n" + + " \"ledger_index\": \"validated\"\n" + + " }"; + + assertCanSerializeAndDeserialize(params, json); + } + + @Test + void testWithLedgerIndexNumber() throws JSONException, JsonProcessingException { + NftInfoRequestParams params = NftInfoRequestParams.builder() + .nfTokenId(NfTokenId.of("000100001E962F495F07A990F4ED55ACCFEEF365DBAA76B6A048C0A200000007")) + .ledgerSpecifier(LedgerSpecifier.of(100)) + .build(); + + String json = "{\n" + + " \"nft_id\": \"000100001E962F495F07A990F4ED55ACCFEEF365DBAA76B6A048C0A200000007\",\n" + + " \"ledger_index\": 100\n" + + " }"; + + assertCanSerializeAndDeserialize(params, json); + } + + @Test + void testWithLedgerHash() throws JSONException, JsonProcessingException { + NftInfoRequestParams params = NftInfoRequestParams.builder() + .nfTokenId(NfTokenId.of("000100001E962F495F07A990F4ED55ACCFEEF365DBAA76B6A048C0A200000007")) + .ledgerSpecifier( + LedgerSpecifier.of(Hash256.of("C53ECF838647FA5A4C780377025FEC7999AB4182590510CA461444B207AB74A9")) + ) + .build(); + + String json = "{\n" + + " \"nft_id\": \"000100001E962F495F07A990F4ED55ACCFEEF365DBAA76B6A048C0A200000007\",\n" + + " \"ledger_hash\": \"C53ECF838647FA5A4C780377025FEC7999AB4182590510CA461444B207AB74A9\"\n" + + " }"; + + assertCanSerializeAndDeserialize(params, json); + } +} \ No newline at end of file diff --git a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/client/nft/NftInfoResultTest.java b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/client/nft/NftInfoResultTest.java new file mode 100644 index 000000000..b2dc295f0 --- /dev/null +++ b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/client/nft/NftInfoResultTest.java @@ -0,0 +1,81 @@ +package org.xrpl.xrpl4j.model.client.nft; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.google.common.primitives.UnsignedInteger; +import com.google.common.primitives.UnsignedLong; +import org.json.JSONException; +import org.junit.jupiter.api.Test; +import org.xrpl.xrpl4j.model.AbstractJsonTest; +import org.xrpl.xrpl4j.model.client.common.LedgerIndex; +import org.xrpl.xrpl4j.model.flags.NfTokenFlags; +import org.xrpl.xrpl4j.model.transactions.Address; +import org.xrpl.xrpl4j.model.transactions.NfTokenId; +import org.xrpl.xrpl4j.model.transactions.NfTokenUri; +import org.xrpl.xrpl4j.model.transactions.TransferFee; + +class NftInfoResultTest extends AbstractJsonTest { + + @Test + void testJsonWithUri() throws JSONException, JsonProcessingException { + NftInfoResult result = NftInfoResult.builder() + .nftId(NfTokenId.of("00080000B4F4AFC5FBCBD76873F18006173D2193467D3EE70000099B00000000")) + .ledgerIndex(LedgerIndex.of(UnsignedInteger.valueOf(269))) + .owner(Address.of("rG9gdNygQ6npA9JvDFWBoeXbiUcTYJnEnk")) + .burned(false) + .flags(NfTokenFlags.TRANSFERABLE) + .transferFee(TransferFee.of(UnsignedInteger.ZERO)) + .issuer(Address.of("rHVokeuSnjPjz718qdb47bGXBBHNMP3KDQ")) + .nftTaxon(UnsignedLong.ZERO) + .nftSerial(UnsignedInteger.ZERO) + .uri(NfTokenUri.of("https://xrpl.org")) + .status("success") + .build(); + + String json = "{\n" + + " \"nft_id\": \"00080000B4F4AFC5FBCBD76873F18006173D2193467D3EE70000099B00000000\",\n" + + " \"ledger_index\": 269,\n" + + " \"owner\": \"rG9gdNygQ6npA9JvDFWBoeXbiUcTYJnEnk\",\n" + + " \"is_burned\": false,\n" + + " \"flags\": 8,\n" + + " \"transfer_fee\": 0,\n" + + " \"issuer\": \"rHVokeuSnjPjz718qdb47bGXBBHNMP3KDQ\",\n" + + " \"nft_taxon\": 0,\n" + + " \"nft_serial\": 0,\n" + + " \"uri\": \"https://xrpl.org\",\n" + + " \"status\": \"success\"\n" + + " }"; + + assertCanSerializeAndDeserialize(result, json); + } + + @Test + void testJsonWithoutUri() throws JSONException, JsonProcessingException { + NftInfoResult result = NftInfoResult.builder() + .nftId(NfTokenId.of("00080000B4F4AFC5FBCBD76873F18006173D2193467D3EE70000099B00000000")) + .ledgerIndex(LedgerIndex.of(UnsignedInteger.valueOf(269))) + .owner(Address.of("rG9gdNygQ6npA9JvDFWBoeXbiUcTYJnEnk")) + .burned(false) + .flags(NfTokenFlags.TRANSFERABLE) + .transferFee(TransferFee.of(UnsignedInteger.ZERO)) + .issuer(Address.of("rHVokeuSnjPjz718qdb47bGXBBHNMP3KDQ")) + .nftTaxon(UnsignedLong.ZERO) + .nftSerial(UnsignedInteger.ZERO) + .status("success") + .build(); + + String json = "{\n" + + " \"nft_id\": \"00080000B4F4AFC5FBCBD76873F18006173D2193467D3EE70000099B00000000\",\n" + + " \"ledger_index\": 269,\n" + + " \"owner\": \"rG9gdNygQ6npA9JvDFWBoeXbiUcTYJnEnk\",\n" + + " \"is_burned\": false,\n" + + " \"flags\": 8,\n" + + " \"transfer_fee\": 0,\n" + + " \"issuer\": \"rHVokeuSnjPjz718qdb47bGXBBHNMP3KDQ\",\n" + + " \"nft_taxon\": 0,\n" + + " \"nft_serial\": 0,\n" + + " \"status\": \"success\"\n" + + " }"; + + assertCanSerializeAndDeserialize(result, json); + } +} \ No newline at end of file diff --git a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/NfTokenMintTest.java b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/NfTokenMintTest.java index 16a0ea347..40adc1dfc 100644 --- a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/NfTokenMintTest.java +++ b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/NfTokenMintTest.java @@ -117,16 +117,14 @@ public void transferFeeUsingPercent() { .flags(NfTokenMintFlags.builder() .tfTransferable(true) .build()) - .transferFee(TransferFee.ofPercent(BigDecimal.valueOf(99.99))) + .transferFee(TransferFee.ofPercent(BigDecimal.valueOf(49.99))) .build(); - assertThat(nfTokenMint.transferFee().equals(Optional.of(9999))); + assertThat(nfTokenMint.transferFee().equals(Optional.of(49_990))); } @Test public void txWithUri() { - - UnsignedLong taxon = UnsignedLong.valueOf(146999694L); String uri = "ipfs://bafybeigdyrzt5sfp7udm7hu76uh7y26nf4dfuylqabf3oclgtqy55fbzdi"; NfTokenMint nfTokenMint = NfTokenMint.builder() diff --git a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/TransferFeeTest.java b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/TransferFeeTest.java index a0c775533..2cf53a3f4 100644 --- a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/TransferFeeTest.java +++ b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/TransferFeeTest.java @@ -45,39 +45,59 @@ public class TransferFeeTest { ObjectMapper objectMapper = ObjectMapperFactory.create(); @Test - public void transferFeeEquality() { + void ofPercent() { + assertThat(TransferFee.ofPercent(BigDecimal.valueOf(0)).value()).isEqualTo(UnsignedInteger.valueOf(0)); + assertThat(TransferFee.ofPercent(BigDecimal.valueOf(0.000)).value()).isEqualTo(UnsignedInteger.valueOf(0)); + assertThat(TransferFee.ofPercent(BigDecimal.valueOf(49.999)).value()).isEqualTo(UnsignedInteger.valueOf(49_999)); + assertThat(TransferFee.ofPercent(BigDecimal.valueOf(49.99)).value()).isEqualTo(UnsignedInteger.valueOf(49_990)); + assertThat(TransferFee.ofPercent(BigDecimal.valueOf(49.9)).value()).isEqualTo(UnsignedInteger.valueOf(49_900)); + assertThat(TransferFee.ofPercent(BigDecimal.valueOf(50)).value()).isEqualTo(UnsignedInteger.valueOf(50_000)); + assertThat(TransferFee.ofPercent(BigDecimal.valueOf(50.000)).value()).isEqualTo(UnsignedInteger.valueOf(50_000)); + } + + @Test + void ofPercentWithNull() { + assertThatThrownBy(() -> TransferFee.ofPercent(null)) + .isInstanceOf(NullPointerException.class); + } + @Test + public void transferFeeEquality() { assertThat(TransferFee.of(UnsignedInteger.ONE)).isEqualTo(TransferFee.of(UnsignedInteger.ONE)); assertThat(TransferFee.of(UnsignedInteger.valueOf(10))) .isEqualTo(TransferFee.of(UnsignedInteger.valueOf(10))); - assertThat(TransferFee.ofPercent(BigDecimal.valueOf(99.99))) - .isEqualTo(TransferFee.ofPercent(BigDecimal.valueOf(99.99))); + assertThat(TransferFee.ofPercent(BigDecimal.valueOf(49.99))) + .isEqualTo(TransferFee.ofPercent(BigDecimal.valueOf(49.99))); - assertThat(TransferFee.ofPercent(BigDecimal.valueOf(99.9))) - .isEqualTo(TransferFee.ofPercent(BigDecimal.valueOf(99.90))); + assertThat(TransferFee.ofPercent(BigDecimal.valueOf(49.9))) + .isEqualTo(TransferFee.ofPercent(BigDecimal.valueOf(49.90))); - assertThat(TransferFee.ofPercent(BigDecimal.valueOf(99.9)).value()) - .isEqualTo(UnsignedInteger.valueOf(9990)); + assertThat(TransferFee.ofPercent(BigDecimal.valueOf(49.9)).value()) + .isEqualTo(UnsignedInteger.valueOf(49900)); } @Test public void percentValueIncorrectFormat() { - assertThrows( - IllegalArgumentException.class, - () -> TransferFee.ofPercent(BigDecimal.valueOf(99.999)), - "Percent value should have a maximum of 2 decimal places." - ); + assertThatThrownBy( + () -> TransferFee.ofPercent(BigDecimal.valueOf(25.2929)) + ).isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("Percent value should have a maximum of 3 decimal places."); } @Test public void validateBounds() { assertDoesNotThrow(() -> TransferFee.of(UnsignedInteger.valueOf(49999))); + assertDoesNotThrow(() -> TransferFee.ofPercent(BigDecimal.valueOf(49.999))); assertDoesNotThrow(() -> TransferFee.of(UnsignedInteger.valueOf(50000))); + assertDoesNotThrow(() -> TransferFee.ofPercent(BigDecimal.valueOf(50.000))); assertThatThrownBy(() -> TransferFee.of(UnsignedInteger.valueOf(50001))) .isInstanceOf(IllegalArgumentException.class) .hasMessage("TransferFee should be in the range 0 to 50000."); + assertThatThrownBy(() -> TransferFee.ofPercent(BigDecimal.valueOf(50.001))) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("TransferFee should be in the range 0 to 50000."); } @Test diff --git a/xrpl4j-integration-tests/src/test/java/org/xrpl/xrpl4j/tests/NftInfoIT.java b/xrpl4j-integration-tests/src/test/java/org/xrpl/xrpl4j/tests/NftInfoIT.java new file mode 100644 index 000000000..259a7eae0 --- /dev/null +++ b/xrpl4j-integration-tests/src/test/java/org/xrpl/xrpl4j/tests/NftInfoIT.java @@ -0,0 +1,65 @@ +package org.xrpl.xrpl4j.tests; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; + +import com.google.common.primitives.UnsignedInteger; +import com.google.common.primitives.UnsignedLong; +import org.junit.jupiter.api.Test; +import org.xrpl.xrpl4j.client.JsonRpcClientErrorException; +import org.xrpl.xrpl4j.client.XrplClient; +import org.xrpl.xrpl4j.model.client.common.LedgerSpecifier; +import org.xrpl.xrpl4j.model.client.nft.NftInfoRequestParams; +import org.xrpl.xrpl4j.model.client.nft.NftInfoResult; +import org.xrpl.xrpl4j.model.flags.NfTokenFlags; +import org.xrpl.xrpl4j.model.transactions.Address; +import org.xrpl.xrpl4j.model.transactions.NfTokenId; +import org.xrpl.xrpl4j.model.transactions.NfTokenUri; +import org.xrpl.xrpl4j.model.transactions.TransferFee; +import org.xrpl.xrpl4j.tests.environment.ClioMainnetEnvironment; +import org.xrpl.xrpl4j.tests.environment.ReportingMainnetEnvironment; + +import java.math.BigDecimal; + +public class NftInfoIT { + + XrplClient xrplClient = new ClioMainnetEnvironment().getXrplClient(); + + @Test + void getNftInfo() throws JsonRpcClientErrorException { + NftInfoRequestParams params = NftInfoRequestParams.builder() + .nfTokenId(NfTokenId.of("0008138808C4E53F4F6EF5D5B2AF64F96B457F42E0ED9530FE9B131300001178")) + .ledgerSpecifier(LedgerSpecifier.VALIDATED) + .build(); + NftInfoResult nftInfo = xrplClient.nftInfo( + params + ); + + assertThat(nftInfo.nftId()).isEqualTo(params.nfTokenId()); + assertThat(nftInfo.owner()).isEqualTo(Address.of("rLpunkscgfzS8so59bUCJBVqZ3eHZue64r")); + assertThat(nftInfo.burned()).isFalse(); + assertThat(nftInfo.flags()).isEqualTo(NfTokenFlags.TRANSFERABLE); + assertThat(nftInfo.transferFee()).isEqualTo(TransferFee.ofPercent(BigDecimal.valueOf(5))); + assertThat(nftInfo.issuer()).isEqualTo(Address.of("ro4HnG6G1Adz2cWSnZ3Dcr39kmXk4ztA5")); + assertThat(nftInfo.nftTaxon()).isEqualTo(UnsignedLong.ZERO); + assertThat(nftInfo.nftSerial()).isEqualTo(UnsignedInteger.valueOf(4472)); + assertThat(nftInfo.uri()).isNotEmpty().get() + .isEqualTo(NfTokenUri.of("68747470733A2F2F62616679626569656E7662786B756F6C6B3778336333366177686A34346E6F6" + + "F687776613370683568376B746A78616D686D6F63333265733632712E697066732E6E667473746F726167652E6C696E6B2F7" + + "26567756C61725F626972645F6E6F5F323633372E6A7067")); + } + + @Test + void getNftInfoFromReportingModeThrows() throws JsonRpcClientErrorException { + XrplClient client = new ReportingMainnetEnvironment().getXrplClient(); + NftInfoRequestParams params = NftInfoRequestParams.builder() + .nfTokenId(NfTokenId.of("0008138808C4E53F4F6EF5D5B2AF64F96B457F42E0ED9530FE9B131300001178")) + .ledgerSpecifier(LedgerSpecifier.VALIDATED) + .build(); + assertThatThrownBy( + () -> client.nftInfo(params) + ).isInstanceOf(JsonRpcClientErrorException.class) + .hasMessage("Unknown method."); + + } +}