diff --git a/README.md b/README.md index 2235569c5..70df87089 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ current [BOM](https://howtodoinjava.com/maven/maven-bom-bill-of-materials-depend org.xrpl xrpl4j-bom - 3.5.1 + 4.0.2 pom import @@ -225,7 +225,7 @@ canonical JSON encoding). Read more about each here: Xrpl4j is structured as a Maven multi-module project, with the following modules: -- **xrpl4j-core**: [![javadoc](https://javadoc.io/badge2/org.xrpl/xrpl4j-binary-codec/javadoc.svg?color=blue)](https://javadoc.io/doc/org.xrpl/xrpl4j-binary-codec) +- **xrpl4j-core**: [![javadoc](https://javadoc.io/badge2/org.xrpl/xrpl4j-core/javadoc.svg?color=blue)](https://javadoc.io/doc/org.xrpl/xrpl4j-core) - Provides core primitives like seeds, public/private keys definitions (supports secp256k1 and ed25519 key types and signing algorithms), signature interfaces, address and binary codecs etc. Also provides Java objects which model XRP Ledger objects, as well as request parameters and response results for the `rippled` websocket and JSON RPC APIs. diff --git a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/ledger/AccountRootObject.java b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/ledger/AccountRootObject.java index 3639c6562..97b7d9c5e 100644 --- a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/ledger/AccountRootObject.java +++ b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/ledger/AccountRootObject.java @@ -239,6 +239,14 @@ default LedgerEntryType ledgerEntryType() { @JsonProperty("AMMID") Optional ammId(); + /** + * An arbitrary 256-bit value that users can set. + * + * @return An {@link Optional} {@link String}. + */ + @JsonProperty("WalletLocator") + Optional walletLocator(); + /** * The unique ID of this {@link AccountRootObject} ledger object. * 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 be1af6b7a..8814086c5 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 @@ -22,6 +22,8 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonValue; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; @@ -261,6 +263,7 @@ default AccountSet normalizeSetFlag() { * @return An {@link Optional} of type {@link String} containing the domain. */ @JsonProperty("Domain") + @JsonInclude(Include.NON_ABSENT) Optional domain(); /** @@ -278,6 +281,7 @@ default AccountSet normalizeSetFlag() { * @return An {@link Optional} of type {@link String} containing the messaging public key. */ @JsonProperty("MessageKey") + @JsonInclude(Include.NON_ABSENT) Optional messageKey(); /** @@ -307,6 +311,23 @@ default AccountSet normalizeSetFlag() { @JsonProperty("NFTokenMinter") Optional
mintAccount(); + /** + * An arbitrary 256-bit value. If specified, the value is stored as part of the account but has no inherent meaning + * or requirements. + * + * @return The 256-bit value as a hex encoded {@link String}. + */ + @JsonProperty("WalletLocator") + Optional walletLocator(); + + /** + * Not used. This field is valid in AccountSet transactions but does nothing. + * + * @return An optionally present {@link UnsignedInteger}. + */ + @JsonProperty("WalletSize") + Optional walletSize(); + /** * Check email hash length. */ 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 524737872..298e019a6 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 @@ -136,6 +136,42 @@ public void serializeAccountSetTransactionWithNetworkId() throws JsonProcessingE assertSerializesAndDeserializes(accountSet, expectedBinary); } + @Test + public void serializeAccountSetTransactionWithPresentOptionalStringFields() throws JsonProcessingException { + AccountSet accountSet = AccountSet.builder() + .account(Address.of("rpP2GdsQwenNnFPefbXFgiTvEgJWQpq8Rw")) + .fee(XrpCurrencyAmount.ofDrops(10)) + .sequence(UnsignedInteger.valueOf(10598)) + .networkId(NetworkId.of(UnsignedInteger.MAX_VALUE)) + .domain("ABCD") + .messageKey("03AB40A0490F9B7ED8DF29D246BF2D6269820A0EE7742ACDD457BEA7C7D0931EDB") + .emailHash("F9879D71855B5FF21E4963273A886BFC") + .walletLocator("F9879D71855B5FF21E4963273A886BFCF9879D71855B5FF21E4963273A886BFC") + .build(); + + String expectedBinary = "12000321FFFFFFFF240000296641F9879D71855B5FF21E4963273A886BFC57F98" + + "79D71855B5FF21E4963273A886BFCF9879D71855B5FF21E4963273A886BFC68400000000000000A722103AB" + + "40A0490F9B7ED8DF29D246BF2D6269820A0EE7742ACDD457BEA7C7D0931EDB73007702ABCD81140F3D0C7D2" + + "CFAB2EC8295451F0B3CA038E8E9CDCD"; + assertSerializesAndDeserializes(accountSet, expectedBinary); + } + + @Test + public void serializeAccountSetTransactionWithEmptyStrings() throws JsonProcessingException { + AccountSet accountSet = AccountSet.builder() + .account(Address.of("rpP2GdsQwenNnFPefbXFgiTvEgJWQpq8Rw")) + .fee(XrpCurrencyAmount.ofDrops(10)) + .sequence(UnsignedInteger.valueOf(10598)) + .networkId(NetworkId.of(UnsignedInteger.MAX_VALUE)) + .domain("") + .messageKey("") + .build(); + + String expectedBinary = "12000321FFFFFFFF240000296668400000000000000A72007300" + + "770081140F3D0C7D2CFAB2EC8295451F0B3CA038E8E9CDCD"; + assertSerializesAndDeserializes(accountSet, expectedBinary); + } + @Test public void serializeAccountSetTransactionWithEmptyFlags() throws JsonProcessingException { AccountSet accountSet = AccountSet.builder() diff --git a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/crypto/signing/SignedTransactionTest.java b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/crypto/signing/SignedTransactionTest.java index 490882afb..75ba502c2 100644 --- a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/crypto/signing/SignedTransactionTest.java +++ b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/crypto/signing/SignedTransactionTest.java @@ -33,22 +33,28 @@ import org.xrpl.xrpl4j.model.flags.TransactionFlags; import org.xrpl.xrpl4j.model.jackson.ObjectMapperFactory; import org.xrpl.xrpl4j.model.transactions.Address; +import org.xrpl.xrpl4j.model.transactions.Memo; +import org.xrpl.xrpl4j.model.transactions.MemoWrapper; import org.xrpl.xrpl4j.model.transactions.Payment; import org.xrpl.xrpl4j.model.transactions.XrpCurrencyAmount; +import java.util.Arrays; +import java.util.Collections; + /** * Unit tests for {@link SingleSignedTransaction}. */ class SignedTransactionTest { /** - * This test constructs the transaction found here: - * https://livenet.xrpl.org/transactions/A7AE53FE15B02E6E2F3C610FB4BA30B12392EB110F1D5E8C20880555E8639B05 to check - * that the hash that's on livenet matches what this library computes. The hash you see in this test is different than - * the hash found on livenet because the real transaction did not set any flags on the transaction and {@link Payment} - * requires a flags field (Even if you set flags to 0, it affects the hash). However, we made {@link Payment#flags()} - * nullable during development and verified that the hashes match, so we are confident that our hash calculation is - * accurate. + * This test constructs the transaction with hash A7AE53FE15B02E6E2F3C610FB4BA30B12392EB110F1D5E8C20880555E8639B05 to + * check that the hash that's on livenet matches what this library computes. The hash you see in this test is + * different from the hash found on livenet because the real transaction did not set any flags on the transaction and + * {@link Payment} requires a flags field (Even if you set flags to 0, it affects the hash). However, we made + * {@link Payment#flags()} nullable during development and verified that the hashes match, so we are confident that + * our hash calculation is accurate. + * + * @see "https://livenet.xrpl.org/transactions/A7AE53FE15B02E6E2F3C610FB4BA30B12392EB110F1D5E8C20880555E8639B05" */ @Test public void computesCorrectTransactionHash() throws JsonProcessingException { @@ -65,18 +71,66 @@ public void computesCorrectTransactionHash() throws JsonProcessingException { .destinationTag(UnsignedInteger.valueOf(371969)) .build(); + final Signature signature = Signature.fromBase16( + "304502210093257D8E88D2A92CE55977641F72CCD235AB76B1AE189BE3377F30A69B131C49" + + "02200B79836114069F0D331418D05818908D85DE755AE5C2DDF42E9637FE1C11754F" + ); + final Payment signedPayment = Payment.builder().from(unsignedTransaction) - .transactionSignature(Signature.fromBase16( - "304502210093257D8E88D2A92CE55977641F72CCD235AB76B1AE189BE3377F30A6" + - "9B131C4902200B79836114069F0D331418D05818908D85DE755AE5C2DDF42E9637FE1C11754F" + .transactionSignature(signature) + .build(); + + SingleSignedTransaction signedTransaction = SingleSignedTransaction.builder() + .signedTransaction(signedPayment) + .signature(signature) + .unsignedTransaction(unsignedTransaction) + .build(); + + String expectedHash = "F847C96B2EEB0609F16C9DB9D74A0CB123B5EAF5B626207977335BF0A1EF53C3"; + assertThat(signedTransaction.hash().value()).isEqualTo(expectedHash); + assertThat(signedTransaction.unsignedTransaction()).isEqualTo(unsignedTransaction); + assertThat(signedTransaction.signedTransaction()).isEqualTo(signedPayment); + assertThat(signedTransaction.signedTransactionBytes().hexValue()).isEqualTo( + XrplBinaryCodec.getInstance().encode(ObjectMapperFactory.create().writeValueAsString(signedPayment)) + ); + } + + /** + * This test constructs the transaction with hash 1A1953AC3BA3123254AA912CE507514A6AAD05EED8981A870B45F604936F0997 to + * check that the hash that's on livenet matches what this library computes. + * + * @see "https://livenet.xrpl.org/transactions/1A1953AC3BA3123254AA912CE507514A6AAD05EED8981A870B45F604936F0997" + */ + @Test + public void computesCorrectTransactionHashWithUnsetFlags() throws JsonProcessingException { + final Payment unsignedTransaction = Payment.builder() + .account(Address.of("rGWx7VAsnwVKRbPFPpvy8Lo4nFf5xjj6Zb")) + .amount(XrpCurrencyAmount.ofDrops(1)) + .destination(Address.of("rxRpSNb1VktvzBz8JF2oJC6qaww6RZ7Lw")) + .fee(XrpCurrencyAmount.ofDrops(12)) + .flags(PaymentFlags.of(TransactionFlags.UNSET.getValue())) // 0 + .lastLedgerSequence(UnsignedInteger.valueOf(86481544)) + .memos(Collections.singletonList( + MemoWrapper.builder() + .memo(Memo.builder() + .memoData("7B226F70223A226D696E74222C22616D6F756E74223A22313030303030303030222C22677061223A2230227D") + .build()) + .build() )) + .sequence(UnsignedInteger.valueOf(84987644)) + .signingPublicKey( + PublicKey.fromBase16EncodedPublicKey("ED05DC98B76FCD734BD44CDF153C34F79728485D2F24F9381CF7A284223EA258CE") + ) .build(); - final Signature signature = Signature.builder().value( - UnsignedByteArray.of(BaseEncoding.base16() - .decode("304502210093257D8E88D2A92CE55977641F72CCD235AB76B1AE189BE3377F30A69B131C49" + - "02200B79836114069F0D331418D05818908D85DE755AE5C2DDF42E9637FE1C11754F")) - ).build(); + final Signature signature = Signature.fromBase16( + "ED6F91CCF14EE94EB072C7671A397A313E3E5CBDAFE773BB6B2F07A0E75A7E65F84B5516268DAEE12902265256" + + "EA1EF046B200148E14FF4E720C06519FD7F40F" + ); + + final Payment signedPayment = Payment.builder().from(unsignedTransaction) + .transactionSignature(signature) + .build(); SingleSignedTransaction signedTransaction = SingleSignedTransaction.builder() .signedTransaction(signedPayment) @@ -84,7 +138,7 @@ public void computesCorrectTransactionHash() throws JsonProcessingException { .unsignedTransaction(unsignedTransaction) .build(); - String expectedHash = "F847C96B2EEB0609F16C9DB9D74A0CB123B5EAF5B626207977335BF0A1EF53C3"; + String expectedHash = "1A1953AC3BA3123254AA912CE507514A6AAD05EED8981A870B45F604936F0997"; assertThat(signedTransaction.hash().value()).isEqualTo(expectedHash); assertThat(signedTransaction.unsignedTransaction()).isEqualTo(unsignedTransaction); assertThat(signedTransaction.signedTransaction()).isEqualTo(signedPayment); @@ -92,4 +146,57 @@ public void computesCorrectTransactionHash() throws JsonProcessingException { XrplBinaryCodec.getInstance().encode(ObjectMapperFactory.create().writeValueAsString(signedPayment)) ); } + + /** + * This test constructs the transaction with hash 1A1953AC3BA3123254AA912CE507514A6AAD05EED8981A870B45F604936F0997 to + * check that the hash that's on livenet _does not_ match when the signature is supplied incorrectly (i.e., this test + * validates that a transaction's signature is always used to compute a transaction hash). + * + * @see "https://livenet.xrpl.org/transactions/1A1953AC3BA3123254AA912CE507514A6AAD05EED8981A870B45F604936F0997" + */ + @Test + public void computesIncorrectTransactionHashWithoutSignature() throws JsonProcessingException { + final Payment unsignedTransaction = Payment.builder() + .account(Address.of("rGWx7VAsnwVKRbPFPpvy8Lo4nFf5xjj6Zb")) + .amount(XrpCurrencyAmount.ofDrops(1)) + .destination(Address.of("rxRpSNb1VktvzBz8JF2oJC6qaww6RZ7Lw")) + .fee(XrpCurrencyAmount.ofDrops(12)) + .flags(PaymentFlags.of(TransactionFlags.UNSET.getValue())) // 0 + .lastLedgerSequence(UnsignedInteger.valueOf(86481544)) + .memos(Collections.singletonList( + MemoWrapper.builder() + .memo(Memo.builder() + .memoData("7B226F70223A226D696E74222C22616D6F756E74223A22313030303030303030222C22677061223A2230227D") + .build()) + .build() + )) + .sequence(UnsignedInteger.valueOf(84987644)) + .signingPublicKey( + PublicKey.fromBase16EncodedPublicKey("ED05DC98B76FCD734BD44CDF153C34F79728485D2F24F9381CF7A284223EA258CE") + ) + .build(); + + final Signature emptySignature = Signature.fromBase16(""); + + final Payment signedPayment = Payment.builder().from(unsignedTransaction) + .transactionSignature(emptySignature) + .build(); + + SingleSignedTransaction signedTransaction = SingleSignedTransaction.builder() + .signedTransaction(signedPayment) + .signature(emptySignature) + .unsignedTransaction(unsignedTransaction) + .build(); + + String expectedHash = "1A1953AC3BA3123254AA912CE507514A6AAD05EED8981A870B45F604936F0997"; + assertThat(signedTransaction.hash().value()).isNotEqualTo(expectedHash); + assertThat(signedTransaction.hash().value()).isEqualTo( + "8E0EDE65ECE8A03ABDD7926B994B2F6F14514FDBD46714F4F511143A1F01A6D0" + ); + assertThat(signedTransaction.unsignedTransaction()).isEqualTo(unsignedTransaction); + assertThat(signedTransaction.signedTransaction()).isEqualTo(signedPayment); + assertThat(signedTransaction.signedTransactionBytes().hexValue()).isEqualTo( + XrplBinaryCodec.getInstance().encode(ObjectMapperFactory.create().writeValueAsString(signedPayment)) + ); + } } 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 6f6447209..4f38e2afc 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 @@ -21,6 +21,7 @@ */ import com.fasterxml.jackson.core.JsonProcessingException; +import com.google.common.base.Strings; import com.google.common.primitives.UnsignedInteger; import org.json.JSONException; import org.junit.jupiter.api.Test; @@ -57,6 +58,8 @@ public void fullyPopulatedAccountSet() throws JSONException, JsonProcessingExcep .flags(AccountSetTransactionFlags.of(TransactionFlags.FULLY_CANONICAL_SIG.getValue())) .mintAccount(Address.of("rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn")) .networkId(NetworkId.of(1024)) + .walletLocator("ABCD") + .walletSize(UnsignedInteger.ONE) .build(); String json = "{\n" + @@ -74,12 +77,94 @@ public void fullyPopulatedAccountSet() throws JSONException, JsonProcessingExcep " \"SigningPubKey\" : \"02356E89059A75438887F9FEE2056A2890DB82A68353BE9C0C0C8F89C0018B37FC\",\n" + " \"NFTokenMinter\" : \"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn\",\n" + " \"NetworkID\": 1024,\n" + + " \"WalletSize\": 1,\n" + + " \"WalletLocator\": \"ABCD\",\n" + " \"EmailHash\":\"f9879d71855b5ff21e4963273a886bfc\"\n" + "}"; assertCanSerializeAndDeserialize(accountSet, json); } + @Test + public void accountSetWithEmptyOptionalStringFields() throws JSONException, JsonProcessingException { + AccountSet accountSet = AccountSet.builder() + .account(Address.of("rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn")) + .fee(XrpCurrencyAmount.ofDrops(12)) + .sequence(UnsignedInteger.valueOf(5)) + .setFlag(AccountSetFlag.ACCOUNT_TXN_ID) + .transferRate(UnsignedInteger.valueOf(1000000001)) + .tickSize(UnsignedInteger.valueOf(15)) + .clearFlag(AccountSetFlag.DEFAULT_RIPPLE) + .signingPublicKey( + PublicKey.fromBase16EncodedPublicKey("02356E89059A75438887F9FEE2056A2890DB82A68353BE9C0C0C8F89C0018B37FC") + ) + .flags(AccountSetTransactionFlags.of(TransactionFlags.FULLY_CANONICAL_SIG.getValue())) + .mintAccount(Address.of("rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn")) + .networkId(NetworkId.of(1024)) + .build(); + + String json = "{\n" + + " \"TransactionType\":\"AccountSet\",\n" + + " \"Account\":\"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn\",\n" + + " \"Fee\":\"12\",\n" + + " \"Sequence\":5,\n" + + " \"Flags\":2147483648,\n" + + " \"SetFlag\":5,\n" + + " \"TransferRate\":1000000001,\n" + + " \"TickSize\":15,\n" + + " \"ClearFlag\":8,\n" + + " \"SigningPubKey\" : \"02356E89059A75438887F9FEE2056A2890DB82A68353BE9C0C0C8F89C0018B37FC\",\n" + + " \"NFTokenMinter\" : \"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn\",\n" + + " \"NetworkID\": 1024\n" + + "}"; + + assertCanSerializeAndDeserialize(accountSet, json); + } + + @Test + public void accountSetWithEmptyStringFields() throws JSONException, JsonProcessingException { + AccountSet accountSet = AccountSet.builder() + .account(Address.of("rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn")) + .fee(XrpCurrencyAmount.ofDrops(12)) + .domain("") + .messageKey("") + .emailHash(Strings.repeat("0", 32)) + .walletLocator(Strings.repeat("0", 64)) + .sequence(UnsignedInteger.valueOf(5)) + .setFlag(AccountSetFlag.ACCOUNT_TXN_ID) + .transferRate(UnsignedInteger.valueOf(1000000001)) + .tickSize(UnsignedInteger.valueOf(15)) + .clearFlag(AccountSetFlag.DEFAULT_RIPPLE) + .signingPublicKey( + PublicKey.fromBase16EncodedPublicKey("02356E89059A75438887F9FEE2056A2890DB82A68353BE9C0C0C8F89C0018B37FC") + ) + .flags(AccountSetTransactionFlags.of(TransactionFlags.FULLY_CANONICAL_SIG.getValue())) + .mintAccount(Address.of("rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn")) + .networkId(NetworkId.of(1024)) + .build(); + + String json = "{\n" + + " \"TransactionType\":\"AccountSet\",\n" + + " \"Account\":\"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn\",\n" + + " \"Fee\":\"12\",\n" + + " \"Sequence\":5,\n" + + " \"Flags\":2147483648,\n" + + " \"Domain\":\"\",\n" + + " \"SetFlag\":5,\n" + + " \"MessageKey\":\"\",\n" + + " \"TransferRate\":1000000001,\n" + + " \"TickSize\":15,\n" + + " \"ClearFlag\":8,\n" + + " \"SigningPubKey\" : \"02356E89059A75438887F9FEE2056A2890DB82A68353BE9C0C0C8F89C0018B37FC\",\n" + + " \"NFTokenMinter\" : \"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn\",\n" + + " \"WalletLocator\" : \"" + Strings.repeat("0", 64) + "\",\n" + + " \"EmailHash\" : \"" + Strings.repeat("0", 32) + "\",\n" + + " \"NetworkID\": 1024\n" + + "}"; + + assertCanSerializeAndDeserialize(accountSet, json); + } + @Test public void testJsonWithUnsetFlags() throws JsonProcessingException, JSONException { AccountSet accountSet = AccountSet.builder() @@ -92,7 +177,7 @@ public void testJsonWithUnsetFlags() throws JsonProcessingException, JSONExcepti .transferRate(UnsignedInteger.valueOf(1000000001)) .tickSize(UnsignedInteger.valueOf(15)) .clearFlag(AccountSetFlag.DEFAULT_RIPPLE) - .emailHash("f9879d71855b5ff21e4963273a886bfc") + .emailHash(Strings.repeat("0", 32)) .signingPublicKey( PublicKey.fromBase16EncodedPublicKey("02356E89059A75438887F9FEE2056A2890DB82A68353BE9C0C0C8F89C0018B37FC") ) @@ -114,7 +199,7 @@ public void testJsonWithUnsetFlags() throws JsonProcessingException, JSONExcepti " \"ClearFlag\":8,\n" + " \"SigningPubKey\" : \"02356E89059A75438887F9FEE2056A2890DB82A68353BE9C0C0C8F89C0018B37FC\",\n" + " \"NFTokenMinter\" : \"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn\",\n" + - " \"EmailHash\":\"f9879d71855b5ff21e4963273a886bfc\"\n" + + " \"EmailHash\":\"" + Strings.repeat("0", 32) + "\"\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 18e2e9378..4ff0d5afb 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 @@ -23,6 +23,7 @@ import static org.assertj.core.api.Assertions.assertThat; import com.fasterxml.jackson.core.JsonProcessingException; +import com.google.common.base.Strings; import com.google.common.primitives.UnsignedInteger; import org.junit.jupiter.api.Test; import org.xrpl.xrpl4j.client.JsonRpcClientErrorException; @@ -392,6 +393,84 @@ void submitAndRetrieveAccountSetWithZeroClearFlagAndSetFlag() assertThat(accountSetTransactionResult.transaction().clearFlag()).isNotEmpty().get().isEqualTo(AccountSetFlag.NONE); } + @Test + void setAndUnsetDomainAndMessageKey() 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 setDomain = AccountSet.builder() + .account(keyPair.publicKey().deriveAddress()) + .fee(FeeUtils.computeNetworkFees(feeResult).recommendedFee()) + .sequence(accountInfo.accountData().sequence()) + .signingPublicKey(keyPair.publicKey()) + .domain("ABCD") + .messageKey("03AB40A0490F9B7ED8DF29D246BF2D6269820A0EE7742ACDD457BEA7C7D0931EDB") + .emailHash("F9879D71855B5FF21E4963273A886BFC") + .walletLocator("F9879D71855B5FF21E4963273A886BFCF9879D71855B5FF21E4963273A886BFC") + .build(); + + SingleSignedTransaction signedSetDomain = signatureService.sign( + keyPair.privateKey(), setDomain + ); + SubmitResult response = xrplClient.submit(signedSetDomain); + + assertThat(response.engineResult()).isEqualTo("tesSUCCESS"); + assertThat(signedSetDomain.hash()).isEqualTo(response.transactionResult().hash()); + logger.info( + "AccountSet transaction successful: https://testnet.xrpl.org/transactions/" + response.transactionResult().hash() + ); + + this.scanForResult(() -> + this.getValidatedTransaction(signedSetDomain.hash(), AccountSet.class) + ); + accountInfo = this.scanForResult( + () -> this.getValidatedAccountInfo(keyPair.publicKey().deriveAddress()) + ); + assertThat(accountInfo.accountData().domain()).isNotEmpty().isEqualTo(setDomain.domain()); + assertThat(accountInfo.accountData().messageKey()).isNotEmpty().isEqualTo(setDomain.messageKey()); + assertThat(accountInfo.accountData().emailHash()).isNotEmpty().isEqualTo(setDomain.emailHash()); + assertThat(accountInfo.accountData().walletLocator()).isNotEmpty().isEqualTo(setDomain.walletLocator()); + + AccountSet clearDomain = AccountSet.builder() + .account(keyPair.publicKey().deriveAddress()) + .fee(FeeUtils.computeNetworkFees(feeResult).recommendedFee()) + .sequence(accountInfo.accountData().sequence()) + .signingPublicKey(keyPair.publicKey()) + .domain("") + .messageKey("") + .emailHash(Strings.repeat("0", 32)) + .walletLocator(Strings.repeat("0", 64)) + .build(); + + SingleSignedTransaction signedClearDomain = signatureService.sign( + keyPair.privateKey(), clearDomain + ); + SubmitResult clearDomainSubmitResult = xrplClient.submit(signedClearDomain); + + assertThat(clearDomainSubmitResult.engineResult()).isEqualTo("tesSUCCESS"); + assertThat(signedClearDomain.hash()).isEqualTo(clearDomainSubmitResult.transactionResult().hash()); + logger.info( + "AccountSet transaction successful: https://testnet.xrpl.org/transactions/" + + clearDomainSubmitResult.transactionResult().hash() + ); + + this.scanForResult(() -> + this.getValidatedTransaction(signedClearDomain.hash(), AccountSet.class) + ); + accountInfo = this.scanForResult( + () -> this.getValidatedAccountInfo(keyPair.publicKey().deriveAddress()) + ); + assertThat(accountInfo.accountData().domain()).isEmpty(); + assertThat(accountInfo.accountData().messageKey()).isEmpty(); + assertThat(accountInfo.accountData().emailHash()).isEmpty(); + assertThat(accountInfo.accountData().walletLocator()).isEmpty(); + } ////////////////////// // Test Helpers