diff --git a/README.md b/README.md
index 2235569c5..80ff03d9d 100644
--- a/README.md
+++ b/README.md
@@ -15,7 +15,7 @@ and address generation, transaction serialization and signing, provides useful J
- Example usage can be found in the `xrpl4j-integration-tests`
module [here](https://github.com/XRPLF/xrpl4j/tree/main/xrpl4j-integration-tests/src/test/java/org/xrpl/xrpl4j/tests).
-## Usage
+## Usage
### Requirements
@@ -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/AbstractIT.java b/xrpl4j-integration-tests/src/test/java/org/xrpl/xrpl4j/tests/AbstractIT.java
index dcc1fdb97..84d3ce135 100644
--- a/xrpl4j-integration-tests/src/test/java/org/xrpl/xrpl4j/tests/AbstractIT.java
+++ b/xrpl4j-integration-tests/src/test/java/org/xrpl/xrpl4j/tests/AbstractIT.java
@@ -703,6 +703,26 @@ private KeyStore loadKeyStore() {
return JavaKeystoreLoader.loadFromClasspath(jksFileName, jksPassword);
}
+ /**
+ * Returns the minimum time that can be used for escrow expirations. The ledger will not accept an expiration time
+ * that is earlier than the last ledger close time, so we must use the latter of current time or ledger close time
+ * (which for unexplained reasons can sometimes be later than now).
+ *
+ * @return An {@link Instant}.
+ */
+ protected Instant getMinExpirationTime() {
+ LedgerResult result = getValidatedLedger();
+ Instant closeTime = xrpTimestampToInstant(
+ result.ledger().closeTime()
+ .orElseThrow(() ->
+ new RuntimeException("Ledger close time must be present to calculate a minimum expiration time.")
+ )
+ );
+
+ Instant now = Instant.now();
+ return closeTime.isBefore(now) ? now : closeTime;
+ }
+
private void logAccountCreation(Address address) {
logger.info("Generated wallet with ClassicAddress={})", address);
}
diff --git a/xrpl4j-integration-tests/src/test/java/org/xrpl/xrpl4j/tests/AccountDeleteIT.java b/xrpl4j-integration-tests/src/test/java/org/xrpl/xrpl4j/tests/AccountDeleteIT.java
new file mode 100644
index 000000000..497f149db
--- /dev/null
+++ b/xrpl4j-integration-tests/src/test/java/org/xrpl/xrpl4j/tests/AccountDeleteIT.java
@@ -0,0 +1,380 @@
+package org.xrpl.xrpl4j.tests;
+
+/*-
+ * ========================LICENSE_START=================================
+ * xrpl4j :: integration-tests
+ * %%
+ * 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 static org.assertj.core.api.Assertions.assertThat;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.google.common.primitives.UnsignedInteger;
+import com.google.common.primitives.UnsignedLong;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.condition.DisabledIf;
+import org.xrpl.xrpl4j.client.JsonRpcClientErrorException;
+import org.xrpl.xrpl4j.crypto.keys.KeyPair;
+import org.xrpl.xrpl4j.crypto.keys.Seed;
+import org.xrpl.xrpl4j.crypto.signing.SingleSignedTransaction;
+import org.xrpl.xrpl4j.model.client.accounts.AccountInfoResult;
+import org.xrpl.xrpl4j.model.client.common.LedgerSpecifier;
+import org.xrpl.xrpl4j.model.client.fees.FeeResult;
+import org.xrpl.xrpl4j.model.client.ledger.LedgerRequestParams;
+import org.xrpl.xrpl4j.model.client.ledger.LedgerResult;
+import org.xrpl.xrpl4j.model.client.transactions.SubmitResult;
+import org.xrpl.xrpl4j.model.client.transactions.TransactionResult;
+import org.xrpl.xrpl4j.model.transactions.AccountDelete;
+import org.xrpl.xrpl4j.model.transactions.AccountSet;
+import org.xrpl.xrpl4j.model.transactions.EscrowCreate;
+import org.xrpl.xrpl4j.model.transactions.XrpCurrencyAmount;
+import org.xrpl.xrpl4j.tests.environment.LocalRippledEnvironment;
+import org.xrpl.xrpl4j.tests.environment.XrplEnvironment;
+
+import java.time.Duration;
+
+/**
+ * An integration test that submits AccountDelete transactions that handle a successful usage along with
+ * examples of all failure cases.
+ *
+ * @see "https://xrpl.org/accountset.html"
+ */
+@DisabledIf(value = "shouldRun", disabledReason = "AccountDeleteIT only runs with local rippled nodes.")
+class AccountDeleteIT extends AbstractIT {
+ static boolean shouldRun() {
+ return System.getProperty("useTestnet") != null ||
+ System.getProperty("useDevnet") != null ||
+ System.getProperty("useClioTestnet") != null;
+ }
+
+ @Test
+ void testAccountDeleteItFailsWith_TooSoon() throws JsonRpcClientErrorException, JsonProcessingException {
+ // create two accounts, one will be the destination in the tx
+ KeyPair senderAccount = constructRandomAccount();
+ KeyPair receiverAccount = constructRandomAccount();
+
+ // get account info for the sequence number
+ AccountInfoResult accountInfo = this.scanForResult(
+ () -> this.getValidatedAccountInfo(senderAccount.publicKey().deriveAddress())
+ );
+
+ // create, sign & submit AccountDelete tx
+ AccountDelete accountDelete = AccountDelete.builder()
+ .account(senderAccount.publicKey().deriveAddress())
+ .fee(XrpCurrencyAmount.builder().value(UnsignedLong.valueOf(2000000)).build())
+ .sequence(accountInfo.accountData().sequence())
+ .destination(receiverAccount.publicKey().deriveAddress())
+ .signingPublicKey(senderAccount.publicKey())
+ .build();
+
+ SingleSignedTransaction signedAccountDelete = signatureService.sign(
+ senderAccount.privateKey(), accountDelete
+ );
+ SubmitResult response = xrplClient.submit(signedAccountDelete);
+
+ // get tecTOO_SOON because need to wait for ledger index to be greater than sequenceNumber + 256
+ assertThat(response.engineResult()).isEqualTo("tecTOO_SOON");
+ assertThat(signedAccountDelete.hash()).isEqualTo(response.transactionResult().hash());
+ }
+
+ @Test
+ void testAccountDeleteItFailsWith_DestinationIsSource() throws JsonRpcClientErrorException, JsonProcessingException {
+ // create one account, will be the sender & destination in the tx
+ KeyPair senderAccount = constructRandomAccount();
+
+ // get account info for the sequence number
+ AccountInfoResult accountInfo = this.scanForResult(
+ () -> this.getValidatedAccountInfo(senderAccount.publicKey().deriveAddress())
+ );
+
+ // create, sign & submit AccountDelete tx
+ AccountDelete accountDelete = AccountDelete.builder()
+ .account(senderAccount.publicKey().deriveAddress())
+ .fee(XrpCurrencyAmount.builder().value(UnsignedLong.valueOf(2000000)).build())
+ .sequence(accountInfo.accountData().sequence())
+ .destination(senderAccount.publicKey().deriveAddress())
+ .signingPublicKey(senderAccount.publicKey())
+ .build();
+
+ SingleSignedTransaction signedAccountDelete = signatureService.sign(
+ senderAccount.privateKey(), accountDelete
+ );
+ SubmitResult response = xrplClient.submit(signedAccountDelete);
+
+ // get temDST_IS_SRC because sender is the same as the destination
+ assertThat(response.engineResult()).isEqualTo("temDST_IS_SRC");
+ assertThat(signedAccountDelete.hash()).isEqualTo(response.transactionResult().hash());
+ }
+
+ @Test
+ void testAccountDeleteItFailsWith_DestinationTagNeeded() throws JsonRpcClientErrorException, JsonProcessingException {
+ // create two accounts, one will be the destination in the tx
+ KeyPair senderAccount = constructRandomAccount();
+ KeyPair receiverAccount = constructRandomAccount();
+
+ // get account info for the sequence number
+ AccountInfoResult receiverAccountInfo = this.scanForResult(
+ () -> this.getValidatedAccountInfo(receiverAccount.publicKey().deriveAddress())
+ );
+
+ // create, sign & submit REQUIRE_DEST AccountSet tx for receiver
+ FeeResult feeResult = xrplClient.fee();
+ AccountSet accountSet = AccountSet.builder()
+ .account(receiverAccount.publicKey().deriveAddress())
+ .fee(feeResult.drops().openLedgerFee())
+ .sequence(receiverAccountInfo.accountData().sequence())
+ .setFlag(AccountSet.AccountSetFlag.REQUIRE_DEST)
+ .signingPublicKey(receiverAccount.publicKey())
+ .build();
+
+ SingleSignedTransaction signedAccountSet = signatureService.sign(
+ receiverAccount.privateKey(), accountSet
+ );
+ SubmitResult accountSetSubmitResult = xrplClient.submit(signedAccountSet);
+
+ assertThat(accountSetSubmitResult.engineResult()).isEqualTo("tesSUCCESS");
+ assertThat(signedAccountSet.hash()).isEqualTo(accountSetSubmitResult.transactionResult().hash());
+
+ // confirm flag was set
+ TransactionResult accountSetTransactionResult = this.scanForResult(() ->
+ this.getValidatedTransaction(signedAccountSet.hash(), AccountSet.class)
+ );
+
+ AccountInfoResult updatedReceiverAccountInfo = this.scanForResult(
+ () -> this.getValidatedAccountInfo(receiverAccount.publicKey().deriveAddress())
+ );
+
+ assertThat(accountSetTransactionResult.transaction().setFlag().orElse(null))
+ .isEqualTo(AccountSet.AccountSetFlag.REQUIRE_DEST);
+ assertThat(updatedReceiverAccountInfo.accountData().flags().lsfRequireDestTag()).isTrue();
+
+ // create, sign & submit AccountDelete tx
+ AccountDelete accountDelete = AccountDelete.builder()
+ .account(senderAccount.publicKey().deriveAddress())
+ .fee(XrpCurrencyAmount.builder().value(UnsignedLong.valueOf(2000000)).build())
+ .sequence(receiverAccountInfo.accountData().sequence())
+ .destination(receiverAccount.publicKey().deriveAddress())
+ .signingPublicKey(senderAccount.publicKey())
+ .build();
+
+ SingleSignedTransaction signedAccountDelete = signatureService.sign(
+ senderAccount.privateKey(), accountDelete
+ );
+ SubmitResult response = xrplClient.submit(signedAccountDelete);
+
+ // get tecDST_TAG_NEEDED because the receiver requires the destination tag to be set
+ assertThat(response.engineResult()).isEqualTo("tecDST_TAG_NEEDED");
+ assertThat(signedAccountDelete.hash()).isEqualTo(response.transactionResult().hash());
+ }
+
+ @Test
+ void testAccountDeleteItFailsWith_NoPermission() throws JsonRpcClientErrorException, JsonProcessingException {
+ // create two accounts, one will be the destination in the tx
+ KeyPair senderAccount = constructRandomAccount();
+ KeyPair receiverAccount = constructRandomAccount();
+
+ // get account info for the sequence number
+ AccountInfoResult receiverAccountInfo = this.scanForResult(
+ () -> this.getValidatedAccountInfo(receiverAccount.publicKey().deriveAddress())
+ );
+
+ // create, sign & submit DEPOSIT_AUTH AccountSet tx for receiver
+ FeeResult feeResult = xrplClient.fee();
+ AccountSet accountSet = AccountSet.builder()
+ .account(receiverAccount.publicKey().deriveAddress())
+ .fee(feeResult.drops().openLedgerFee())
+ .sequence(receiverAccountInfo.accountData().sequence())
+ .setFlag(AccountSet.AccountSetFlag.DEPOSIT_AUTH)
+ .signingPublicKey(receiverAccount.publicKey())
+ .build();
+
+ SingleSignedTransaction signedAccountSet = signatureService.sign(
+ receiverAccount.privateKey(), accountSet
+ );
+ SubmitResult accountSetSubmitResult = xrplClient.submit(signedAccountSet);
+
+ assertThat(accountSetSubmitResult.engineResult()).isEqualTo("tesSUCCESS");
+ assertThat(signedAccountSet.hash()).isEqualTo(accountSetSubmitResult.transactionResult().hash());
+
+ // confirm flag was set
+ TransactionResult accountSetTransactionResult = this.scanForResult(
+ () -> this.getValidatedTransaction(signedAccountSet.hash(), AccountSet.class)
+ );
+
+ AccountInfoResult updatedReceiverAccountInfo = this.scanForResult(
+ () -> this.getValidatedAccountInfo(receiverAccount.publicKey().deriveAddress())
+ );
+
+ assertThat(accountSetTransactionResult.transaction().setFlag().orElse(null))
+ .isEqualTo(AccountSet.AccountSetFlag.DEPOSIT_AUTH);
+ assertThat(updatedReceiverAccountInfo.accountData().flags().lsfDepositAuth()).isTrue();
+
+ // create, sign & submit AccountDelete tx
+ AccountDelete accountDelete = AccountDelete.builder()
+ .account(senderAccount.publicKey().deriveAddress())
+ .fee(XrpCurrencyAmount.builder().value(UnsignedLong.valueOf(2000000)).build())
+ .sequence(receiverAccountInfo.accountData().sequence())
+ .destination(receiverAccount.publicKey().deriveAddress())
+ .signingPublicKey(senderAccount.publicKey())
+ .build();
+
+ SingleSignedTransaction signedAccountDelete = signatureService.sign(
+ senderAccount.privateKey(), accountDelete
+ );
+ SubmitResult response = xrplClient.submit(signedAccountDelete);
+
+ // get tecNO_PERMISSION because deposit auth is enabled by receiver and sender is not authorized
+ assertThat(response.engineResult()).isEqualTo("tecNO_PERMISSION");
+ assertThat(signedAccountDelete.hash()).isEqualTo(response.transactionResult().hash());
+ }
+
+ @Test
+ void testAccountDeleteItFailsWith_NoDestination() throws JsonRpcClientErrorException, JsonProcessingException {
+ // create one account and a random key pair that will be used for the destination
+ KeyPair senderAccount = constructRandomAccount();
+ KeyPair randomKeyPair = Seed.ed25519Seed().deriveKeyPair();
+
+ // get account info for the sequence number
+ AccountInfoResult accountInfo = this.scanForResult(
+ () -> this.getValidatedAccountInfo(senderAccount.publicKey().deriveAddress())
+ );
+
+ // create, sign & submit AccountDelete tx
+ AccountDelete accountDelete = AccountDelete.builder()
+ .account(senderAccount.publicKey().deriveAddress())
+ .fee(XrpCurrencyAmount.builder().value(UnsignedLong.valueOf(2000000)).build())
+ .sequence(accountInfo.accountData().sequence())
+ .destination(randomKeyPair.publicKey().deriveAddress())
+ .signingPublicKey(senderAccount.publicKey())
+ .build();
+
+ SingleSignedTransaction signedAccountDelete = signatureService.sign(
+ senderAccount.privateKey(), accountDelete
+ );
+ SubmitResult response = xrplClient.submit(signedAccountDelete);
+
+ // get tecNO_DST because destination was not a funded account on the ledger
+ assertThat(response.engineResult()).isEqualTo("tecNO_DST");
+ assertThat(signedAccountDelete.hash()).isEqualTo(response.transactionResult().hash());
+ }
+
+ @Test
+ void testAccountDeleteItFailsWith_HasObligations() throws JsonRpcClientErrorException, JsonProcessingException {
+ // create sender account
+ KeyPair senderAccount = constructRandomAccount();
+
+ // get account info for the sequence number
+ AccountInfoResult accountInfo = this.scanForResult(
+ () -> this.getValidatedAccountInfo(senderAccount.publicKey().deriveAddress())
+ );
+
+ // create EscrowCreate tx to link an account with an object for tecHAS_OBLIGATIONS error
+ EscrowCreate escrowCreate = EscrowCreate.builder()
+ .account(senderAccount.publicKey().deriveAddress())
+ .fee(XrpCurrencyAmount.builder().value(UnsignedLong.valueOf(200)).build())
+ .amount(XrpCurrencyAmount.of(UnsignedLong.valueOf(10)))
+ .sequence(accountInfo.accountData().sequence())
+ .destination(senderAccount.publicKey().deriveAddress())
+ .finishAfter(instantToXrpTimestamp(getMinExpirationTime().plus(Duration.ofSeconds(10))))
+ .signingPublicKey(senderAccount.publicKey())
+ .build();
+
+ // sign and submit EscrowCreate tx
+ SingleSignedTransaction signedEscrowCreate = signatureService.sign(
+ senderAccount.privateKey(), escrowCreate
+ );
+ SubmitResult escrowCreateResult = xrplClient.submit(signedEscrowCreate);
+
+ assertThat(escrowCreateResult.engineResult()).isEqualTo("tesSUCCESS");
+ assertThat(signedEscrowCreate.hash()).isEqualTo(escrowCreateResult.transactionResult().hash());
+
+ // accept next 256 ledgers to avoid tec_TOOSOON error case and get current ledger index
+ for (int i = 0; i < 256; i++) {
+ LocalRippledEnvironment localRippledEnvironment =
+ (LocalRippledEnvironment) XrplEnvironment.getConfiguredEnvironment();
+ localRippledEnvironment.acceptLedger();
+ }
+
+ LedgerResult lastLedgerResult = xrplClient.ledger(LedgerRequestParams.builder()
+ .ledgerSpecifier(LedgerSpecifier.CURRENT).build());
+
+ // create receiver account, then create sign & submit AccountDelete tx
+ KeyPair receiverAccount = constructRandomAccount();
+
+ AccountDelete accountDelete = AccountDelete.builder()
+ .account(senderAccount.publicKey().deriveAddress())
+ .fee(XrpCurrencyAmount.builder().value(UnsignedLong.valueOf(2000000)).build())
+ .sequence(signedEscrowCreate.signedTransaction().sequence().plus(UnsignedInteger.ONE))
+ .destination(receiverAccount.publicKey().deriveAddress())
+ .lastLedgerSequence(lastLedgerResult.ledgerCurrentIndexSafe().unsignedIntegerValue())
+ .signingPublicKey(senderAccount.publicKey())
+ .build();
+
+ SingleSignedTransaction signedAccountDelete = signatureService.sign(
+ senderAccount.privateKey(), accountDelete
+ );
+
+ SubmitResult response = xrplClient.submit(signedAccountDelete);
+
+ // get tecHAS_OBLIGATIONS because there are objects depending on the account that is trying to be deleted
+ assertThat(response.engineResult()).isEqualTo("tecHAS_OBLIGATIONS");
+ assertThat(signedAccountDelete.hash()).isEqualTo(response.transactionResult().hash());
+ }
+
+ @Test
+ void testAccountDeleteIt() throws JsonRpcClientErrorException, JsonProcessingException {
+ // create two accounts, one will be the destination in the tx
+ KeyPair senderAccount = constructRandomAccount();
+ KeyPair receiverAccount = constructRandomAccount();
+
+ // get account info for the sequence number
+ AccountInfoResult accountInfo = this.scanForResult(
+ () -> this.getValidatedAccountInfo(senderAccount.publicKey().deriveAddress())
+ );
+
+ // accept next 256 ledgers to avoid tec_TOOSOON error case and get current ledger index
+ for (int i = 0; i < 256; i++) {
+ LocalRippledEnvironment localRippledEnvironment =
+ (LocalRippledEnvironment) XrplEnvironment.getConfiguredEnvironment();
+ localRippledEnvironment.acceptLedger();
+ }
+
+ LedgerResult lastLedgerResult = xrplClient.ledger(LedgerRequestParams.builder()
+ .ledgerSpecifier(LedgerSpecifier.CURRENT).build());
+
+ // create, sign & submit AccountDelete tx
+ AccountDelete accountDelete = AccountDelete.builder()
+ .account(senderAccount.publicKey().deriveAddress())
+ .fee(XrpCurrencyAmount.builder().value(UnsignedLong.valueOf(2000000)).build())
+ .sequence(accountInfo.accountData().sequence())
+ .destination(receiverAccount.publicKey().deriveAddress())
+ .lastLedgerSequence(lastLedgerResult.ledgerCurrentIndexSafe().unsignedIntegerValue())
+ .signingPublicKey(senderAccount.publicKey())
+ .build();
+
+ SingleSignedTransaction signedAccountDelete = signatureService.sign(
+ senderAccount.privateKey(), accountDelete
+ );
+
+ // after 256 other txs are submitted, then submit AccountDelete
+ SubmitResult response = xrplClient.submit(signedAccountDelete);
+
+ // get tesSUCCESS because we wait for sequence # + 256 is less than ledger index
+ assertThat(response.engineResult()).isEqualTo("tesSUCCESS");
+ assertThat(signedAccountDelete.hash()).isEqualTo(response.transactionResult().hash());
+ }
+}
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
diff --git a/xrpl4j-integration-tests/src/test/java/org/xrpl/xrpl4j/tests/EscrowIT.java b/xrpl4j-integration-tests/src/test/java/org/xrpl/xrpl4j/tests/EscrowIT.java
index c112b6009..9958bdff7 100644
--- a/xrpl4j-integration-tests/src/test/java/org/xrpl/xrpl4j/tests/EscrowIT.java
+++ b/xrpl4j-integration-tests/src/test/java/org/xrpl/xrpl4j/tests/EscrowIT.java
@@ -484,26 +484,6 @@ public void createAndCancelCryptoConditionBasedEscrow() throws JsonRpcClientErro
}
- /**
- * Returns the minimum time that can be used for escrow expirations. The ledger will not accept an expiration time
- * that is earlier than the last ledger close time, so we must use the latter of current time or ledger close time
- * (which for unexplained reasons can sometimes be later than now).
- *
- * @return An {@link Instant}.
- */
- private Instant getMinExpirationTime() {
- LedgerResult result = getValidatedLedger();
- Instant closeTime = xrpTimestampToInstant(
- result.ledger().closeTime()
- .orElseThrow(() ->
- new RuntimeException("Ledger close time must be present to calculate a minimum expiration time.")
- )
- );
-
- Instant now = Instant.now();
- return closeTime.isBefore(now) ? now : closeTime;
- }
-
private void assertEntryEqualsObjectFromAccountObjects(
Address escrowOwner,
UnsignedInteger createSequence
diff --git a/xrpl4j-integration-tests/src/test/java/org/xrpl/xrpl4j/tests/environment/LocalRippledEnvironment.java b/xrpl4j-integration-tests/src/test/java/org/xrpl/xrpl4j/tests/environment/LocalRippledEnvironment.java
index 13f63add7..71c0aa553 100644
--- a/xrpl4j-integration-tests/src/test/java/org/xrpl/xrpl4j/tests/environment/LocalRippledEnvironment.java
+++ b/xrpl4j-integration-tests/src/test/java/org/xrpl/xrpl4j/tests/environment/LocalRippledEnvironment.java
@@ -103,4 +103,10 @@ protected void sendPayment(KeyPair sourceKeyPair, Address destinationAddress, Xr
result.transactionResult().transaction());
}
+ /**
+ * Method to accept next ledger ad hoc, only available in RippledContainer.java implementation.
+ */
+ public void acceptLedger() {
+ rippledContainer.acceptLedger();
+ }
}
diff --git a/xrpl4j-integration-tests/src/test/java/org/xrpl/xrpl4j/tests/environment/RippledContainer.java b/xrpl4j-integration-tests/src/test/java/org/xrpl/xrpl4j/tests/environment/RippledContainer.java
index feebbac3d..e123a1f48 100644
--- a/xrpl4j-integration-tests/src/test/java/org/xrpl/xrpl4j/tests/environment/RippledContainer.java
+++ b/xrpl4j-integration-tests/src/test/java/org/xrpl/xrpl4j/tests/environment/RippledContainer.java
@@ -188,4 +188,11 @@ public HttpUrl getBaseUri() {
return getBaseUri(rippledContainer);
}
+ /**
+ * Exposed method to accept next ledger ad hoc.
+ */
+ public void acceptLedger() {
+ assertContainerStarted();
+ LEDGER_ACCEPTOR.accept(this);
+ }
}