> {
.put("UInt32", () -> new UInt32Type())
.put("UInt64", () -> new UInt64Type())
.put("Vector256", () -> new Vector256Type())
+ .put("Issue", () -> new IssueType())
.build();
private final UnsignedByteArray bytes;
diff --git a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/crypto/keys/PrivateKey.java b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/crypto/keys/PrivateKey.java
index f8a5e4285..1880aaa0c 100644
--- a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/crypto/keys/PrivateKey.java
+++ b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/crypto/keys/PrivateKey.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.
@@ -20,6 +20,7 @@
* =========================LICENSE_END==================================
*/
+import com.google.common.base.Preconditions;
import org.xrpl.xrpl4j.codec.addresses.KeyType;
import org.xrpl.xrpl4j.codec.addresses.UnsignedByte;
import org.xrpl.xrpl4j.codec.addresses.UnsignedByteArray;
@@ -32,23 +33,98 @@
public class PrivateKey implements PrivateKeyable, javax.security.auth.Destroyable {
/**
- * Keys generated from the secp256k1 curve have 33 bytes in XRP Ledger. However, keys derived from the ed25519 curve
- * have only 32 bytes, and so get prefixed with this HEX value so that all keys in the ledger are 33 bytes.
+ * A one-byte prefix for ed25519 keys.
+ *
+ * @deprecated This value will be removed in a future version. Prefer {@link #ED2559_PREFIX} or
+ * {@link #SECP256K1_PREFIX} instead.
*/
+ @Deprecated
public static final UnsignedByte PREFIX = UnsignedByte.of(0xED);
+ /**
+ * Private keys (whether from the ed25519 or secp256k1 curves) have 32 bytes naturally. At the same time, secp256k1
+ * public keys have 33 bytes naturally, whereas ed25519 public keys have 32 bytes naturally. Because of this, in XRPL,
+ * ed25519 public keys are prefixed with a one-byte prefix (i.e., 0xED). For consistency, this library (and other XRPL
+ * tooling) also prepends all private keys with artificial prefixes (0xED for ed25519 or 0x00 for secp256k1). This
+ * value is the one-byte prefix for ed25519 keys.
+ */
+ public static final UnsignedByte ED2559_PREFIX = UnsignedByte.of(0xED);
+
+ /**
+ * Private keys (whether from the ed25519 or secp256k1 curves) have 32 bytes naturally. At the same time, secp256k1
+ * public keys have 33 bytes naturally, whereas ed25519 public keys have 32 bytes naturally. Because of this, in XRPL,
+ * ed25519 public keys are prefixed with a one-byte prefix (i.e., 0xED). For consistency, this library (and other XRPL
+ * tooling) also prepends all private keys with artificial prefixes (0xED for ed25519 or 0x00 for secp256k1). This
+ * value is the one-byte prefix for secp256k1 keys.
+ */
+ public static final UnsignedByte SECP256K1_PREFIX = UnsignedByte.of(0x00);
+
private final UnsignedByteArray value;
+
+ private final KeyType keyType;
+
private boolean destroyed;
/**
- * Instantiates a new instance of {@link PrivateKey} using the supplied bytes.
+ * Instantiates a new instance of {@link PrivateKey} using the supplied 32 bytes and specified key type.
+ *
+ * @param value An {@link UnsignedByteArray} containing a private key's natural bytes (i.e., 32 bytes).
+ * @param keyType A {@link KeyType} for this private key.
+ *
+ * @return A {@link PrivateKey}.
+ */
+ public static PrivateKey fromNaturalBytes(final UnsignedByteArray value, final KeyType keyType) {
+ return new PrivateKey(value, keyType); // <-- rely on constructor for all validation and error messaging.
+ }
+
+ /**
+ * Instantiates a new instance of {@link PrivateKey} using the supplied bytes by inspecting the first byte out of 33
+ * to see which {@link KeyType} to assign.
+ *
+ * @param value An {@link UnsignedByteArray} containing a private key's natural bytes (i.e., 32 bytes).
+ *
+ * @return A {@link PrivateKey}.
+ */
+ public static PrivateKey fromPrefixedBytes(final UnsignedByteArray value) {
+ Objects.requireNonNull(value);
+
+ Preconditions.checkArgument(value.length() == 33, String.format(
+ "The `fromPrefixedBytes` function requires input length of 33 bytes, but %s were supplied.",
+ value.length()
+ ));
+
+ final UnsignedByte prefixByte = value.get(0); // <-- relies upon the above length check.
+ if (ED2559_PREFIX.equals(prefixByte)) {
+ return new PrivateKey(value.slice(1, 33), KeyType.ED25519);
+ } else if (SECP256K1_PREFIX.equals(prefixByte)) {
+ return new PrivateKey(value.slice(1, 33), KeyType.SECP256K1);
+ } else {
+ throw new IllegalArgumentException(String.format(
+ "PrivateKey construction requires 32 natural bytes plug a one-byte prefix value of either `0xED` for ed25519 " +
+ "private keys or `0x00` for secp256k1 private keys. Input byte length was %s bytes with a prefixByte value " +
+ "of `0x%s`", value.length(), prefixByte.hexValue())
+ );
+ }
+ }
+
+ /**
+ * Instantiates a new instance of {@link PrivateKey} using the supplied bytes by inspecting the first byte out of 33
+ * to see which {@link KeyType} to assign.
*
* @param value An {@link UnsignedByteArray} containing this key's binary value.
*
* @return A {@link PrivateKey}.
+ *
+ * @deprecated This method will be removed in a future version. Prefer {@link #fromPrefixedBytes(UnsignedByteArray)}
+ * instead.
*/
+ @Deprecated
public static PrivateKey of(final UnsignedByteArray value) {
- return new PrivateKey(value);
+ // Assumption: Any developer using this method before it was deprecated (i.e., v3.2.1 and before) will likely have
+ // expected `value` to have been prefixed. However, until this method was "fixed" in v3.2.2, a developer might have
+ // used this method incorrectly because (1) this method had no length check and (2) the computation of the KeyType
+ // simply inspected the first byte, and naively defaulted to secp256k1 if the first byte was not `0xED`.
+ return fromPrefixedBytes(value);
}
/**
@@ -56,17 +132,91 @@ public static PrivateKey of(final UnsignedByteArray value) {
*
* @param value An {@link UnsignedByteArray} for this key's value.
*/
- private PrivateKey(final UnsignedByteArray value) {
- this.value = Objects.requireNonNull(value);
+ private PrivateKey(final UnsignedByteArray value, final KeyType keyType) {
+ Objects.requireNonNull(value); // <-- Check not-null first.
+ this.keyType = Objects.requireNonNull(keyType);
+
+ // We assert this precondition here because this is a private constructor that can be fully tested via unit test,
+ // so this precondition should never be violated, and if it is, then it's a bug in xrpl4j.
+ Preconditions.checkArgument(
+ value.length() == 32,
+ "Byte values passed to this constructor must be 32 bytes long, with no prefix."
+ );
+
+ // NOTE: We do not do any further sanity checking of `value` (e.g., converting to a BigInteger and checking if it's
+ // in the proper range of [1, N-1]) on grounds that any particular underlying implementation will enforce these
+ // invariants for us, and will likely be more correct than we would.
+
+ // Note: `UnsignedByteArray#toByteArray` will perform a copy, which is what we want in order to enforce
+ // immutability of this PrivateKey (because Java 8 doesn't support immutable byte arrays).
+ this.value = UnsignedByteArray.of(value.toByteArray()); // <- Always copy to ensure immutability
}
/**
* Accessor for the key value, in binary (Note: will be 33 bytes).
*
* @return An instance of {@link UnsignedByteArray}.
+ *
+ * @deprecated Prefer {@link #prefixedBytes()} or {@link #naturalBytes()} instead.
*/
+ @Deprecated
public UnsignedByteArray value() {
- return UnsignedByteArray.of(value.toByteArray());
+ // Check for empty value, which can occur if this PrivateKey is "destroyed" but still in memory.
+ if (value.length() == 0) {
+ return UnsignedByteArray.empty();
+ } else {
+ // This is technically wrong (because `value()` had an ambiguous meaning prior to fixing #486), but this mirrors
+ // what's in v3 prior to fixing #486, and will be fixed in v4 once the deprecated `.value()` is removed.
+ return this.prefixedBytes();
+ }
+ }
+
+ /**
+ * Accessor for the byte value in {@link #value()} but in a more natural form (i.e., the size of the returned value
+ * will be 32 bytes). Natural ed25519 or secp256k1 private keys will ordinarily contain only 32 bytes. However, in
+ * XRPL, private keys are represented with a single-byte prefix (i.e., `0xED` for ed25519 and `0x00` for secp256k1
+ * keys).
+ *
+ * @return An instance of {@link UnsignedByteArray}.
+ */
+ public UnsignedByteArray naturalBytes() {
+ // Check for empty value, which can occur if this PrivateKey is "destroyed" but still in memory.
+ if (value.length() == 0) {
+ return UnsignedByteArray.empty();
+ } else {
+ // Note: `toByteArray()` will perform a copy, which is what we want in order to enforce immutability of this
+ // PrivateKey (because Java 8 doesn't support immutable byte arrays).
+ return UnsignedByteArray.of(value.toByteArray());
+ }
+ }
+
+ /**
+ * Accessor for the bytes of this private key in a prefixed. Natural ed25519 or secp256k1 private keys will ordinarily
+ * contain only 32 bytes. However, in XRPL, private keys are typically represented with a single-byte prefix (i.e.,
+ * `0xED` for ed25519 and `0x00` for secp256k1 keys), which this method provides.
+ *
+ * @return An instance of {@link UnsignedByteArray}.
+ */
+ public UnsignedByteArray prefixedBytes() {
+ // Check for empty value, which can occur if this PrivateKey is "destroyed" but still in memory.
+ if (value.length() == 0) {
+ return UnsignedByteArray.empty();
+ } else {
+ switch (keyType) {
+ case ED25519: {
+ return UnsignedByteArray.of(ED2559_PREFIX)
+ .append(this.naturalBytes()); // <-- Forward to this function to guarantee copied bytes
+ }
+ case SECP256K1: {
+ return UnsignedByteArray.of(SECP256K1_PREFIX)
+ .append(this.naturalBytes()); // <-- Forward to this function to guarantee copied bytes
+ }
+ default: {
+ // This should never happen; if it does, there's a bug in this implementation
+ throw new IllegalStateException(String.format("Invalid keyType=%s", keyType));
+ }
+ }
+ }
}
/**
@@ -75,8 +225,7 @@ public UnsignedByteArray value() {
* @return A {@link KeyType}.
*/
public final KeyType keyType() {
- final UnsignedByte prefixByte = this.value().get(0);
- return prefixByte.equals(PREFIX) ? KeyType.ED25519 : KeyType.SECP256K1;
+ return this.keyType;
}
@Override
@@ -95,25 +244,25 @@ public boolean equals(Object obj) {
if (this == obj) {
return true;
}
- if (!(obj instanceof PrivateKey)) {
+ if (obj == null || getClass() != obj.getClass()) {
return false;
}
-
PrivateKey that = (PrivateKey) obj;
-
- return value.equals(that.value);
+ return Objects.equals(value, that.value) && keyType == that.keyType;
}
@Override
public int hashCode() {
- return value.hashCode();
+ return Objects.hash(value, keyType);
}
@Override
public String toString() {
- return "PrivateKey{" +
- "value=[redacted]" +
- ", destroyed=" + destroyed +
- '}';
+ return String.format("PrivateKey{" +
+ "value=[redacted]," +
+ "keyType=%s," +
+ "destroyed=%s" +
+ "}", this.keyType(), this.isDestroyed()
+ );
}
}
diff --git a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/crypto/keys/PublicKey.java b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/crypto/keys/PublicKey.java
index 5a07e795c..84e99416d 100644
--- a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/crypto/keys/PublicKey.java
+++ b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/crypto/keys/PublicKey.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.
@@ -35,6 +35,7 @@
import org.xrpl.xrpl4j.codec.addresses.Decoded;
import org.xrpl.xrpl4j.codec.addresses.KeyType;
import org.xrpl.xrpl4j.codec.addresses.PublicKeyCodec;
+import org.xrpl.xrpl4j.codec.addresses.UnsignedByte;
import org.xrpl.xrpl4j.codec.addresses.UnsignedByteArray;
import org.xrpl.xrpl4j.codec.addresses.Version;
import org.xrpl.xrpl4j.model.jackson.modules.PublicKeyDeserializer;
@@ -53,12 +54,18 @@
public interface PublicKey {
/**
- * Multi-signed transactions must contain an empty String in the SigningPublicKey field. This constant
- * is an {@link PublicKey} that can be used as the {@link Transaction#signingPublicKey()} value for multi-signed
+ * A one-byte prefix for ed25519 keys. In XRPL, ed25519 public keys are prefixed with a one-byte prefix (i.e., `0xED`)
+ * in order to be consistent with secp256k1 public keys, which always have 33 bytes.
+ */
+ UnsignedByte ED2559_PREFIX = UnsignedByte.of(0xED);
+
+ /**
+ * Multi-signed transactions must contain an empty String in the SigningPublicKey field. This constant is an
+ * {@link PublicKey} that can be used as the {@link Transaction#signingPublicKey()} value for multi-signed
* transactions.
*/
PublicKey MULTI_SIGN_PUBLIC_KEY = PublicKey.builder().value(UnsignedByteArray.empty()).build();
-
+
/**
* Instantiates a new builder.
*
@@ -77,11 +84,11 @@ static ImmutablePublicKey.Builder builder() {
*/
static PublicKey fromBase58EncodedPublicKey(final String base58EncodedPublicKey) {
Objects.requireNonNull(base58EncodedPublicKey);
-
+
if (base58EncodedPublicKey.isEmpty()) {
return MULTI_SIGN_PUBLIC_KEY;
}
-
+
return PublicKey.builder()
.value(PublicKeyCodec.getInstance().decodeAccountPublicKey(base58EncodedPublicKey))
.build();
@@ -100,7 +107,7 @@ static PublicKey fromBase16EncodedPublicKey(final String base16EncodedPublicKey)
if (base16EncodedPublicKey.isEmpty()) {
return MULTI_SIGN_PUBLIC_KEY;
}
-
+
return PublicKey.builder()
.value(UnsignedByteArray.of(BaseEncoding.base16().decode(base16EncodedPublicKey.toUpperCase())))
.build();
@@ -123,7 +130,7 @@ default String base58Value() {
if (value().length() == 0) {
return "";
}
-
+
return PublicKeyCodec.getInstance().encodeAccountPublicKey(this.value());
}
@@ -175,5 +182,5 @@ static UnsignedByteArray computePublicKeyHash(final UnsignedByteArray publicKey)
digest.doFinal(ripemdSha256, 0);
return UnsignedByteArray.of(ripemdSha256);
}
-
+
}
diff --git a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/crypto/keys/Seed.java b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/crypto/keys/Seed.java
index 4e62dfd29..c2eabd2e9 100644
--- a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/crypto/keys/Seed.java
+++ b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/crypto/keys/Seed.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.
@@ -21,6 +21,7 @@
*/
import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import com.google.common.hash.Hashing;
import com.google.common.primitives.UnsignedInteger;
@@ -34,11 +35,11 @@
import org.xrpl.xrpl4j.codec.addresses.Decoded;
import org.xrpl.xrpl4j.codec.addresses.KeyType;
import org.xrpl.xrpl4j.codec.addresses.SeedCodec;
-import org.xrpl.xrpl4j.codec.addresses.UnsignedByte;
import org.xrpl.xrpl4j.codec.addresses.UnsignedByteArray;
import org.xrpl.xrpl4j.codec.addresses.Version;
import org.xrpl.xrpl4j.codec.addresses.exceptions.DecodeException;
import org.xrpl.xrpl4j.crypto.HashingUtils;
+import org.xrpl.xrpl4j.crypto.signing.bc.Secp256k1;
import java.math.BigInteger;
import java.util.Objects;
@@ -172,6 +173,7 @@ static Seed secp256k1SeedFromEntropy(final Entropy entropy) {
* @param base58EncodedSecret A base58-encoded {@link String} that represents an encoded seed.
*
* @return A {@link Seed}.
+ *
* @see "https://xrpl.org/xrp-testnet-faucet.html"
*/
static Seed fromBase58EncodedSecret(final Base58EncodedSecret base58EncodedSecret) {
@@ -309,20 +311,18 @@ public static KeyPair deriveKeyPair(final Seed seed) {
}
UnsignedByteArray rawPrivateKey = HashingUtils.sha512Half(decoded.bytes());
+ // `rawPrivateKey` will be 32 bytes due to hashing, so no padding required.
Ed25519PrivateKeyParameters privateKey = new Ed25519PrivateKeyParameters(rawPrivateKey.toByteArray(), 0);
Ed25519PublicKeyParameters publicKey = privateKey.generatePublicKey();
- // XRPL ED25519 keys are prefixed with 0xED so that the keys are 33 bytes and match the length of secp256k1
- // keys. Note that Bouncy Castle only deals with 32 byte keys, so we need to manually add the prefix
- final UnsignedByte prefix = UnsignedByte.of(0xED);
- final UnsignedByteArray prefixedPrivateKey = UnsignedByteArray.of(prefix)
- .append(UnsignedByteArray.of(privateKey.getEncoded()));
- final UnsignedByteArray prefixedPublicKey = UnsignedByteArray.of(prefix)
+ // ed25519 public keys in XRPL have a one-byte prefix of `0xED` so that all public keys have 33 bytes (this is
+ // to conform with secp256k1 public keys, which are 33 bytes long and have a `0x00` byte prefix.
+ final UnsignedByteArray prefixedPublicKey = UnsignedByteArray.of(PublicKey.ED2559_PREFIX)
.append(UnsignedByteArray.of(publicKey.getEncoded()));
return KeyPair.builder()
- .privateKey(PrivateKey.of(prefixedPrivateKey))
+ .privateKey(PrivateKey.fromNaturalBytes(UnsignedByteArray.of(privateKey.getEncoded()), KeyType.ED25519))
.publicKey(PublicKey.fromBase16EncodedPublicKey(prefixedPublicKey.hexValue()))
.build();
}
@@ -382,15 +382,17 @@ public static KeyPair deriveKeyPair(final Seed seed) {
private static KeyPair deriveKeyPair(final UnsignedByteArray seedBytes, final int accountNumber) {
Objects.requireNonNull(seedBytes);
- // private key needs to be a BigInteger so we can derive the public key by multiplying G by the private key.
+ // private key needs to be a BigInteger, so we can derive the public key by multiplying G by the private key.
final BigInteger privateKeyInt = derivePrivateKey(seedBytes, accountNumber);
- final UnsignedByteArray publicKeyInt = derivePublicKey(privateKeyInt);
+
+ // This derivePublicKey will pad to 33 bytes.
+ final UnsignedByteArray publicKeyByteArray = derivePublicKey(privateKeyInt);
+ // This merely enforces the invariant that should be defined in `derivePublicKey(privateKeyInt);`
+ Preconditions.checkArgument(publicKeyByteArray.length() == 33, "Length was " + publicKeyByteArray.length());
return KeyPair.builder()
- .privateKey(PrivateKey.of(UnsignedByteArray.of(privateKeyInt.toByteArray())))
- .publicKey(PublicKey.fromBase16EncodedPublicKey(
- UnsignedByteArray.of(publicKeyInt.toByteArray()).hexValue()
- ))
+ .privateKey(PrivateKey.fromPrefixedBytes(Secp256k1.toUnsignedByteArray(privateKeyInt, 33)))
+ .publicKey(PublicKey.builder().value(publicKeyByteArray).build())
.build();
}
@@ -403,13 +405,17 @@ private static KeyPair deriveKeyPair(final UnsignedByteArray seedBytes, final in
*/
private static UnsignedByteArray derivePublicKey(final BigInteger privateKey) {
Objects.requireNonNull(privateKey);
- return UnsignedByteArray.of(EC_DOMAIN_PARAMETERS.getG().multiply(privateKey).getEncoded(true));
+
+ UnsignedByteArray unpaddedBytes = UnsignedByteArray.of(
+ EC_DOMAIN_PARAMETERS.getG().multiply(privateKey).getEncoded(true));
+
+ return Secp256k1.withZeroPrefixPadding(unpaddedBytes, 33); // <-- Ensure returned UBA has 33 bytes.
}
/**
* Derive a public key from the supplied {@code seed} and {@code accountNumber}.
*
- * @param seed A {@link UnsignedByteArray} representing a seed that can be used to generated an XRPL
+ * @param seed A {@link UnsignedByteArray} representing a seed that can be used to generate an XRPL
* address.
* @param accountNumber An integer representing the account nunmber.
*
diff --git a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/crypto/keys/bc/BcKeyUtils.java b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/crypto/keys/bc/BcKeyUtils.java
index 12d7d34e5..0b8df8cb3 100644
--- a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/crypto/keys/bc/BcKeyUtils.java
+++ b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/crypto/keys/bc/BcKeyUtils.java
@@ -37,6 +37,7 @@
import org.xrpl.xrpl4j.codec.addresses.UnsignedByteArray;
import org.xrpl.xrpl4j.crypto.keys.PrivateKey;
import org.xrpl.xrpl4j.crypto.keys.PublicKey;
+import org.xrpl.xrpl4j.crypto.signing.bc.Secp256k1;
import java.math.BigInteger;
import java.security.Security;
@@ -84,10 +85,11 @@ public static PrivateKey toPrivateKey(final Ed25519PrivateKeyParameters ed25519P
Objects.requireNonNull(ed25519PrivateKeyParameters);
// XRPL ED25519 keys are prefixed with 0xED so that the keys are 33 bytes and match the length of sekp256k1 keys.
- // Bouncy Castle only deals with 32 byte keys, so we need to manually add the prefix
- UnsignedByteArray prefixedPrivateKey = UnsignedByteArray.of(PrivateKey.PREFIX)
- .append(UnsignedByteArray.of(ed25519PrivateKeyParameters.getEncoded()));
- return PrivateKey.of(prefixedPrivateKey);
+ // Bouncy Castle only deals with 32 byte keys, but in XRPL these often include a one byte prefix (i.e., `0xED`) to
+ // indicate this is an ed25519 private key. However, this is taken care of by the `PrivateKey.fromNaturalBytes`.
+ return PrivateKey.fromNaturalBytes(
+ UnsignedByteArray.of(ed25519PrivateKeyParameters.getEncoded()), KeyType.ED25519
+ );
}
/**
@@ -98,10 +100,10 @@ public static PrivateKey toPrivateKey(final Ed25519PrivateKeyParameters ed25519P
* @return A {@link PrivateKey}.
*/
public static PrivateKey toPrivateKey(final ECPrivateKeyParameters ecPrivateKeyParameters) {
- // Convert the HEX representation of the BigInteger into bytes.
- byte[] privateKeyBytes = BaseEncoding.base16().decode(ecPrivateKeyParameters.getD().toString(16).toUpperCase());
- final UnsignedByteArray privateKeyUba = UnsignedByteArray.of(privateKeyBytes);
- return PrivateKey.of(privateKeyUba);
+ return PrivateKey.fromPrefixedBytes(
+ // Call `UnsignedByteArray.from` to properly prefix-pad the BigInteger's bytes.
+ Secp256k1.toUnsignedByteArray(ecPrivateKeyParameters.getD(), 33)
+ );
}
/**
@@ -134,7 +136,7 @@ public static PublicKey toPublicKey(final Ed25519PublicKeyParameters ed25519Publ
Objects.requireNonNull(ed25519PublicKeyParameters);
// XRPL ED25519 keys are prefixed with 0xED so that the keys are 33 bytes and match the length of sekp256k1 keys.
// Bouncy Castle only deals with 32 byte keys, so we need to manually add the prefix
- UnsignedByteArray prefixedPublicKey = UnsignedByteArray.of(PrivateKey.PREFIX)
+ UnsignedByteArray prefixedPublicKey = UnsignedByteArray.of(PublicKey.ED2559_PREFIX)
.append(UnsignedByteArray.of(ed25519PublicKeyParameters.getEncoded()));
return PublicKey.builder()
@@ -203,7 +205,8 @@ public static PublicKey toPublicKey(final PrivateKey privateKey) {
public static Ed25519PrivateKeyParameters toEd25519PrivateKeyParams(PrivateKey privateKey) {
Objects.requireNonNull(privateKey);
Preconditions.checkArgument(privateKey.keyType() == ED25519);
- return new Ed25519PrivateKeyParameters(privateKey.value().toByteArray(), 1); // <-- Strip leading prefix byte.
+ // Use offset 0 with no prefix
+ return new Ed25519PrivateKeyParameters(privateKey.naturalBytes().toByteArray(), 0);
}
/**
@@ -233,9 +236,9 @@ public static ECPrivateKeyParameters toEcPrivateKeyParams(final PrivateKey priva
Objects.requireNonNull(privateKey);
Preconditions.checkArgument(privateKey.keyType() == KeyType.SECP256K1, "KeyType must be SECP256K1");
- final BigInteger privateKeyInt = new BigInteger(
- BaseEncoding.base16().encode(privateKey.value().toByteArray()), 16
- );
- return new ECPrivateKeyParameters(privateKeyInt, BcKeyUtils.PARAMS);
+ // From http://www.secg.org/sec1-v2.pdf: A PrivateKey consists of an elliptic curve secret key `d` which is an
+ // integer in the interval [1, n − 1]. Therefore, it is safe to assume that the signum below should always be 1.
+ final BigInteger secretKeyD = new BigInteger(1, privateKey.naturalBytes().toByteArray());
+ return new ECPrivateKeyParameters(secretKeyD, BcKeyUtils.PARAMS);
}
}
diff --git a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/crypto/signing/AbstractTransactionSigner.java b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/crypto/signing/AbstractTransactionSigner.java
index 3a529d490..f30cdbe49 100644
--- a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/crypto/signing/AbstractTransactionSigner.java
+++ b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/crypto/signing/AbstractTransactionSigner.java
@@ -31,7 +31,9 @@
import java.util.Objects;
/**
- * An abstract implementation of {@link SignatureService} with common functionality that sub-classes can utilize.
+ * An abstract implementation of {@link SignatureService} with common functionality that subclasses can utilize.
+ *
+ * @param A type that extends {@link PrivateKeyable}.
*/
public abstract class AbstractTransactionSigner
implements TransactionSigner
{
diff --git a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/crypto/signing/SignatureUtils.java b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/crypto/signing/SignatureUtils.java
index 6ade9325c..7f8622555 100644
--- a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/crypto/signing/SignatureUtils.java
+++ b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/crypto/signing/SignatureUtils.java
@@ -31,9 +31,16 @@
import org.xrpl.xrpl4j.model.transactions.AccountDelete;
import org.xrpl.xrpl4j.model.transactions.AccountSet;
import org.xrpl.xrpl4j.model.transactions.Address;
+import org.xrpl.xrpl4j.model.transactions.AmmBid;
+import org.xrpl.xrpl4j.model.transactions.AmmCreate;
+import org.xrpl.xrpl4j.model.transactions.AmmDelete;
+import org.xrpl.xrpl4j.model.transactions.AmmDeposit;
+import org.xrpl.xrpl4j.model.transactions.AmmVote;
+import org.xrpl.xrpl4j.model.transactions.AmmWithdraw;
import org.xrpl.xrpl4j.model.transactions.CheckCancel;
import org.xrpl.xrpl4j.model.transactions.CheckCash;
import org.xrpl.xrpl4j.model.transactions.CheckCreate;
+import org.xrpl.xrpl4j.model.transactions.Clawback;
import org.xrpl.xrpl4j.model.transactions.DepositPreAuth;
import org.xrpl.xrpl4j.model.transactions.EscrowCancel;
import org.xrpl.xrpl4j.model.transactions.EscrowCreate;
@@ -271,6 +278,34 @@ public SingleSignedTransaction addSignatureToTransact
transactionWithSignature = TicketCreate.builder().from((TicketCreate) transaction)
.transactionSignature(signature)
.build();
+ } else if (Clawback.class.isAssignableFrom(transaction.getClass())) {
+ transactionWithSignature = Clawback.builder().from((Clawback) transaction)
+ .transactionSignature(signature)
+ .build();
+ } else if (AmmBid.class.isAssignableFrom(transaction.getClass())) {
+ transactionWithSignature = AmmBid.builder().from((AmmBid) transaction)
+ .transactionSignature(signature)
+ .build();
+ } else if (AmmCreate.class.isAssignableFrom(transaction.getClass())) {
+ transactionWithSignature = AmmCreate.builder().from((AmmCreate) transaction)
+ .transactionSignature(signature)
+ .build();
+ } else if (AmmDeposit.class.isAssignableFrom(transaction.getClass())) {
+ transactionWithSignature = AmmDeposit.builder().from((AmmDeposit) transaction)
+ .transactionSignature(signature)
+ .build();
+ } else if (AmmVote.class.isAssignableFrom(transaction.getClass())) {
+ transactionWithSignature = AmmVote.builder().from((AmmVote) transaction)
+ .transactionSignature(signature)
+ .build();
+ } else if (AmmWithdraw.class.isAssignableFrom(transaction.getClass())) {
+ transactionWithSignature = AmmWithdraw.builder().from((AmmWithdraw) transaction)
+ .transactionSignature(signature)
+ .build();
+ } else if (AmmDelete.class.isAssignableFrom(transaction.getClass())) {
+ transactionWithSignature = AmmDelete.builder().from((AmmDelete) transaction)
+ .transactionSignature(signature)
+ .build();
} else {
// Should never happen, but will in a unit test if we miss one.
throw new IllegalArgumentException("Signing fields could not be added to the transaction.");
@@ -405,6 +440,34 @@ public T addMultiSignaturesToTransaction(T transaction,
transactionWithSignatures = TicketCreate.builder().from((TicketCreate) transaction)
.signers(signers)
.build();
+ } else if (Clawback.class.isAssignableFrom(transaction.getClass())) {
+ transactionWithSignatures = Clawback.builder().from((Clawback) transaction)
+ .signers(signers)
+ .build();
+ } else if (AmmBid.class.isAssignableFrom(transaction.getClass())) {
+ transactionWithSignatures = AmmBid.builder().from((AmmBid) transaction)
+ .signers(signers)
+ .build();
+ } else if (AmmCreate.class.isAssignableFrom(transaction.getClass())) {
+ transactionWithSignatures = AmmCreate.builder().from((AmmCreate) transaction)
+ .signers(signers)
+ .build();
+ } else if (AmmDeposit.class.isAssignableFrom(transaction.getClass())) {
+ transactionWithSignatures = AmmDeposit.builder().from((AmmDeposit) transaction)
+ .signers(signers)
+ .build();
+ } else if (AmmVote.class.isAssignableFrom(transaction.getClass())) {
+ transactionWithSignatures = AmmVote.builder().from((AmmVote) transaction)
+ .signers(signers)
+ .build();
+ } else if (AmmWithdraw.class.isAssignableFrom(transaction.getClass())) {
+ transactionWithSignatures = AmmWithdraw.builder().from((AmmWithdraw) transaction)
+ .signers(signers)
+ .build();
+ } else if (AmmDelete.class.isAssignableFrom(transaction.getClass())) {
+ transactionWithSignatures = AmmDelete.builder().from((AmmDelete) transaction)
+ .signers(signers)
+ .build();
} else {
// Should never happen, but will in a unit test if we miss one.
throw new IllegalArgumentException("Signing fields could not be added to the transaction.");
diff --git a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/crypto/signing/bc/BcSignatureService.java b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/crypto/signing/bc/BcSignatureService.java
index 310df2616..977281264 100644
--- a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/crypto/signing/bc/BcSignatureService.java
+++ b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/crypto/signing/bc/BcSignatureService.java
@@ -94,30 +94,21 @@ protected synchronized Signature edDsaSign(
Objects.requireNonNull(privateKey);
Objects.requireNonNull(signableTransactionBytes);
- final byte[] privateKeyBytes = new byte[32];
- try {
- // Remove ED prefix byte (if it's there)
- System.arraycopy(privateKey.value().toByteArray(), 1, privateKeyBytes, 0, 32);
- Ed25519PrivateKeyParameters privateKeyParameters = new Ed25519PrivateKeyParameters(
- privateKeyBytes, 0
- );
-
- ed25519Signer.reset();
- ed25519Signer.init(true, privateKeyParameters);
- ed25519Signer.update(
- signableTransactionBytes.toByteArray(), 0, signableTransactionBytes.getUnsignedBytes().size()
- );
-
- final UnsignedByteArray sigBytes = UnsignedByteArray.of(ed25519Signer.generateSignature());
- return Signature.builder()
- .value(sigBytes)
- .build();
- } finally {
- // Clear out the copied array, which was only used for signing.
- for (int i = 0; i < 32; i++) {
- privateKeyBytes[i] = (byte) 0;
- }
- }
+ final Ed25519PrivateKeyParameters privateKeyParameters = BcKeyUtils.toEd25519PrivateKeyParams(privateKey);
+
+ final byte[] signableBytes = signableTransactionBytes.toByteArray();
+
+ ed25519Signer.reset();
+ ed25519Signer.init(true, privateKeyParameters);
+ ed25519Signer.update(signableBytes, 0, signableBytes.length);
+
+ final UnsignedByteArray sigBytes = UnsignedByteArray.of(ed25519Signer.generateSignature());
+ return Signature.builder()
+ .value(sigBytes)
+ .build();
+
+ // Note: Ed25519PrivateKeyParameters does not provide a destroy function, but it will be eligible for cleanup (in
+ // the next GC) once this function exits.
}
@SuppressWarnings("checkstyle:LocalVariableName")
@@ -128,10 +119,9 @@ protected synchronized Signature ecDsaSign(final PrivateKey privateKey, final Un
final UnsignedByteArray messageHash = HashingUtils.sha512Half(transactionBytes);
- final BigInteger privateKeyInt = new BigInteger(privateKey.value().toByteArray());
- final ECPrivateKeyParameters parameters = new ECPrivateKeyParameters(privateKeyInt, BcKeyUtils.PARAMS);
+ final ECPrivateKeyParameters ecPrivateKeyParams = BcKeyUtils.toEcPrivateKeyParams(privateKey);
- ecdsaSigner.init(true, parameters);
+ ecdsaSigner.init(true, ecPrivateKeyParams);
final BigInteger[] signatures = ecdsaSigner.generateSignature(messageHash.toByteArray());
final BigInteger r = signatures[0];
BigInteger s = signatures[1];
diff --git a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/crypto/signing/bc/Secp256k1.java b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/crypto/signing/bc/Secp256k1.java
index 6097303a5..c747b37c3 100644
--- a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/crypto/signing/bc/Secp256k1.java
+++ b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/crypto/signing/bc/Secp256k1.java
@@ -20,17 +20,20 @@
* =========================LICENSE_END==================================
*/
+import com.google.common.base.Preconditions;
import org.bouncycastle.asn1.sec.SECNamedCurves;
import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.crypto.params.ECDomainParameters;
+import org.xrpl.xrpl4j.codec.addresses.UnsignedByte;
+import org.xrpl.xrpl4j.codec.addresses.UnsignedByteArray;
import org.xrpl.xrpl4j.crypto.keys.bc.BcKeyUtils;
+import java.math.BigInteger;
+import java.util.Objects;
+
/**
* Static constants for Secp256k1 operations.
- *
- * @deprecated This interface is deprecated in-favor of {@link BcKeyUtils#PARAMS}.
*/
-@Deprecated
public interface Secp256k1 {
/**
@@ -54,4 +57,95 @@ public interface Secp256k1 {
EC_PARAMETERS.getH()
);
+ /**
+ * Private keys (whether from the ed25519 or secp256k1 curves) have 32 bytes naturally. At the same time, secp256k1
+ * public keys have 33 bytes naturally, whereas ed25519 public keys have 32 bytes naturally. Because of this, in XRPL,
+ * ed25519 public keys are prefixed with a one-byte prefix (i.e., 0xED). For consistency, this library (and other XRPL
+ * tooling) also prepends all private keys with artificial prefixes (0xED for ed25519 or 0x00 for secp256k1). This
+ * value is the one-byte prefix for secp256k1 keys.
+ */
+ UnsignedByte SECP256K1_PREFIX = UnsignedByte.of(0x00);
+
+ /**
+ * Creates an {@link UnsignedByteArray} from the bytes of a supplied {@link BigInteger}. If the length of the
+ * resulting array is not at least {@code minFinalByteLength}, then the result is prefix padded with `0x00` bytes
+ * until the final array length is {@code minFinalByteLength}.
+ *
+ * This function exists to ensure that transformation of secp256k1 private keys from a {@link BigInteger} to a
+ * byte array are done in a consistent manner, always yielding the desired number of bytes. For example, secp256k1
+ * private keys are 32-bytes long naturally. However, when transformed to a byte array via
+ * {@link BigInteger#toByteArray()}, the result will not always have the same number of leading zero bytes that one
+ * might expect. Sometimes the returned array will have 33 bytes, one of which is a zero-byte prefix pad that is meant
+ * to ensure the underlying number is not represented as a negative number. Other times, the array will have fewer
+ * than 32 bytes, for example 31 or even 30, if the byte array has redundant leading zero bytes.
+ *
+ *
Note that this function assumes the supplied {@code amount} is always positive, which roughly correlates with
+ * the secp256k1 requirement that private key scalar `D` values be in the range [1, N-1].
+ *
+ *
Thus, this function can be used to normalize a byte array containing a secp256k1 private key with a desired
+ * number of 0-byte padding to ensure that it is always the desired {@code minFinalByteLength} (e.g., in this library,
+ * secp256k1 private keys should always be comprised of a 32-byte natural private key with a one-byte `0x00` prefix
+ * pad).
+ *
+ * @param amount A {@link BigInteger} to convert into an {@link UnsignedByteArray}.
+ * @param minFinalByteLength The minimum length, in bytes, that the final result must be. If the final byte length is
+ * less than this number, the resulting array will be prefix padded to increase its length
+ * to this number.
+ *
+ * @return An {@link UnsignedByteArray} with a length of at least {@code minFinalByteLength}.
+ *
+ * @see "https://github.com/XRPLF/xrpl4j/issues/486"
+ */
+ static UnsignedByteArray toUnsignedByteArray(final BigInteger amount, int minFinalByteLength) {
+ Objects.requireNonNull(amount);
+ Preconditions.checkArgument(amount.signum() >= 0, "amount must not be negative");
+ Preconditions.checkArgument(minFinalByteLength >= 0, "minFinalByteLength must not be negative");
+
+ // Return the `amount` as an UnsignedByteArray, but with the proper zero-byte prefix padding.
+ return withZeroPrefixPadding(amount.toByteArray(), minFinalByteLength);
+ }
+
+ /**
+ * Construct a new {@link UnsignedByteArray} that contains the bytes from {@code bytes}, but with enough `0x00` prefix
+ * padding bytes such that the final length of the returned value is {@code minFinalByteLength}.
+ *
+ * @param bytes An {@link UnsignedByteArray} to zero-pad.
+ * @param minFinalByteLength The minimum length, in bytes, that the final result must be zero-byte prefix-padded to.
+ * If this number is greater-than {@code #length}, then this value will be reduced to
+ * {@code #length}.
+ *
+ * @return A copy of this {@link UnsignedByteArray} that has been zero-byte prefix-padded such that its final length
+ * is at least {@code minFinalByteLength}.
+ */
+ static UnsignedByteArray withZeroPrefixPadding(final UnsignedByteArray bytes, int minFinalByteLength) {
+ Preconditions.checkArgument(minFinalByteLength >= 0, "minFinalByteLength must not be negative");
+
+ return withZeroPrefixPadding(bytes.toByteArray(), minFinalByteLength);
+ }
+
+ /**
+ * Construct a new {@link UnsignedByteArray} that contains the bytes from {@code bytes}, but with enough `0x00` prefix
+ * padding bytes such that the final length of the returned value is {@code minFinalByteLength}.
+ *
+ * @param bytes A byte array to zero-pad.
+ * @param minFinalByteLength The minimum length, in bytes, that the final result must be zero-byte prefix-padded to.
+ * If this number is greater-than {@code #length}, then this value will be reduced to
+ * {@code #length}.
+ *
+ * @return A copy of this {@link UnsignedByteArray} that has been zero-byte prefix-padded such that its final length
+ * is at least {@code minFinalByteLength}.
+ */
+ static UnsignedByteArray withZeroPrefixPadding(final byte[] bytes, int minFinalByteLength) {
+ Preconditions.checkArgument(minFinalByteLength >= 0, "minFinalByteLength must not be negative");
+
+ if (bytes.length > minFinalByteLength) { // <-- Increase `minFinalByteLength` to be at least bytes.length
+ minFinalByteLength = bytes.length;
+ }
+
+ final int numPadBytes = minFinalByteLength - bytes.length; // <-- numPadBytes will never be negative
+ byte[] resultBytes = new byte[minFinalByteLength];
+ System.arraycopy(bytes, 0, resultBytes, numPadBytes, bytes.length);
+ return UnsignedByteArray.of(resultBytes);
+ }
+
}
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 2c038ad4a..13e765602 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
@@ -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.
@@ -20,6 +20,8 @@
* =========================LICENSE_END==================================
*/
+import com.google.common.annotations.Beta;
+
/**
* A definition class for all rippled method name constants.
*/
@@ -176,6 +178,12 @@ public class XrplMethods {
*/
public static final String RIPPLE_PATH_FIND = "ripple_path_find";
+ /**
+ * Constant for the ripple_path_find rippled API method.
+ */
+ @Beta
+ public static final String AMM_INFO = "amm_info";
+
// Payment Channel methods
/**
* Constant for the channel_authorize rippled API method.
diff --git a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/client/amm/AmmInfo.java b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/client/amm/AmmInfo.java
new file mode 100644
index 000000000..94a81b4cd
--- /dev/null
+++ b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/client/amm/AmmInfo.java
@@ -0,0 +1,121 @@
+package org.xrpl.xrpl4j.model.client.amm;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import com.google.common.annotations.Beta;
+import org.immutables.value.Value;
+import org.xrpl.xrpl4j.model.client.XrplResult;
+import org.xrpl.xrpl4j.model.transactions.Address;
+import org.xrpl.xrpl4j.model.transactions.CurrencyAmount;
+import org.xrpl.xrpl4j.model.transactions.IssuedCurrencyAmount;
+import org.xrpl.xrpl4j.model.transactions.TradingFee;
+
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * Information about the requested AMM ledger entry. This response is very closely related to
+ * {@link org.xrpl.xrpl4j.model.ledger.AmmObject}, however rippled returns the object in a different format in
+ * responses to {@code amm_info} RPC requests.
+ *
+ *
This class will be marked {@link Beta} until the AMM amendment is enabled on mainnet. Its API is subject to
+ * change.
+ */
+@Value.Immutable
+@JsonSerialize(as = ImmutableAmmInfo.class)
+@JsonDeserialize(as = ImmutableAmmInfo.class)
+@Beta
+public interface AmmInfo extends XrplResult {
+
+ /**
+ * Construct a {@code AmmInfo} builder.
+ *
+ * @return An {@link ImmutableAmmInfo.Builder}.
+ */
+ static ImmutableAmmInfo.Builder builder() {
+ return ImmutableAmmInfo.builder();
+ }
+
+ /**
+ * The address of the special account that holds this AMM's assets.
+ *
+ * @return An {@link Address}.
+ */
+ @JsonProperty("account")
+ Address account();
+
+ /**
+ * The definition for one of the two assets this AMM holds.
+ *
+ * @return A {@link CurrencyAmount}.
+ */
+ @JsonProperty("amount")
+ CurrencyAmount amount();
+
+ /**
+ * The definition for the other asset this AMM holds.
+ *
+ * @return A {@link CurrencyAmount}.
+ */
+ @JsonProperty("amount2")
+ CurrencyAmount amount2();
+
+ /**
+ * Whether the first asset of the AMM is frozen. Always false is the first asset is XRP.
+ *
+ * @return {@code true} if asset 1 is frozen, otherwise {@code false}.
+ */
+ @Value.Default
+ @JsonProperty("asset_frozen")
+ default boolean assetFrozen() {
+ return false;
+ }
+
+ /**
+ * Whether the second asset of the AMM is frozen. Always false is the second asset is XRP.
+ *
+ * @return {@code true} if asset 2 is frozen, otherwise {@code false}.
+ */
+ @Value.Default
+ @JsonProperty("asset2_frozen")
+ default boolean asset2Frozen() {
+ return false;
+ }
+
+ /**
+ * Details of the current owner of the auction slot.
+ *
+ * @return An {@link AmmInfoAuctionSlot}.
+ */
+ @JsonProperty("auction_slot")
+ Optional auctionSlot();
+
+ /**
+ * The total outstanding balance of liquidity provider tokens from this AMM instance. The holders of these tokens
+ * can vote on the AMM's trading fee in proportion to their holdings, or redeem the tokens for a share of the AMM's
+ * assets which grows with the trading fees collected.
+ *
+ * @return An {@link IssuedCurrencyAmount}.
+ */
+ @JsonProperty("lp_token")
+ IssuedCurrencyAmount lpToken();
+
+ /**
+ * The percentage fee to be charged for trades against this AMM instance, in units of 1/10,000. The maximum value is
+ * 1000, for a 1% fee.
+ *
+ * @return A {@link TradingFee}.
+ */
+ @JsonProperty("trading_fee")
+ TradingFee tradingFee();
+
+ /**
+ * A list of vote objects, representing votes on the pool's trading fee.
+ *
+ * @return A {@link List} of {@link AmmInfoVoteEntry}s.
+ */
+ @JsonProperty("vote_slots")
+ List voteSlots();
+
+}
diff --git a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/client/amm/AmmInfoAuctionSlot.java b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/client/amm/AmmInfoAuctionSlot.java
new file mode 100644
index 000000000..5583a68e1
--- /dev/null
+++ b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/client/amm/AmmInfoAuctionSlot.java
@@ -0,0 +1,91 @@
+package org.xrpl.xrpl4j.model.client.amm;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import com.google.common.annotations.Beta;
+import com.google.common.primitives.UnsignedInteger;
+import org.immutables.value.Value;
+import org.xrpl.xrpl4j.model.transactions.Address;
+import org.xrpl.xrpl4j.model.transactions.IssuedCurrencyAmount;
+import org.xrpl.xrpl4j.model.transactions.TradingFee;
+
+import java.time.ZonedDateTime;
+import java.util.List;
+
+/**
+ * Object mapping for an AMM auction slot returned in response to an {@code amm_info} RPC call. The structure
+ * of the response object is similar but has a slightly different format from
+ * {@link org.xrpl.xrpl4j.model.ledger.AuctionSlot}.
+ *
+ * This class will be marked {@link Beta} until the AMM amendment is enabled on mainnet. Its API is subject to
+ * change.
+ */
+@Value.Immutable
+@JsonSerialize(as = ImmutableAmmInfoAuctionSlot.class)
+@JsonDeserialize(as = ImmutableAmmInfoAuctionSlot.class)
+@Beta
+public interface AmmInfoAuctionSlot {
+
+ /**
+ * Construct a {@code AmmInfoAuctionSlot} builder.
+ *
+ * @return An {@link ImmutableAmmInfoAuctionSlot.Builder}.
+ */
+ static ImmutableAmmInfoAuctionSlot.Builder builder() {
+ return ImmutableAmmInfoAuctionSlot.builder();
+ }
+
+ /**
+ * The current owner of this auction slot.
+ *
+ * @return An {@link Address}.
+ */
+ @JsonProperty("account")
+ Address account();
+
+ /**
+ * A list of at most 4 additional accounts that are authorized to trade at the discounted fee for this AMM instance.
+ *
+ * @return A {@link List} of {@link AmmInfoAuthAccount}s.
+ */
+ @JsonProperty("auth_accounts")
+ List authAccounts();
+
+ /**
+ * The trading fee to be charged to the auction owner. By default this is 0, meaning that the auction owner can trade
+ * at no fee instead of the standard fee for this AMM.
+ *
+ * @return A {@link TradingFee}.
+ */
+ @JsonProperty("discounted_fee")
+ TradingFee discountedFee();
+
+ /**
+ * The time when this slot expires, as a {@link ZonedDateTime}.
+ *
+ * @return An {@link ZonedDateTime}
+ */
+ @JsonProperty("expiration")
+ @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ssZ", locale = "en_US")
+ ZonedDateTime expiration();
+
+ /**
+ * The amount the auction owner paid to win this slot, in LP Tokens.
+ *
+ * @return An {@link IssuedCurrencyAmount}.
+ */
+ @JsonProperty("price")
+ IssuedCurrencyAmount price();
+
+ /**
+ * An {@link UnsignedInteger} between 1 and 20 denoting the time slot used for the continuous auction slot pricing
+ * mechanism of the AMM.
+ *
+ * @return An {@link UnsignedInteger}.
+ */
+ @JsonProperty("time_interval")
+ UnsignedInteger timeInterval();
+
+}
diff --git a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/client/amm/AmmInfoAuthAccount.java b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/client/amm/AmmInfoAuthAccount.java
new file mode 100644
index 000000000..6df3907dc
--- /dev/null
+++ b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/client/amm/AmmInfoAuthAccount.java
@@ -0,0 +1,50 @@
+package org.xrpl.xrpl4j.model.client.amm;
+
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import com.google.common.annotations.Beta;
+import org.immutables.value.Value;
+import org.xrpl.xrpl4j.model.transactions.Address;
+
+/**
+ * An account that is authorized to trade at the discounted fee for an AMM instance.
+ *
+ * This class will be marked {@link Beta} until the AMM amendment is enabled on mainnet. Its API is subject to
+ * change.
+ */
+@Value.Immutable
+@JsonSerialize(as = ImmutableAmmInfoAuthAccount.class)
+@JsonDeserialize(as = ImmutableAmmInfoAuthAccount.class)
+@Beta
+public interface AmmInfoAuthAccount {
+
+ /**
+ * Construct a {@code AmmInfoAuthAccount} builder.
+ *
+ * @return An {@link ImmutableAmmInfoAuthAccount.Builder}.
+ */
+ static ImmutableAmmInfoAuthAccount.Builder builder() {
+ return ImmutableAmmInfoAuthAccount.builder();
+ }
+
+ /**
+ * Construct an {@link AmmInfoAuthAccount} containing the given {@link Address}.
+ *
+ * @param account An {@link Address}.
+ *
+ * @return An {@link AmmInfoAuthAccount} containing the address.
+ */
+ static AmmInfoAuthAccount of(Address account) {
+ return builder()
+ .account(account)
+ .build();
+ }
+
+ /**
+ * The address of the authorized account.
+ *
+ * @return An {@link Address}.
+ */
+ Address account();
+
+}
diff --git a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/client/amm/AmmInfoRequestParams.java b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/client/amm/AmmInfoRequestParams.java
new file mode 100644
index 000000000..622af735e
--- /dev/null
+++ b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/client/amm/AmmInfoRequestParams.java
@@ -0,0 +1,82 @@
+package org.xrpl.xrpl4j.model.client.amm;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import com.google.common.annotations.Beta;
+import org.immutables.value.Value;
+import org.xrpl.xrpl4j.model.client.XrplRequestParams;
+import org.xrpl.xrpl4j.model.ledger.Issue;
+import org.xrpl.xrpl4j.model.transactions.Address;
+
+import java.util.Optional;
+
+/**
+ * Request parameters for the {@code amm_info} rippled API method.
+ *
+ * This class will be marked {@link Beta} until the AMM amendment is enabled on mainnet. Its API is subject to
+ * change.
+ */
+@Value.Immutable
+@JsonSerialize(as = ImmutableAmmInfoRequestParams.class)
+@JsonDeserialize(as = ImmutableAmmInfoRequestParams.class)
+@Beta
+public interface AmmInfoRequestParams extends XrplRequestParams {
+
+ /**
+ * Construct a new {@link AmmInfoRequestParams} that specifies the AMM account to query.
+ *
+ * @param ammAccount The {@link Address} of the AMM account.
+ *
+ * @return An {@link AmmInfoRequestParams}.
+ */
+ static AmmInfoRequestParams from(Address ammAccount) {
+ return ImmutableAmmInfoRequestParams.builder()
+ .ammAccount(ammAccount)
+ .build();
+ }
+
+ /**
+ * Construct a new {@link AmmInfoRequestParams} that specifies {@code asset} and {@code asset2}.
+ *
+ * @param asset The first asset of the AMM, as an {@link Issue}.
+ * @param asset2 The second asset of the AMM, as an {@link Issue}.
+ *
+ * @return An {@link AmmInfoRequestParams}.
+ */
+ static AmmInfoRequestParams from(Issue asset, Issue asset2) {
+ return ImmutableAmmInfoRequestParams.builder()
+ .asset(asset)
+ .asset2(asset2)
+ .build();
+ }
+
+ /**
+ * The address of the AMM's special AccountRoot. (This is the issuer of the AMM's LP Tokens).
+ *
+ * If this field is specified, {@link #asset()} and {@link #asset2()} must be empty.
+ *
+ * @return An {@link Optional} {@link Address}.
+ */
+ @JsonProperty("amm_account")
+ Optional ammAccount();
+
+ /**
+ * One of the assets of the AMM to look up.
+ *
+ * If this field is specified, {@link #asset2()} must be present, and {@link #ammAccount()} must be empty.
+ *
+ * @return An {@link Issue}.
+ */
+ Optional asset();
+
+ /**
+ * The other of the assets of the AMM.
+ *
+ * If this field is specified, {@link #asset()} must be present, and {@link #ammAccount()} must be empty.
+ *
+ * @return An {@link Issue}.
+ */
+ Optional asset2();
+
+}
diff --git a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/client/amm/AmmInfoResult.java b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/client/amm/AmmInfoResult.java
new file mode 100644
index 000000000..b7edbfd40
--- /dev/null
+++ b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/client/amm/AmmInfoResult.java
@@ -0,0 +1,119 @@
+package org.xrpl.xrpl4j.model.client.amm;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import com.google.common.annotations.Beta;
+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.transactions.Hash256;
+
+import java.util.Optional;
+
+/**
+ * The result of an "amm_info" rippled API method call.
+ *
+ * This class will be marked {@link Beta} until the AMM amendment is enabled on mainnet. Its API is subject to
+ * change.
+ */
+@Value.Immutable
+@JsonSerialize(as = ImmutableAmmInfoResult.class)
+@JsonDeserialize(as = ImmutableAmmInfoResult.class)
+@Beta
+public interface AmmInfoResult extends XrplResult {
+
+ /**
+ * Construct a {@code AmmInfoResult} builder.
+ *
+ * @return An {@link ImmutableAmmInfoResult.Builder}.
+ */
+ static ImmutableAmmInfoResult.Builder builder() {
+ return ImmutableAmmInfoResult.builder();
+ }
+
+ /**
+ * The AMM ledger object.
+ *
+ * @return An {@link AmmInfoResult}.
+ */
+ @JsonProperty("amm")
+ AmmInfo amm();
+
+ /**
+ * (Omitted if ledger_current_index is provided instead) The ledger index of the ledger version used when
+ * retrieving this information. The information does not contain any changes from ledger versions newer than this one.
+ *
+ * @return An optionally-present {@link LedgerIndex}.
+ */
+ @JsonProperty("ledger_index")
+ Optional ledgerIndex();
+
+ /**
+ * Get {@link #ledgerIndex()}, or throw an {@link IllegalStateException} if {@link #ledgerIndex()} is empty.
+ *
+ * @return The value of {@link #ledgerIndex()}.
+ * @throws IllegalStateException If {@link #ledgerIndex()} is empty.
+ */
+ @JsonIgnore
+ @Value.Auxiliary
+ default LedgerIndex ledgerIndexSafe() {
+ return ledgerIndex()
+ .orElseThrow(() -> new IllegalStateException("Result did not contain a ledgerIndex."));
+ }
+
+ /**
+ * (Omitted if ledger_index is provided instead) The ledger index of the current in-progress ledger,
+ * which was used when retrieving this information.
+ *
+ * @return An optionally-present {@link LedgerIndex}.
+ */
+ @JsonProperty("ledger_current_index")
+ Optional ledgerCurrentIndex();
+
+ /**
+ * Get {@link #ledgerCurrentIndex()}, or throw an {@link IllegalStateException} if {@link #ledgerCurrentIndex()} is
+ * empty.
+ *
+ * @return The value of {@link #ledgerCurrentIndex()}.
+ * @throws IllegalStateException If {@link #ledgerCurrentIndex()} is empty.
+ */
+ @JsonIgnore
+ @Value.Auxiliary
+ default LedgerIndex ledgerCurrentIndexSafe() {
+ return ledgerCurrentIndex()
+ .orElseThrow(() -> new IllegalStateException("Result did not contain a ledgerCurrentIndex."));
+ }
+
+ /**
+ * True if this data is from a validated ledger version; if false, this data is not final.
+ *
+ * @return {@code true} if this data is from a validated ledger version, otherwise {@code false}.
+ */
+ @Value.Default
+ default boolean validated() {
+ return false;
+ }
+
+ /**
+ * The identifying Hash of the ledger version used to generate this response.
+ *
+ * @return A {@link Hash256} containing the ledger hash.
+ */
+ @JsonProperty("ledger_hash")
+ Optional ledgerHash();
+
+ /**
+ * Get {@link #ledgerHash()}, or throw an {@link IllegalStateException} if {@link #ledgerHash()} is empty.
+ *
+ * @return The value of {@link #ledgerHash()}.
+ * @throws IllegalStateException If {@link #ledgerHash()} is empty.
+ */
+ @JsonIgnore
+ @Value.Auxiliary
+ default Hash256 ledgerHashSafe() {
+ return ledgerHash()
+ .orElseThrow(() -> new IllegalStateException("Result did not contain a ledgerHash."));
+ }
+}
diff --git a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/client/amm/AmmInfoVoteEntry.java b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/client/amm/AmmInfoVoteEntry.java
new file mode 100644
index 000000000..020de79e8
--- /dev/null
+++ b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/client/amm/AmmInfoVoteEntry.java
@@ -0,0 +1,58 @@
+package org.xrpl.xrpl4j.model.client.amm;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import com.google.common.annotations.Beta;
+import org.immutables.value.Value;
+import org.xrpl.xrpl4j.model.ledger.ImmutableVoteEntry;
+import org.xrpl.xrpl4j.model.transactions.Address;
+import org.xrpl.xrpl4j.model.transactions.TradingFee;
+import org.xrpl.xrpl4j.model.transactions.VoteWeight;
+
+/**
+ * Describes a vote for the trading fee on an AMM by an LP.
+ *
+ * This class will be marked {@link Beta} until the AMM amendment is enabled on mainnet. Its API is subject to
+ * change.
+ */
+@Value.Immutable
+@JsonSerialize(as = ImmutableAmmInfoVoteEntry.class)
+@JsonDeserialize(as = ImmutableAmmInfoVoteEntry.class)
+@Beta
+public interface AmmInfoVoteEntry {
+
+ /**
+ * Construct a {@code AmmInfoVoteEntry} builder.
+ *
+ * @return An {@link ImmutableAmmInfoVoteEntry.Builder}.
+ */
+ static ImmutableAmmInfoVoteEntry.Builder builder() {
+ return ImmutableAmmInfoVoteEntry.builder();
+ }
+
+ /**
+ * The address of the LP who voted.
+ *
+ * @return An {@link Address}.
+ */
+ @JsonProperty("account")
+ Address account();
+
+ /**
+ * The trading fee that the LP voted for.
+ *
+ * @return A {@link TradingFee}.
+ */
+ @JsonProperty("trading_fee")
+ TradingFee tradingFee();
+
+ /**
+ * The weight of the LP's vote.
+ *
+ * @return The {@link VoteWeight}.
+ */
+ @JsonProperty("vote_weight")
+ VoteWeight voteWeight();
+
+}
diff --git a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/client/ledger/AmmLedgerEntryParams.java b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/client/ledger/AmmLedgerEntryParams.java
new file mode 100644
index 000000000..e9accab4e
--- /dev/null
+++ b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/client/ledger/AmmLedgerEntryParams.java
@@ -0,0 +1,40 @@
+package org.xrpl.xrpl4j.model.client.ledger;
+
+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.ledger.Issue;
+
+/**
+ * Parameters that uniquely identify an {@link org.xrpl.xrpl4j.model.ledger.AmmObject} on ledger that can be used
+ * in a {@link LedgerEntryRequestParams} to request an {@link org.xrpl.xrpl4j.model.ledger.AmmObject}.
+ */
+@Immutable
+@JsonSerialize(as = ImmutableAmmLedgerEntryParams.class)
+@JsonDeserialize(as = ImmutableAmmLedgerEntryParams.class)
+public interface AmmLedgerEntryParams {
+
+ /**
+ * Construct a {@code AmmLedgerEntryParams} builder.
+ *
+ * @return An {@link ImmutableAmmLedgerEntryParams.Builder}.
+ */
+ static ImmutableAmmLedgerEntryParams.Builder builder() {
+ return ImmutableAmmLedgerEntryParams.builder();
+ }
+
+ /**
+ * One of the two assets in the AMM's pool.
+ *
+ * @return An {@link Issue}.
+ */
+ Issue asset();
+
+ /**
+ * The other of the two assets in the AMM's pool.
+ *
+ * @return An {@link Issue}.
+ */
+ Issue asset2();
+
+}
diff --git a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/client/ledger/DepositPreAuthLedgerEntryParams.java b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/client/ledger/DepositPreAuthLedgerEntryParams.java
new file mode 100644
index 000000000..8dde10185
--- /dev/null
+++ b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/client/ledger/DepositPreAuthLedgerEntryParams.java
@@ -0,0 +1,40 @@
+package org.xrpl.xrpl4j.model.client.ledger;
+
+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.transactions.Address;
+
+/**
+ * Parameters that uniquely identify a {@link org.xrpl.xrpl4j.model.ledger.DepositPreAuthObject} on ledger that can be
+ * used in a {@link LedgerEntryRequestParams} to request an {@link org.xrpl.xrpl4j.model.ledger.DepositPreAuthObject}.
+ */
+@Immutable
+@JsonSerialize(as = ImmutableDepositPreAuthLedgerEntryParams.class)
+@JsonDeserialize(as = ImmutableDepositPreAuthLedgerEntryParams.class)
+public interface DepositPreAuthLedgerEntryParams {
+
+ /**
+ * Construct a {@code DepositPreAuthLedgerEntryParams} builder.
+ *
+ * @return An {@link ImmutableDepositPreAuthLedgerEntryParams.Builder}.
+ */
+ static ImmutableDepositPreAuthLedgerEntryParams.Builder builder() {
+ return ImmutableDepositPreAuthLedgerEntryParams.builder();
+ }
+
+ /**
+ * The {@link Address} of the account that provided the preauthorization.
+ *
+ * @return An {@link Address}.
+ */
+ Address owner();
+
+ /**
+ * The {@link Address} of the account that received the preauthorization.
+ *
+ * @return An {@link Address}.
+ */
+ Address authorized();
+
+}
diff --git a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/client/ledger/EscrowLedgerEntryParams.java b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/client/ledger/EscrowLedgerEntryParams.java
new file mode 100644
index 000000000..1c015e654
--- /dev/null
+++ b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/client/ledger/EscrowLedgerEntryParams.java
@@ -0,0 +1,41 @@
+package org.xrpl.xrpl4j.model.client.ledger;
+
+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.Immutable;
+import org.xrpl.xrpl4j.model.transactions.Address;
+
+/**
+ * Parameters that uniquely identify an {@link org.xrpl.xrpl4j.model.ledger.EscrowObject} on ledger that can be used in
+ * a {@link LedgerEntryRequestParams} to request an {@link org.xrpl.xrpl4j.model.ledger.EscrowObject}.
+ */
+@Immutable
+@JsonSerialize(as = ImmutableEscrowLedgerEntryParams.class)
+@JsonDeserialize(as = ImmutableEscrowLedgerEntryParams.class)
+public interface EscrowLedgerEntryParams {
+
+ /**
+ * Construct a {@code EscrowLedgerEntryParams} builder.
+ *
+ * @return An {@link ImmutableEscrowLedgerEntryParams.Builder}.
+ */
+ static ImmutableEscrowLedgerEntryParams.Builder builder() {
+ return ImmutableEscrowLedgerEntryParams.builder();
+ }
+
+ /**
+ * The owner (sender) of the Escrow object.
+ *
+ * @return The {@link Address} of the owner.
+ */
+ Address owner();
+
+ /**
+ * The Sequence Number of the transaction that created the Escrow object.
+ *
+ * @return An {@link UnsignedInteger}.
+ */
+ UnsignedInteger seq();
+
+}
diff --git a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/client/ledger/LedgerEntryRequestParams.java b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/client/ledger/LedgerEntryRequestParams.java
new file mode 100644
index 000000000..9a74f558c
--- /dev/null
+++ b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/client/ledger/LedgerEntryRequestParams.java
@@ -0,0 +1,456 @@
+package org.xrpl.xrpl4j.model.client.ledger;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+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;
+import org.immutables.value.Value.Style.BuilderVisibility;
+import org.xrpl.xrpl4j.model.client.XrplRequestParams;
+import org.xrpl.xrpl4j.model.client.common.LedgerSpecifier;
+import org.xrpl.xrpl4j.model.ledger.AccountRootObject;
+import org.xrpl.xrpl4j.model.ledger.AmmObject;
+import org.xrpl.xrpl4j.model.ledger.CheckObject;
+import org.xrpl.xrpl4j.model.ledger.DepositPreAuthObject;
+import org.xrpl.xrpl4j.model.ledger.EscrowObject;
+import org.xrpl.xrpl4j.model.ledger.LedgerObject;
+import org.xrpl.xrpl4j.model.ledger.NfTokenPageObject;
+import org.xrpl.xrpl4j.model.ledger.OfferObject;
+import org.xrpl.xrpl4j.model.ledger.PayChannelObject;
+import org.xrpl.xrpl4j.model.ledger.RippleStateObject;
+import org.xrpl.xrpl4j.model.ledger.TicketObject;
+import org.xrpl.xrpl4j.model.transactions.Address;
+import org.xrpl.xrpl4j.model.transactions.Hash256;
+
+import java.util.Optional;
+
+/**
+ * Request parameters for the {@code ledger_entry} RPC.
+ *
+ * Unlike most other immutable objects in this library, this class's builder is not accessible to developers.
+ * Instead, developers should construct instances of {@link LedgerEntryRequestParams} via its various static
+ * constructors.
+ *
+ * Each static constructor constructs {@link LedgerEntryRequestParams} for a particular type of ledger entry
+ * as described on xrpl.org.
+ *
+ * @param The type of {@link LedgerObject} that will be returned in the response to the {@code ledger_entry} RPC
+ * call with these {@link LedgerEntryRequestParams}.
+ */
+@Value.Immutable
+// Note: These parameters should only be constructed via the provided static constructors. Exposing the builder to
+// developers allows them to specify multiple types of ledger_entry request, which is unsafe to do.
+@Value.Style(builderVisibility = BuilderVisibility.PACKAGE)
+@JsonSerialize(as = ImmutableLedgerEntryRequestParams.class)
+@JsonDeserialize(as = ImmutableLedgerEntryRequestParams.class)
+@SuppressWarnings("OverloadMethodsDeclarationOrder")
+public interface LedgerEntryRequestParams extends XrplRequestParams {
+
+ /**
+ * Construct a {@link LedgerEntryRequestParams} that requests a ledger entry by index.
+ *
+ * @param id The index or ID of the ledger entry as a {@link Hash256}.
+ * @param ledgerSpecifier A {@link LedgerSpecifier} indicating the ledger to query data from.
+ * @param ledgerObjectClass The class of {@link LedgerObject} that should be returned by rippled as a {@link Class} of
+ * {@link T}.
+ * @param The actual type of {@link LedgerObject} that should be returned by rippled.
+ *
+ * @return A {@link LedgerEntryRequestParams} for {@link T}.
+ */
+ static LedgerEntryRequestParams index(
+ Hash256 id,
+ Class ledgerObjectClass,
+ LedgerSpecifier ledgerSpecifier
+ ) {
+ return ImmutableLedgerEntryRequestParams.builder()
+ .index(id)
+ .ledgerObjectClass(ledgerObjectClass)
+ .ledgerSpecifier(ledgerSpecifier)
+ .build();
+ }
+
+ /**
+ * Construct a {@link LedgerEntryRequestParams} that requests a ledger entry by index but does not narrow down the
+ * polymorphic type of {@link LedgerObject} that is returned. These parameters are useful when querying a ledger entry
+ * by ID that the developer does not know the type of at compile time.
+ *
+ * @param id The index or ID of the ledger entry as a {@link Hash256}.
+ * @param ledgerSpecifier A {@link LedgerSpecifier} indicating the ledger to query data from.
+ *
+ * @return A {@link LedgerEntryRequestParams} for {@link LedgerObject}.
+ */
+ static LedgerEntryRequestParams index(
+ Hash256 id,
+ LedgerSpecifier ledgerSpecifier
+ ) {
+ return ImmutableLedgerEntryRequestParams.builder()
+ .index(id)
+ .ledgerSpecifier(ledgerSpecifier)
+ .build();
+ }
+
+ /**
+ * Construct a {@link LedgerEntryRequestParams} that requests an {@link AccountRootObject} ledger entry by address.
+ *
+ * @param address The {@link Address} of the account who owns the {@link AccountRootObject}.
+ * @param ledgerSpecifier A {@link LedgerSpecifier} indicating the ledger to query data from.
+ *
+ * @return A {@link LedgerEntryRequestParams} for {@link AccountRootObject}.
+ */
+ static LedgerEntryRequestParams accountRoot(
+ Address address,
+ LedgerSpecifier ledgerSpecifier
+ ) {
+ return ImmutableLedgerEntryRequestParams.builder()
+ .accountRoot(address)
+ .ledgerSpecifier(ledgerSpecifier)
+ .build();
+ }
+
+ /**
+ * Construct a {@link LedgerEntryRequestParams} that requests an {@link AmmObject} ledger entry.
+ *
+ * Note that although the rippled API allows you to specify either the AMM's ID or its two assets, this
+ * class does not allow developers to request an {@link AmmObject} by ID via this method. Instead, developers should
+ * use {@link LedgerEntryRequestParams#index()} and specify {@link AmmObject} as the {@code ledgerObjectClass}
+ * parameter.
+ *
+ * @param params The {@link AmmLedgerEntryParams} that uniquely identify the {@link AmmObject} on ledger.
+ * @param ledgerSpecifier A {@link LedgerSpecifier} indicating the ledger to query data from.
+ *
+ * @return A {@link LedgerEntryRequestParams} for {@link AmmObject}.
+ */
+ static LedgerEntryRequestParams amm(
+ AmmLedgerEntryParams params,
+ LedgerSpecifier ledgerSpecifier
+ ) {
+ return ImmutableLedgerEntryRequestParams.builder()
+ .amm(params)
+ .ledgerSpecifier(ledgerSpecifier)
+ .build();
+ }
+
+ /**
+ * Construct a {@link LedgerEntryRequestParams} that requests an {@link OfferObject} ledger entry.
+ *
+ * Note that although the rippled API allows you to specify either the Offer's ID or the account that created it
+ * and the sequence number of the transaction that created it, this class does not allow developers to request an
+ * {@link OfferObject} by ID via this method. Instead, developers should use {@link LedgerEntryRequestParams#index()}
+ * and specify {@link OfferObject} as the {@code ledgerObjectClass} parameter.
+ *
+ * @param params The {@link OfferLedgerEntryParams} that uniquely identify the {@link OfferObject} on
+ * ledger.
+ * @param ledgerSpecifier A {@link LedgerSpecifier} indicating the ledger to query data from.
+ *
+ * @return A {@link LedgerEntryRequestParams} for {@link OfferObject}.
+ */
+ static LedgerEntryRequestParams offer(
+ OfferLedgerEntryParams params,
+ LedgerSpecifier ledgerSpecifier
+ ) {
+ return ImmutableLedgerEntryRequestParams.builder()
+ .offer(params)
+ .ledgerSpecifier(ledgerSpecifier)
+ .build();
+ }
+
+ /**
+ * Construct a {@link LedgerEntryRequestParams} that requests a {@link RippleStateObject} ledger entry.
+ *
+ * @param params The {@link RippleStateLedgerEntryParams} that uniquely identify the
+ * {@link RippleStateObject} on ledger.
+ * @param ledgerSpecifier A {@link LedgerSpecifier} indicating the ledger to query data from.
+ *
+ * @return A {@link LedgerEntryRequestParams} for {@link RippleStateObject}.
+ */
+ static LedgerEntryRequestParams rippleState(
+ RippleStateLedgerEntryParams params,
+ LedgerSpecifier ledgerSpecifier
+ ) {
+ return ImmutableLedgerEntryRequestParams.builder()
+ .rippleState(params)
+ .ledgerSpecifier(ledgerSpecifier)
+ .build();
+ }
+
+ /**
+ * Construct a {@link LedgerEntryRequestParams} that requests a {@link CheckObject} ledger entry.
+ *
+ * @param id The index or ID of the {@link CheckObject}.
+ * @param ledgerSpecifier A {@link LedgerSpecifier} indicating the ledger to query data from.
+ *
+ * @return A {@link LedgerEntryRequestParams} for {@link RippleStateObject}.
+ */
+ static LedgerEntryRequestParams check(
+ Hash256 id,
+ LedgerSpecifier ledgerSpecifier
+ ) {
+ return ImmutableLedgerEntryRequestParams.builder()
+ .check(id)
+ .ledgerSpecifier(ledgerSpecifier)
+ .build();
+ }
+
+ /**
+ * Construct a {@link LedgerEntryRequestParams} that requests a {@link EscrowObject} ledger entry.
+ *
+ * Note that although the rippled API allows you to specify either the Escrow's ID or the owner and sequence
+ * number of the transaction that created the Escrow, this class does not allow developers to request an
+ * {@link EscrowObject} by ID via this method. Instead, developers should use {@link LedgerEntryRequestParams#index()}
+ * and specify {@link EscrowObject} as the {@code ledgerObjectClass} parameter.
+ *
+ * @param params The {@link EscrowLedgerEntryParams} that uniquely identify the {@link EscrowObject} on
+ * ledger.
+ * @param ledgerSpecifier A {@link LedgerSpecifier} indicating the ledger to query data from.
+ *
+ * @return A {@link LedgerEntryRequestParams} for {@link EscrowObject}.
+ */
+ static LedgerEntryRequestParams escrow(
+ EscrowLedgerEntryParams params,
+ LedgerSpecifier ledgerSpecifier
+ ) {
+ return ImmutableLedgerEntryRequestParams.builder()
+ .escrow(params)
+ .ledgerSpecifier(ledgerSpecifier)
+ .build();
+ }
+
+ /**
+ * Construct a {@link LedgerEntryRequestParams} that requests a {@link PayChannelObject} ledger entry.
+ *
+ * @param id The index or ID of the {@link PayChannelObject}.
+ * @param ledgerSpecifier A {@link LedgerSpecifier} indicating the ledger to query data from.
+ *
+ * @return A {@link LedgerEntryRequestParams} for {@link PayChannelObject}.
+ */
+ static LedgerEntryRequestParams paymentChannel(
+ Hash256 id,
+ LedgerSpecifier ledgerSpecifier
+ ) {
+ return ImmutableLedgerEntryRequestParams.builder()
+ .paymentChannel(id)
+ .ledgerSpecifier(ledgerSpecifier)
+ .build();
+ }
+
+ /**
+ * Construct a {@link LedgerEntryRequestParams} that requests a {@link DepositPreAuthObject} ledger entry.
+ *
+ * Note that although the rippled API allows you to specify either the DepositPreAuth's ID or the owner and the
+ * account that is authorized, this class does not allow developers to request an {@link DepositPreAuthObject} by ID
+ * via this method. Instead, developers should use {@link LedgerEntryRequestParams#index()} and specify
+ * {@link DepositPreAuthObject} as the {@code ledgerObjectClass} parameter.
+ *
+ * @param params The {@link DepositPreAuthLedgerEntryParams} that uniquely identify the
+ * {@link DepositPreAuthObject} on ledger.
+ * @param ledgerSpecifier A {@link LedgerSpecifier} indicating the ledger to query data from.
+ *
+ * @return A {@link LedgerEntryRequestParams} for {@link DepositPreAuthObject}.
+ */
+ static LedgerEntryRequestParams depositPreAuth(
+ DepositPreAuthLedgerEntryParams params,
+ LedgerSpecifier ledgerSpecifier
+ ) {
+ return ImmutableLedgerEntryRequestParams.builder()
+ .depositPreAuth(params)
+ .ledgerSpecifier(ledgerSpecifier)
+ .build();
+ }
+
+ /**
+ * Construct a {@link LedgerEntryRequestParams} that requests a {@link TicketObject} ledger entry.
+ *
+ * Note that although the rippled API allows you to specify either the Ticket's ID or the owner of the Ticket and
+ * the Ticket's sequence, this class does not allow developers to request an {@link TicketObject} by ID via this
+ * method. Instead, developers should use {@link LedgerEntryRequestParams#index()} and specify {@link TicketObject} as
+ * the {@code ledgerObjectClass} parameter.
+ *
+ * @param params The {@link TicketLedgerEntryParams} that uniquely identify the {@link TicketObject} on
+ * ledger.
+ * @param ledgerSpecifier A {@link LedgerSpecifier} indicating the ledger to query data from.
+ *
+ * @return A {@link LedgerEntryRequestParams} for {@link TicketObject}.
+ */
+ static LedgerEntryRequestParams ticket(
+ TicketLedgerEntryParams params,
+ LedgerSpecifier ledgerSpecifier
+ ) {
+ return ImmutableLedgerEntryRequestParams.builder()
+ .ticket(params)
+ .ledgerSpecifier(ledgerSpecifier)
+ .build();
+ }
+
+ /**
+ * Construct a {@link LedgerEntryRequestParams} that requests a {@link NfTokenPageObject} ledger entry.
+ *
+ * @param id The index or ID of the {@link NfTokenPageObject}.
+ * @param ledgerSpecifier A {@link LedgerSpecifier} indicating the ledger to query data from.
+ *
+ * @return A {@link LedgerEntryRequestParams} for {@link NfTokenPageObject}.
+ */
+ static LedgerEntryRequestParams nftPage(
+ Hash256 id,
+ LedgerSpecifier ledgerSpecifier
+ ) {
+ return ImmutableLedgerEntryRequestParams.builder()
+ .nftPage(id)
+ .ledgerSpecifier(ledgerSpecifier)
+ .build();
+ }
+
+ /**
+ * 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
+ LedgerSpecifier ledgerSpecifier();
+
+ /**
+ * If true, return the requested ledger entry's contents as a hex string in the XRP Ledger's binary format. Otherwise,
+ * return data in JSON format. This field will always be {@code false}.
+ *
+ * @return A boolean.
+ */
+ @Value.Derived
+ default boolean binary() {
+ return false;
+ }
+
+ /**
+ * Look up a ledger entry by ID/index.
+ *
+ * @return An optionally-present {@link Hash256}.
+ */
+ Optional index();
+
+ /**
+ * Look up an {@link org.xrpl.xrpl4j.model.ledger.AccountRootObject} by {@link Address}.
+ *
+ * @return An optionally-present {@link Address}.
+ */
+ @JsonProperty("account_root")
+ Optional accountRoot();
+
+ /**
+ * Look up an {@link org.xrpl.xrpl4j.model.ledger.AmmObject} by {@link AmmLedgerEntryParams}.
+ *
+ * @return An optionally-present {@link AmmLedgerEntryParams}.
+ */
+ Optional amm();
+
+ /**
+ * Look up an {@link org.xrpl.xrpl4j.model.ledger.OfferObject} by {@link OfferLedgerEntryParams}.
+ *
+ * @return An optionally-present {@link OfferLedgerEntryParams}.
+ */
+ Optional offer();
+
+ /**
+ * Look up a {@link org.xrpl.xrpl4j.model.ledger.RippleStateObject} by {@link RippleStateLedgerEntryParams}.
+ *
+ * @return An optionally-present {@link RippleStateLedgerEntryParams}.
+ */
+ @JsonProperty("ripple_state")
+ Optional rippleState();
+
+ /**
+ * Look up a {@link org.xrpl.xrpl4j.model.ledger.CheckObject} by ID.
+ *
+ * @return An optionally-present {@link Hash256}.
+ */
+ Optional check();
+
+ /**
+ * Look up an {@link org.xrpl.xrpl4j.model.ledger.EscrowObject} by {@link EscrowLedgerEntryParams}.
+ *
+ * @return An optionally-present {@link EscrowLedgerEntryParams}.
+ */
+ Optional escrow();
+
+ /**
+ * Look up a {@link org.xrpl.xrpl4j.model.ledger.PayChannelObject} by ID.
+ *
+ * @return An optionally-present {@link Hash256}.
+ */
+ @JsonProperty("payment_channel")
+ Optional paymentChannel();
+
+ /**
+ * Look up an {@link org.xrpl.xrpl4j.model.ledger.NfTokenPageObject} by ID.
+ *
+ * @return An optionally-present {@link Hash256}.
+ */
+ @JsonProperty("nft_page")
+ Optional nftPage();
+
+ /**
+ * Look up a {@link org.xrpl.xrpl4j.model.ledger.DepositPreAuthObject} by {@link DepositPreAuthLedgerEntryParams}.
+ *
+ * @return An optionally-present {@link DepositPreAuthLedgerEntryParams}.
+ */
+ @JsonProperty("deposit_preauth")
+ Optional depositPreAuth();
+
+ /**
+ * Look up a {@link org.xrpl.xrpl4j.model.ledger.TicketObject} by {@link TicketLedgerEntryParams}.
+ *
+ * @return An optionally-present {@link TicketLedgerEntryParams}.
+ */
+ Optional ticket();
+
+ /**
+ * The {@link Class} of {@link T}. This field is helpful when telling Jackson how to deserialize rippled's response to
+ * a {@link T}.
+ *
+ * @return A {@link Class} of type {@link T}.
+ */
+ @JsonIgnore
+ @Value.Default
+ default Class ledgerObjectClass() {
+ if (accountRoot().isPresent()) {
+ return (Class) AccountRootObject.class;
+ }
+
+ if (amm().isPresent()) {
+ return (Class) AmmObject.class;
+ }
+
+ if (offer().isPresent()) {
+ return (Class) OfferObject.class;
+ }
+
+ if (rippleState().isPresent()) {
+ return (Class) RippleStateObject.class;
+ }
+
+ if (check().isPresent()) {
+ return (Class) CheckObject.class;
+ }
+
+ if (escrow().isPresent()) {
+ return (Class) EscrowObject.class;
+ }
+
+ if (paymentChannel().isPresent()) {
+ return (Class) PayChannelObject.class;
+ }
+
+ if (nftPage().isPresent()) {
+ return (Class) NfTokenPageObject.class;
+ }
+
+ if (depositPreAuth().isPresent()) {
+ return (Class) DepositPreAuthObject.class;
+ }
+
+ if (ticket().isPresent()) {
+ return (Class) TicketObject.class;
+ }
+
+ return (Class) LedgerObject.class;
+ }
+}
diff --git a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/client/ledger/LedgerEntryResult.java b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/client/ledger/LedgerEntryResult.java
new file mode 100644
index 000000000..49d71c663
--- /dev/null
+++ b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/client/ledger/LedgerEntryResult.java
@@ -0,0 +1,126 @@
+package org.xrpl.xrpl4j.model.client.ledger;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import org.immutables.value.Value;
+import org.immutables.value.Value.Immutable;
+import org.xrpl.xrpl4j.model.client.XrplResult;
+import org.xrpl.xrpl4j.model.client.common.LedgerIndex;
+import org.xrpl.xrpl4j.model.ledger.LedgerObject;
+import org.xrpl.xrpl4j.model.transactions.Hash256;
+
+import java.util.Optional;
+
+/**
+ * The result of a {@code ledger_entry} RPC call.
+ *
+ * @param The type of {@link LedgerObject} contained in the result.
+ */
+@Immutable
+@JsonSerialize(as = ImmutableLedgerEntryResult.class)
+@JsonDeserialize(as = ImmutableLedgerEntryResult.class)
+public interface LedgerEntryResult extends XrplResult {
+
+ /**
+ * Construct a {@code LedgerEntryResult} builder.
+ *
+ * @return An {@link ImmutableLedgerEntryResult.Builder}.
+ */
+ static ImmutableLedgerEntryResult.Builder builder() {
+ return ImmutableLedgerEntryResult.builder();
+ }
+
+ /**
+ * The ledger entry returned, as a {@link T}.
+ *
+ * @return A {@link T}.
+ */
+ T node();
+
+ /**
+ * Unique identifying hash of the entire ledger.
+ *
+ * @return A {@link Hash256} containing the ledger hash.
+ */
+ @JsonProperty("ledger_hash")
+ Optional ledgerHash();
+
+ /**
+ * Get {@link #ledgerHash()}, or throw an {@link IllegalStateException} if {@link #ledgerHash()} is empty.
+ *
+ * @return The value of {@link #ledgerHash()}.
+ *
+ * @throws IllegalStateException If {@link #ledgerHash()} is empty.
+ */
+ @JsonIgnore
+ @Value.Auxiliary
+ default Hash256 ledgerHashSafe() {
+ return ledgerHash()
+ .orElseThrow(() -> new IllegalStateException("Result did not contain a ledgerHash."));
+ }
+
+ /**
+ * The {@link LedgerIndex} of this ledger.
+ *
+ * @return The {@link LedgerIndex} of this ledger.
+ */
+ @JsonProperty("ledger_index")
+ Optional ledgerIndex();
+
+ /**
+ * Get {@link #ledgerIndex()}, or throw an {@link IllegalStateException} if {@link #ledgerIndex()} is empty.
+ *
+ * @return The value of {@link #ledgerIndex()}.
+ *
+ * @throws IllegalStateException If {@link #ledgerIndex()} is empty.
+ */
+ @JsonIgnore
+ @Value.Auxiliary
+ default LedgerIndex ledgerIndexSafe() {
+ return ledgerIndex()
+ .orElseThrow(() -> new IllegalStateException("Result did not contain a ledgerIndex."));
+ }
+
+ /**
+ * The {@link LedgerIndex} of this ledger, if the ledger is the current ledger. Only present on a current ledger
+ * response.
+ *
+ * @return A {@link LedgerIndex} if this result is for the current ledger, otherwise {@link Optional#empty()}.
+ */
+ @JsonProperty("ledger_current_index")
+ Optional ledgerCurrentIndex();
+
+ /**
+ * Get {@link #ledgerCurrentIndex()}, or throw an {@link IllegalStateException} if {@link #ledgerCurrentIndex()} is
+ * empty.
+ *
+ * @return The value of {@link #ledgerCurrentIndex()}.
+ *
+ * @throws IllegalStateException If {@link #ledgerCurrentIndex()} is empty.
+ */
+ @JsonIgnore
+ @Value.Auxiliary
+ default LedgerIndex ledgerCurrentIndexSafe() {
+ return ledgerCurrentIndex()
+ .orElseThrow(() -> new IllegalStateException("Result did not contain a ledgerCurrentIndex."));
+ }
+
+ /**
+ * True if this data is from a validated ledger version; if false, this data is not final.
+ *
+ * @return {@code true} if this data is from a validated ledger version, otherwise {@code false}.
+ */
+ @Value.Default
+ default boolean validated() {
+ return false;
+ }
+
+ /**
+ * The ID of the ledger entry returned.
+ *
+ * @return The {@link Hash256} representing the ID of the ledger entry.
+ */
+ Hash256 index();
+}
diff --git a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/client/ledger/OfferLedgerEntryParams.java b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/client/ledger/OfferLedgerEntryParams.java
new file mode 100644
index 000000000..8227de2ba
--- /dev/null
+++ b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/client/ledger/OfferLedgerEntryParams.java
@@ -0,0 +1,41 @@
+package org.xrpl.xrpl4j.model.client.ledger;
+
+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.Immutable;
+import org.xrpl.xrpl4j.model.transactions.Address;
+
+/**
+ * Parameters that uniquely identify an {@link org.xrpl.xrpl4j.model.ledger.OfferObject} on ledger that can be used in a
+ * {@link LedgerEntryRequestParams} to request an {@link org.xrpl.xrpl4j.model.ledger.OfferObject}.
+ */
+@Immutable
+@JsonSerialize(as = ImmutableOfferLedgerEntryParams.class)
+@JsonDeserialize(as = ImmutableOfferLedgerEntryParams.class)
+public interface OfferLedgerEntryParams {
+
+ /**
+ * Construct a {@code OfferLedgerEntryParams} builder.
+ *
+ * @return An {@link ImmutableOfferLedgerEntryParams.Builder}.
+ */
+ static ImmutableOfferLedgerEntryParams.Builder builder() {
+ return ImmutableOfferLedgerEntryParams.builder();
+ }
+
+ /**
+ * The account that placed the offer.
+ *
+ * @return The {@link Address} of the account.
+ */
+ Address account();
+
+ /**
+ * The Sequence Number of the transaction that created the Offer entry.
+ *
+ * @return An {@link UnsignedInteger}.
+ */
+ UnsignedInteger seq();
+
+}
diff --git a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/client/ledger/RippleStateLedgerEntryParams.java b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/client/ledger/RippleStateLedgerEntryParams.java
new file mode 100644
index 000000000..7824b388d
--- /dev/null
+++ b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/client/ledger/RippleStateLedgerEntryParams.java
@@ -0,0 +1,78 @@
+package org.xrpl.xrpl4j.model.client.ledger;
+
+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.transactions.Address;
+
+import java.util.List;
+
+/**
+ * Parameters that uniquely identify a {@link org.xrpl.xrpl4j.model.ledger.RippleStateObject} on ledger that can be used
+ * in a {@link LedgerEntryRequestParams} to request a {@link org.xrpl.xrpl4j.model.ledger.RippleStateObject}.
+ */
+@Immutable
+@JsonSerialize(as = ImmutableRippleStateLedgerEntryParams.class)
+@JsonDeserialize(as = ImmutableRippleStateLedgerEntryParams.class)
+public interface RippleStateLedgerEntryParams {
+
+ /**
+ * Construct a {@code RippleStateLedgerEntryParams} builder.
+ *
+ * @return An {@link ImmutableRippleStateLedgerEntryParams.Builder}.
+ */
+ static ImmutableRippleStateLedgerEntryParams.Builder builder() {
+ return ImmutableRippleStateLedgerEntryParams.builder();
+ }
+
+ /**
+ * A {@link RippleStateAccounts} containing the two accounts linked by the
+ * {@link org.xrpl.xrpl4j.model.ledger.RippleStateObject}.
+ *
+ * @return A {@link RippleStateAccounts}.
+ */
+ @JsonUnwrapped
+ RippleStateAccounts accounts();
+
+ /**
+ * The currency code of the {@link org.xrpl.xrpl4j.model.ledger.RippleStateObject} to retrieve.
+ *
+ * @return A {@link String}.
+ */
+ String currency();
+
+ /**
+ * Specifies two {@link Address}es of accounts that are linked by a
+ * {@link org.xrpl.xrpl4j.model.ledger.RippleStateObject}.
+ */
+ @Immutable
+ @JsonSerialize(as = ImmutableRippleStateAccounts.class)
+ @JsonDeserialize(as = ImmutableRippleStateAccounts.class)
+ interface RippleStateAccounts {
+
+ /**
+ * Construct a new {@link RippleStateAccounts}.
+ *
+ * @param account The {@link Address} of one of the accounts linked in the object.
+ * @param otherAccount The {@link Address} of the other account linked in the object.
+ *
+ * @return A {@link RippleStateAccounts}.
+ */
+ static RippleStateAccounts of(Address account, Address otherAccount) {
+ return ImmutableRippleStateAccounts.builder()
+ .addAccounts(account, otherAccount)
+ .build();
+ }
+
+ /**
+ * The {@link Address}es of the accounts linked by the {@link org.xrpl.xrpl4j.model.ledger.RippleStateObject}.
+ *
+ * Note that this is typed as a {@link List} so that this object is serialized as a JSON array.
+ *
+ * @return A {@link List} of {@link Address}es.
+ */
+ List accounts();
+
+ }
+}
diff --git a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/client/ledger/TicketLedgerEntryParams.java b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/client/ledger/TicketLedgerEntryParams.java
new file mode 100644
index 000000000..67d29c44b
--- /dev/null
+++ b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/client/ledger/TicketLedgerEntryParams.java
@@ -0,0 +1,43 @@
+package org.xrpl.xrpl4j.model.client.ledger;
+
+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.Immutable;
+import org.xrpl.xrpl4j.model.transactions.Address;
+
+/**
+ * Parameters that uniquely identify a {@link org.xrpl.xrpl4j.model.ledger.TicketObject} on ledger that can be used in a
+ * {@link LedgerEntryRequestParams} to request a {@link org.xrpl.xrpl4j.model.ledger.TicketObject}.
+ */
+@Immutable
+@JsonSerialize(as = ImmutableTicketLedgerEntryParams.class)
+@JsonDeserialize(as = ImmutableTicketLedgerEntryParams.class)
+public interface TicketLedgerEntryParams {
+
+ /**
+ * Construct a {@code TicketLedgerEntryParams} builder.
+ *
+ * @return An {@link ImmutableTicketLedgerEntryParams.Builder}.
+ */
+ static ImmutableTicketLedgerEntryParams.Builder builder() {
+ return ImmutableTicketLedgerEntryParams.builder();
+ }
+
+ /**
+ * The owner of the Ticket.
+ *
+ * @return The {@link Address} of the owner.
+ */
+ Address account();
+
+ /**
+ * The Ticket Sequence number of the Ticket to retrieve.
+ *
+ * @return An {@link UnsignedInteger}.
+ */
+ @JsonProperty("ticket_seq")
+ UnsignedInteger ticketSeq();
+
+}
diff --git a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/flags/AccountRootFlags.java b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/flags/AccountRootFlags.java
index 0df9b28ba..ca841206f 100644
--- a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/flags/AccountRootFlags.java
+++ b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/flags/AccountRootFlags.java
@@ -20,6 +20,7 @@
* =========================LICENSE_END==================================
*/
+import com.google.common.annotations.Beta;
import org.xrpl.xrpl4j.model.transactions.AccountSet;
/**
@@ -97,6 +98,15 @@ public class AccountRootFlags extends Flags {
*/
public static final AccountRootFlags DISALLOW_INCOMING_TRUSTLINE = new AccountRootFlags(0x20000000);
+ /**
+ * Constant {@link AccountRootFlags} for the {@code lsfAllowTrustLineClawback} account flag.
+ *
+ * This constant will be marked {@link Beta} until the Clawback amendment is enabled on mainnet. Its API is subject
+ * to change.
+ */
+ @Beta
+ public static final AccountRootFlags ALLOW_TRUSTLINE_CLAWBACK = new AccountRootFlags(0x80000000L);
+
/**
* Required-args Constructor.
*
@@ -110,6 +120,7 @@ private AccountRootFlags(final long value) {
* Construct {@link AccountRootFlags} with a given value.
*
* @param value The long-number encoded flags value of this {@link AccountRootFlags}.
+ *
* @return New {@link AccountRootFlags}.
*/
public static AccountRootFlags of(long value) {
@@ -235,4 +246,17 @@ public boolean lsfDisallowIncomingPayChan() {
public boolean lsfDisallowIncomingTrustline() {
return this.isSet(AccountRootFlags.DISALLOW_INCOMING_TRUSTLINE);
}
+
+ /**
+ * Allows trustline clawback on this account.
+ *
+ * This constant will be marked {@link Beta} until the Clawback amendment is enabled on mainnet. Its API is subject
+ * to change.
+ *
+ * @return {@code true} if {@code lsfAllowTrustLineClawback} is set, otherwise {@code false}.
+ */
+ @Beta
+ public boolean lsfAllowTrustLineClawback() {
+ return this.isSet(AccountRootFlags.ALLOW_TRUSTLINE_CLAWBACK);
+ }
}
diff --git a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/flags/AmmDepositFlags.java b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/flags/AmmDepositFlags.java
new file mode 100644
index 000000000..570c2b903
--- /dev/null
+++ b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/flags/AmmDepositFlags.java
@@ -0,0 +1,108 @@
+package org.xrpl.xrpl4j.model.flags;
+
+import com.google.common.annotations.Beta;
+import org.xrpl.xrpl4j.model.transactions.AmmDeposit;
+
+/**
+ * A set of {@link TransactionFlags} that can be set on {@link AmmDeposit} transactions. Exactly one flag must be set on
+ * each {@link AmmDeposit} transaction, so this class does not allow for combination of multiple flags.
+ *
+ * While most other TransactionFlags support empty flags or 0, AmmDeposit transactions must have a Flags field
+ * to denote the deposit mode. Therefore, AmmDepositFlags does not support empty or unset flags.
+ *
+ *
+ * This class will be marked {@link Beta} until the AMM amendment is enabled on mainnet. Its API is subject to
+ * change.
+ */
+@Beta
+public class AmmDepositFlags extends TransactionFlags {
+
+ /**
+ * Constant {@link AmmDepositFlags} for the {@code tfLPToken} flag.
+ */
+ public static final AmmDepositFlags LP_TOKEN = new AmmDepositFlags(0x00010000);
+
+ /**
+ * Constant {@link AmmDepositFlags} for the {@code tfSingleAsset} flag.
+ */
+ public static final AmmDepositFlags SINGLE_ASSET = new AmmDepositFlags(0x00080000);
+
+ /**
+ * Constant {@link AmmDepositFlags} for the {@code tfTwoAsset} flag.
+ */
+ public static final AmmDepositFlags TWO_ASSET = new AmmDepositFlags(0x00100000);
+
+ /**
+ * Constant {@link AmmDepositFlags} for the {@code tfOneAssetLPToken} flag.
+ */
+ public static final AmmDepositFlags ONE_ASSET_LP_TOKEN = new AmmDepositFlags(0x00200000);
+
+ /**
+ * Constant {@link AmmDepositFlags} for the {@code tfLimitLPToken} flag.
+ */
+ public static final AmmDepositFlags LIMIT_LP_TOKEN = new AmmDepositFlags(0x00400000);
+
+ /**
+ * Constant {@link AmmDepositFlags} for the {@code tfTwoAssetIfEmpty} flag.
+ */
+ public static final AmmDepositFlags TWO_ASSET_IF_EMPTY = new AmmDepositFlags(0x00800000);
+
+ private AmmDepositFlags(long value) {
+ super(value);
+ }
+
+ /**
+ * Whether the {@code tfLPToken} flag is set.
+ *
+ * @return {@code true} if {@code tfLPToken} is set, otherwise {@code false}.
+ */
+ public boolean tfLpToken() {
+ return this.isSet(LP_TOKEN);
+ }
+
+ /**
+ * Whether the {@code tfSingleAsset} flag is set.
+ *
+ * @return {@code true} if {@code tfSingleAsset} is set, otherwise {@code false}.
+ */
+ public boolean tfSingleAsset() {
+ return this.isSet(SINGLE_ASSET);
+ }
+
+ /**
+ * Whether the {@code tfTwoAsset} flag is set.
+ *
+ * @return {@code true} if {@code tfTwoAsset} is set, otherwise {@code false}.
+ */
+ public boolean tfTwoAsset() {
+ return this.isSet(TWO_ASSET);
+ }
+
+ /**
+ * Whether the {@code tfOneAssetLPToken} flag is set.
+ *
+ * @return {@code true} if {@code tfOneAssetLPToken} is set, otherwise {@code false}.
+ */
+ public boolean tfOneAssetLpToken() {
+ return this.isSet(ONE_ASSET_LP_TOKEN);
+ }
+
+ /**
+ * Whether the {@code tfLimitLPToken} flag is set.
+ *
+ * @return {@code true} if {@code tfLimitLPToken} is set, otherwise {@code false}.
+ */
+ public boolean tfLimitLpToken() {
+ return this.isSet(LIMIT_LP_TOKEN);
+ }
+
+ /**
+ * Whether the {@code tfTwoAssetIfEmpty} flag is set.
+ *
+ * @return {@code true} if {@code tfTwoAssetIfEmpty} is set, otherwise {@code false}.
+ */
+ public boolean tfTwoAssetIfEmpty() {
+ return this.isSet(TWO_ASSET_IF_EMPTY);
+ }
+
+}
\ No newline at end of file
diff --git a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/flags/AmmWithdrawFlags.java b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/flags/AmmWithdrawFlags.java
new file mode 100644
index 000000000..dfdc55bb7
--- /dev/null
+++ b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/flags/AmmWithdrawFlags.java
@@ -0,0 +1,121 @@
+package org.xrpl.xrpl4j.model.flags;
+
+import com.google.common.annotations.Beta;
+
+/**
+ * A set of {@link TransactionFlags} that can be set on {@link org.xrpl.xrpl4j.model.transactions.AmmWithdraw}
+ * transactions. Exactly one flag must be set on each {@link org.xrpl.xrpl4j.model.transactions.AmmWithdraw}
+ * transaction, so this class does not allow for combination of multiple flags.
+ *
+ * While most other TransactionFlags support empty flags or 0, AmmWithdraw transactions must have a Flags field
+ * to denote the withdraw mode. Therefore, AmmWithdrawFlags does not support empty or unset flags.
+ *
+ *
+ * This class will be marked {@link Beta} until the AMM amendment is enabled on mainnet. Its API is subject to
+ * change.
+ */
+@Beta
+public class AmmWithdrawFlags extends TransactionFlags {
+
+ /**
+ * Constant {@link AmmWithdrawFlags} for the {@code tfLPToken} flag.
+ */
+ public static final AmmWithdrawFlags LP_TOKEN = new AmmWithdrawFlags(0x00010000);
+
+ /**
+ * Constant {@link AmmWithdrawFlags} for the {@code tfWithdrawAll} flag.
+ */
+ public static final AmmWithdrawFlags WITHDRAW_ALL = new AmmWithdrawFlags(0x00020000);
+
+ /**
+ * Constant {@link AmmWithdrawFlags} for the {@code tfOneAssetWithdrawAll} flag.
+ */
+ public static final AmmWithdrawFlags ONE_ASSET_WITHDRAW_ALL = new AmmWithdrawFlags(0x00040000);
+
+ /**
+ * Constant {@link AmmWithdrawFlags} for the {@code tfSingleAsset} flag.
+ */
+ public static final AmmWithdrawFlags SINGLE_ASSET = new AmmWithdrawFlags(0x00080000);
+
+ /**
+ * Constant {@link AmmWithdrawFlags} for the {@code tfTwoAsset} flag.
+ */
+ public static final AmmWithdrawFlags TWO_ASSET = new AmmWithdrawFlags(0x00100000);
+
+ /**
+ * Constant {@link AmmWithdrawFlags} for the {@code tfOneAssetLPToken} flag.
+ */
+ public static final AmmWithdrawFlags ONE_ASSET_LP_TOKEN = new AmmWithdrawFlags(0x00200000);
+
+ /**
+ * Constant {@link AmmWithdrawFlags} for the {@code tfLimitLPToken} flag.
+ */
+ public static final AmmWithdrawFlags LIMIT_LP_TOKEN = new AmmWithdrawFlags(0x00400000);
+
+ private AmmWithdrawFlags(long value) {
+ super(value);
+ }
+
+ /**
+ * Whether the {@code tfLPToken} flag is set.
+ *
+ * @return {@code true} if {@code tfLPToken} is set, otherwise {@code false}.
+ */
+ public boolean tfLpToken() {
+ return this.isSet(LP_TOKEN);
+ }
+
+ /**
+ * Whether the {@code tfWithdrawAll} flag is set.
+ *
+ * @return {@code true} if {@code tfWithdrawAll} is set, otherwise {@code false}.
+ */
+ public boolean tfWithdrawAll() {
+ return this.isSet(WITHDRAW_ALL);
+ }
+
+ /**
+ * Whether the {@code tfOneAssetWithdrawAll} flag is set.
+ *
+ * @return {@code true} if {@code tfOneAssetWithdrawAll} is set, otherwise {@code false}.
+ */
+ public boolean tfOneAssetWithdrawAll() {
+ return this.isSet(ONE_ASSET_WITHDRAW_ALL);
+ }
+
+ /**
+ * Whether the {@code tfSingleAsset} flag is set.
+ *
+ * @return {@code true} if {@code tfSingleAsset} is set, otherwise {@code false}.
+ */
+ public boolean tfSingleAsset() {
+ return this.isSet(SINGLE_ASSET);
+ }
+
+ /**
+ * Whether the {@code tfTwoAsset} flag is set.
+ *
+ * @return {@code true} if {@code tfTwoAsset} is set, otherwise {@code false}.
+ */
+ public boolean tfTwoAsset() {
+ return this.isSet(TWO_ASSET);
+ }
+
+ /**
+ * Whether the {@code tfOneAssetLPToken} flag is set.
+ *
+ * @return {@code true} if {@code tfOneAssetLPToken} is set, otherwise {@code false}.
+ */
+ public boolean tfOneAssetLpToken() {
+ return this.isSet(ONE_ASSET_LP_TOKEN);
+ }
+
+ /**
+ * Whether the {@code tfLimitLPToken} flag is set.
+ *
+ * @return {@code true} if {@code tfLimitLPToken} is set, otherwise {@code false}.
+ */
+ public boolean tfLimitLpToken() {
+ return this.isSet(LIMIT_LP_TOKEN);
+ }
+}
diff --git a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/jackson/modules/AffectedNodeDeserializer.java b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/jackson/modules/AffectedNodeDeserializer.java
index 25cde0d03..da326cd40 100644
--- a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/jackson/modules/AffectedNodeDeserializer.java
+++ b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/jackson/modules/AffectedNodeDeserializer.java
@@ -31,6 +31,7 @@
import org.xrpl.xrpl4j.model.transactions.metadata.CreatedNode;
import org.xrpl.xrpl4j.model.transactions.metadata.DeletedNode;
import org.xrpl.xrpl4j.model.transactions.metadata.MetaAccountRootObject;
+import org.xrpl.xrpl4j.model.transactions.metadata.MetaAmmObject;
import org.xrpl.xrpl4j.model.transactions.metadata.MetaCheckObject;
import org.xrpl.xrpl4j.model.transactions.metadata.MetaDepositPreAuthObject;
import org.xrpl.xrpl4j.model.transactions.metadata.MetaEscrowObject;
@@ -117,6 +118,8 @@ private Class extends MetaLedgerObject> determineLedgerObjectType(String ledge
return MetaTicketObject.class;
case "NFTokenPage":
return MetaNfTokenPageObject.class;
+ case "AMM":
+ return MetaAmmObject.class;
default:
return MetaUnknownObject.class;
}
diff --git a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/jackson/modules/TradingFeeDeserializer.java b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/jackson/modules/TradingFeeDeserializer.java
new file mode 100644
index 000000000..75ee1f426
--- /dev/null
+++ b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/jackson/modules/TradingFeeDeserializer.java
@@ -0,0 +1,27 @@
+package org.xrpl.xrpl4j.model.jackson.modules;
+
+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.TradingFee;
+
+import java.io.IOException;
+
+/**
+ * Custom Jackson deserializer for {@link TradingFee}s.
+ */
+public class TradingFeeDeserializer extends StdDeserializer {
+
+ /**
+ * No-args constructor.
+ */
+ public TradingFeeDeserializer() {
+ super(TradingFee.class);
+ }
+
+ @Override
+ public TradingFee deserialize(JsonParser jsonParser, DeserializationContext ctxt) throws IOException {
+ return TradingFee.of(UnsignedInteger.valueOf(jsonParser.getLongValue()));
+ }
+}
diff --git a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/jackson/modules/TradingFeeSerializer.java b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/jackson/modules/TradingFeeSerializer.java
new file mode 100644
index 000000000..98342fef2
--- /dev/null
+++ b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/jackson/modules/TradingFeeSerializer.java
@@ -0,0 +1,26 @@
+package org.xrpl.xrpl4j.model.jackson.modules;
+
+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.TradingFee;
+
+import java.io.IOException;
+
+/**
+ * Custom Jackson serializer for {@link TradingFee}s.
+ */
+public class TradingFeeSerializer extends StdScalarSerializer {
+
+ /**
+ * No-args constructor.
+ */
+ public TradingFeeSerializer() {
+ super(TradingFee.class, false);
+ }
+
+ @Override
+ public void serialize(TradingFee tradingFee, JsonGenerator gen, SerializerProvider provider) throws IOException {
+ gen.writeNumber(tradingFee.value().longValue());
+ }
+}
diff --git a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/jackson/modules/VoteWeightDeserializer.java b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/jackson/modules/VoteWeightDeserializer.java
new file mode 100644
index 000000000..9e3c3d345
--- /dev/null
+++ b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/jackson/modules/VoteWeightDeserializer.java
@@ -0,0 +1,27 @@
+package org.xrpl.xrpl4j.model.jackson.modules;
+
+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.VoteWeight;
+
+import java.io.IOException;
+
+/**
+ * Custom Jackson deserializer for {@link VoteWeight}s.
+ */
+public class VoteWeightDeserializer extends StdDeserializer {
+
+ /**
+ * No-args constructor.
+ */
+ public VoteWeightDeserializer() {
+ super(VoteWeight.class);
+ }
+
+ @Override
+ public VoteWeight deserialize(JsonParser jsonParser, DeserializationContext ctxt) throws IOException {
+ return VoteWeight.of(UnsignedInteger.valueOf(jsonParser.getLongValue()));
+ }
+}
diff --git a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/jackson/modules/VoteWeightSerializer.java b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/jackson/modules/VoteWeightSerializer.java
new file mode 100644
index 000000000..a71119b7f
--- /dev/null
+++ b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/jackson/modules/VoteWeightSerializer.java
@@ -0,0 +1,26 @@
+package org.xrpl.xrpl4j.model.jackson.modules;
+
+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.VoteWeight;
+
+import java.io.IOException;
+
+/**
+ * Custom Jackson serializer for {@link VoteWeight}s.
+ */
+public class VoteWeightSerializer extends StdScalarSerializer {
+
+ /**
+ * No-args constructor.
+ */
+ public VoteWeightSerializer() {
+ super(VoteWeight.class, false);
+ }
+
+ @Override
+ public void serialize(VoteWeight voteWeight, JsonGenerator gen, SerializerProvider provider) throws IOException {
+ gen.writeNumber(voteWeight.value().longValue());
+ }
+}
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 ba1c7e328..3639c6562 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
@@ -23,6 +23,7 @@
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import com.google.common.annotations.Beta;
import com.google.common.primitives.UnsignedInteger;
import org.immutables.value.Value;
import org.xrpl.xrpl4j.model.flags.AccountRootFlags;
@@ -225,6 +226,19 @@ default LedgerEntryType ledgerEntryType() {
@JsonProperty("NFTokenMinter")
Optional nfTokenMinter();
+ /**
+ * The ledger entry ID of the corresponding AMM ledger entry. Set during account creation; cannot be modified.
+ * If present, indicates that this is a special AMM AccountRoot; always omitted on non-AMM accounts.
+ *
+ * This method will be marked {@link com.google.common.annotations.Beta} until the AMM amendment is enabled on
+ * mainnet. Its API is subject to change.
+ *
+ * @return An optionally-present {@link Hash256}.
+ */
+ @Beta
+ @JsonProperty("AMMID")
+ Optional ammId();
+
/**
* The unique ID of this {@link AccountRootObject} ledger object.
*
diff --git a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/ledger/AmmObject.java b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/ledger/AmmObject.java
new file mode 100644
index 000000000..3466b047c
--- /dev/null
+++ b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/ledger/AmmObject.java
@@ -0,0 +1,152 @@
+package org.xrpl.xrpl4j.model.ledger;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import com.google.common.annotations.Beta;
+import org.immutables.value.Value;
+import org.xrpl.xrpl4j.model.flags.Flags;
+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.TradingFee;
+
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+/**
+ * Represents an AMM ledger object, which describes a single Automated Market Maker instance.
+ *
+ * This class will be marked {@link Beta} until the AMM amendment is enabled on mainnet. Its API is subject to
+ * change.
+ */
+@Value.Immutable
+@JsonSerialize(as = ImmutableAmmObject.class)
+@JsonDeserialize(as = ImmutableAmmObject.class)
+@Beta
+public interface AmmObject extends LedgerObject {
+
+ /**
+ * Construct a {@code AmmObject} builder.
+ *
+ * @return An {@link ImmutableAmmObject.Builder}.
+ */
+ static ImmutableAmmObject.Builder builder() {
+ return ImmutableAmmObject.builder();
+ }
+
+ /**
+ * The type of ledger object, which will always be "AMM" in this case.
+ *
+ * @return Always returns {@link org.xrpl.xrpl4j.model.ledger.LedgerObject.LedgerEntryType#AMM}.
+ */
+ @JsonProperty("LedgerEntryType")
+ @Value.Derived
+ default LedgerEntryType ledgerEntryType() {
+ return LedgerEntryType.AMM;
+ }
+
+ /**
+ * A bit-map of boolean flags. No flags are defined for {@link AmmObject}, so this value is always 0.
+ *
+ * @return Always {@link Flags#UNSET}.
+ */
+ @JsonProperty("Flags")
+ @Value.Derived
+ default Flags flags() {
+ return Flags.UNSET;
+ }
+
+ /**
+ * The definition for one of the two assets this AMM holds.
+ *
+ * @return An {@link Issue}.
+ */
+ @JsonProperty("Asset")
+ Issue asset();
+
+ /**
+ * The definition for the other asset this AMM holds.
+ *
+ * @return An {@link Issue}.
+ */
+ @JsonProperty("Asset2")
+ Issue asset2();
+
+ /**
+ * The address of the special account that holds this AMM's assets.
+ *
+ * @return An {@link Address}.
+ */
+ @JsonProperty("Account")
+ Address account();
+
+ /**
+ * Details of the current owner of the auction slot.
+ *
+ * @return An {@link AuctionSlot}.
+ */
+ @JsonProperty("AuctionSlot")
+ Optional auctionSlot();
+
+ /**
+ * The total outstanding balance of liquidity provider tokens from this AMM instance. The holders of these tokens can
+ * vote on the AMM's trading fee in proportion to their holdings, or redeem the tokens for a share of the AMM's assets
+ * which grows with the trading fees collected.
+ *
+ * @return An {@link IssuedCurrencyAmount}.
+ */
+ @JsonProperty("LPTokenBalance")
+ IssuedCurrencyAmount lpTokenBalance();
+
+ /**
+ * The percentage fee to be charged for trades against this AMM instance, in units of 1/10,000. The maximum value is
+ * 1000, for a 1% fee.
+ *
+ * @return A {@link TradingFee}.
+ */
+ @JsonProperty("TradingFee")
+ TradingFee tradingFee();
+
+ /**
+ * A list of vote objects, representing votes on the pool's trading fee.
+ *
+ * @return A {@link List} of {@link VoteEntryWrapper}s.
+ */
+ @JsonProperty("VoteSlots")
+ List voteSlots();
+
+ /**
+ * A hint indicating which page of the sender's owner directory links to this object, in case the directory consists
+ * of multiple pages.
+ *
+ * Note: The object does not contain a direct link to the owner directory containing it, since that value can be
+ * derived from the Account.
+ *
+ * @return A {@link String} containing the owner node hint.
+ */
+ @JsonProperty("OwnerNode")
+ String ownerNode();
+
+ /**
+ * Unwraps the {@link VoteEntryWrapper}s in {@link #voteSlots()} for easier access to {@link VoteEntry}s.
+ *
+ * @return A {@link List} of {@link VoteEntry}.
+ */
+ @JsonIgnore
+ @Value.Derived
+ default List voteSlotsUnwrapped() {
+ return voteSlots().stream()
+ .map(VoteEntryWrapper::voteEntry)
+ .collect(Collectors.toList());
+ }
+
+ /**
+ * The unique ID of the {@link AmmObject}.
+ *
+ * @return A {@link Hash256}.
+ */
+ Hash256 index();
+}
diff --git a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/ledger/AuctionSlot.java b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/ledger/AuctionSlot.java
new file mode 100644
index 000000000..0deb2c1f2
--- /dev/null
+++ b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/ledger/AuctionSlot.java
@@ -0,0 +1,94 @@
+package org.xrpl.xrpl4j.model.ledger;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import com.google.common.annotations.Beta;
+import com.google.common.primitives.UnsignedInteger;
+import org.immutables.value.Value;
+import org.xrpl.xrpl4j.model.transactions.Address;
+import org.xrpl.xrpl4j.model.transactions.IssuedCurrencyAmount;
+import org.xrpl.xrpl4j.model.transactions.TradingFee;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * Represents an AuctionSlot object in an {@link AmmObject}, containing details of the current owner of the auction
+ * slot.
+ *
+ * This class will be marked {@link Beta} until the AMM amendment is enabled on mainnet. Its API is subject to
+ * change.
+ */
+@Value.Immutable
+@JsonSerialize(as = ImmutableAuctionSlot.class)
+@JsonDeserialize(as = ImmutableAuctionSlot.class)
+@Beta
+public interface AuctionSlot {
+
+ /**
+ * Construct a {@code AuctionSlot} builder.
+ *
+ * @return An {@link ImmutableAuctionSlot.Builder}.
+ */
+ static ImmutableAuctionSlot.Builder builder() {
+ return ImmutableAuctionSlot.builder();
+ }
+
+ /**
+ * The current owner of this auction slot.
+ *
+ * @return An {@link Address}.
+ */
+ @JsonProperty("Account")
+ Address account();
+
+ /**
+ * A list of at most 4 additional accounts that are authorized to trade at the discounted fee for this AMM instance.
+ *
+ * @return A {@link List} of {@link AuthAccountWrapper}s.
+ */
+ @JsonProperty("AuthAccounts")
+ List authAccounts();
+
+ /**
+ * Extracts all the addresses found in the {@link AuthAccount}s found in {@link #authAccounts()}.
+ *
+ * @return A {@link List} of {@link Address}.
+ */
+ @JsonIgnore
+ @Value.Derived
+ default List authAccountsAddresses() {
+ return authAccounts().stream()
+ .map(AuthAccountWrapper::authAccount)
+ .map(AuthAccount::account)
+ .collect(Collectors.toList());
+ }
+
+ /**
+ * The trading fee to be charged to the auction owner. By default this is 0, meaning that the auction owner can trade
+ * at no fee instead of the standard fee for this AMM.
+ *
+ * @return A {@link TradingFee}.
+ */
+ @JsonProperty("DiscountedFee")
+ TradingFee discountedFee();
+
+ /**
+ * The amount the auction owner paid to win this slot, in LP Tokens.
+ *
+ * @return An {@link IssuedCurrencyAmount}.
+ */
+ @JsonProperty("Price")
+ IssuedCurrencyAmount price();
+
+ /**
+ * The time when this slot expires, in seconds since the Ripple Epoch.
+ *
+ * @return An {@link UnsignedInteger}
+ */
+ @JsonProperty("Expiration")
+ UnsignedInteger expiration();
+
+}
diff --git a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/ledger/AuthAccount.java b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/ledger/AuthAccount.java
new file mode 100644
index 000000000..402d9d9d0
--- /dev/null
+++ b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/ledger/AuthAccount.java
@@ -0,0 +1,43 @@
+package org.xrpl.xrpl4j.model.ledger;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import com.google.common.annotations.Beta;
+import org.immutables.value.Value;
+import org.xrpl.xrpl4j.model.transactions.Address;
+
+/**
+ * An account that is authorized to trade at the discounted fee for an AMM instance.
+ *
+ * This class will be marked {@link Beta} until the AMM amendment is enabled on mainnet. Its API is subject to
+ * change.
+ */
+@Value.Immutable
+@JsonSerialize(as = ImmutableAuthAccount.class)
+@JsonDeserialize(as = ImmutableAuthAccount.class)
+@Beta
+public interface AuthAccount {
+
+ /**
+ * Construct an {@link AuthAccount} containing the specified {@link Address}.
+ *
+ * @param account An {@link Address}.
+ *
+ * @return An {@link AuthAccount}.
+ */
+ static AuthAccount of(Address account) {
+ return ImmutableAuthAccount.builder()
+ .account(account)
+ .build();
+ }
+
+ /**
+ * The address of the account.
+ *
+ * @return An {@link Address}.
+ */
+ @JsonProperty("Account")
+ Address account();
+
+}
diff --git a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/ledger/AuthAccountWrapper.java b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/ledger/AuthAccountWrapper.java
new file mode 100644
index 000000000..e9fc3ea63
--- /dev/null
+++ b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/ledger/AuthAccountWrapper.java
@@ -0,0 +1,42 @@
+package org.xrpl.xrpl4j.model.ledger;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import com.google.common.annotations.Beta;
+import org.immutables.value.Value;
+
+/**
+ * A wrapper around {@link AuthAccount}s.
+ *
+ * This class will be marked {@link Beta} until the AMM amendment is enabled on mainnet. Its API is subject to
+ * change.
+ */
+@Value.Immutable
+@JsonSerialize(as = ImmutableAuthAccountWrapper.class)
+@JsonDeserialize(as = ImmutableAuthAccountWrapper.class)
+@Beta
+public interface AuthAccountWrapper {
+
+ /**
+ * Construct an {@link AuthAccountWrapper} containing the specified {@link AuthAccount}.
+ *
+ * @param authAccount An {@link AuthAccount}.
+ *
+ * @return An {@link AuthAccountWrapper}.
+ */
+ static AuthAccountWrapper of(AuthAccount authAccount) {
+ return ImmutableAuthAccountWrapper.builder()
+ .authAccount(authAccount)
+ .build();
+ }
+
+ /**
+ * An {@link AuthAccount}.
+ *
+ * @return An {@link AuthAccount}.
+ */
+ @JsonProperty("AuthAccount")
+ AuthAccount authAccount();
+
+}
diff --git a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/ledger/Issue.java b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/ledger/Issue.java
index 613cb60e1..7fadc95ac 100644
--- a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/ledger/Issue.java
+++ b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/ledger/Issue.java
@@ -63,4 +63,4 @@ static ImmutableIssue.Builder builder() {
*/
Optional issuer();
-}
\ No newline at end of file
+}
diff --git a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/ledger/LedgerObject.java b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/ledger/LedgerObject.java
index fd9a23619..b6b7e1b2f 100644
--- a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/ledger/LedgerObject.java
+++ b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/ledger/LedgerObject.java
@@ -24,6 +24,7 @@
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.JsonValue;
+import com.google.common.annotations.Beta;
/**
* Market interface for XRP Ledger Objects.
@@ -52,6 +53,7 @@
@JsonSubTypes.Type(value = ImmutableRippleStateObject.class, name = "RippleState"),
@JsonSubTypes.Type(value = ImmutableSignerListObject.class, name = "SignerList"),
@JsonSubTypes.Type(value = ImmutableTicketObject.class, name = "Ticket"),
+ @JsonSubTypes.Type(value = ImmutableAmmObject.class, name = "AMM"),
@JsonSubTypes.Type(value = ImmutableNfTokenPageObject.class, name = "NFTokenPage"),
})
// TODO: Uncomment subtypes as we implement
@@ -139,7 +141,16 @@ enum LedgerEntryType {
/**
* The {@link LedgerEntryType} for {@code NfTokenPageObject} ledger objects.
*/
- NFTOKEN_PAGE("NFTokenPage");
+ NFTOKEN_PAGE("NFTokenPage"),
+
+ /**
+ * The {@link LedgerEntryType} for {@code AmmObject} ledger objects.
+ *
+ * This constant will be marked {@link Beta} until the AMM amendment is enabled on mainnet. Its API is subject to
+ * change.
+ */
+ @Beta
+ AMM("AMM");
private final String value;
diff --git a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/ledger/TicketObject.java b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/ledger/TicketObject.java
index 0a3a834fe..f103989d7 100644
--- a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/ledger/TicketObject.java
+++ b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/ledger/TicketObject.java
@@ -110,4 +110,11 @@ default Flags flags() {
*/
@JsonProperty("TicketSequence")
UnsignedInteger ticketSequence();
+
+ /**
+ * The unique ID of the {@link TicketObject}.
+ *
+ * @return A {@link Hash256}.
+ */
+ Hash256 index();
}
diff --git a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/ledger/VoteEntry.java b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/ledger/VoteEntry.java
new file mode 100644
index 000000000..3bb2fd063
--- /dev/null
+++ b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/ledger/VoteEntry.java
@@ -0,0 +1,57 @@
+package org.xrpl.xrpl4j.model.ledger;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import com.google.common.annotations.Beta;
+import org.immutables.value.Value;
+import org.xrpl.xrpl4j.model.transactions.Address;
+import org.xrpl.xrpl4j.model.transactions.TradingFee;
+import org.xrpl.xrpl4j.model.transactions.VoteWeight;
+
+/**
+ * Describes a vote for the trading fee on an AMM by an LP.
+ *
+ * This class will be marked {@link Beta} until the AMM amendment is enabled on mainnet. Its API is subject to
+ * change.
+ */
+@Value.Immutable
+@JsonSerialize(as = ImmutableVoteEntry.class)
+@JsonDeserialize(as = ImmutableVoteEntry.class)
+@Beta
+public interface VoteEntry {
+
+ /**
+ * Construct a {@code VoteEntry} builder.
+ *
+ * @return An {@link ImmutableVoteEntry.Builder}.
+ */
+ static ImmutableVoteEntry.Builder builder() {
+ return ImmutableVoteEntry.builder();
+ }
+
+ /**
+ * The address of the LP who voted.
+ *
+ * @return An {@link Address}.
+ */
+ @JsonProperty("Account")
+ Address account();
+
+ /**
+ * The trading fee that the LP voted for.
+ *
+ * @return A {@link TradingFee}.
+ */
+ @JsonProperty("TradingFee")
+ TradingFee tradingFee();
+
+ /**
+ * The weight of the LP's vote.
+ *
+ * @return The {@link VoteWeight}.
+ */
+ @JsonProperty("VoteWeight")
+ VoteWeight voteWeight();
+
+}
diff --git a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/ledger/VoteEntryWrapper.java b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/ledger/VoteEntryWrapper.java
new file mode 100644
index 000000000..70a058775
--- /dev/null
+++ b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/ledger/VoteEntryWrapper.java
@@ -0,0 +1,43 @@
+package org.xrpl.xrpl4j.model.ledger;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import com.google.common.annotations.Beta;
+import org.immutables.value.Value;
+
+/**
+ * A wrapper around a {@link VoteEntry}.
+ *
+ * This class will be marked {@link Beta} until the AMM amendment is enabled on mainnet. Its API is subject to
+ * change.
+ */
+@Value.Immutable
+@JsonSerialize(as = ImmutableVoteEntryWrapper.class)
+@JsonDeserialize(as = ImmutableVoteEntryWrapper.class)
+@Beta
+public interface VoteEntryWrapper {
+
+ /**
+ * Construct a {@link VoteEntryWrapper} containing the specified
+ * {@link VoteEntry}.
+ *
+ * @param voteEntry A {@link VoteEntry}.
+ *
+ * @return A {@link VoteEntryWrapper}.
+ */
+ static VoteEntryWrapper of(VoteEntry voteEntry) {
+ return ImmutableVoteEntryWrapper.builder()
+ .voteEntry(voteEntry)
+ .build();
+ }
+
+ /**
+ * A {@link VoteEntry}.
+ *
+ * @return A {@link VoteEntry}.
+ */
+ @JsonProperty("VoteEntry")
+ VoteEntry voteEntry();
+
+}
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 ace5b6994..be1af6b7a 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
@@ -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.
@@ -21,16 +21,19 @@
*/
import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonValue;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import com.google.common.annotations.Beta;
import com.google.common.base.Preconditions;
import com.google.common.primitives.UnsignedInteger;
import org.immutables.value.Value;
import org.xrpl.xrpl4j.model.flags.AccountSetTransactionFlags;
-import org.xrpl.xrpl4j.model.flags.TransactionFlags;
+import java.util.Arrays;
+import java.util.Collections;
import java.util.Optional;
/**
@@ -65,25 +68,189 @@ default AccountSetTransactionFlags flags() {
/**
* Unique identifier of a flag to disable for this account.
*
+ * If this field is empty, developers should check if {@link #clearFlagRawValue()} is also empty. If
+ * {@link #clearFlagRawValue()} is present, it means that the {@code ClearFlag} field of the transaction was not a
+ * valid {@link AccountSetFlag} but was still present in a validated transaction on ledger.
+ *
* Because the preferred way of setting account flags is with {@link AccountSetFlag}s, this field should
* not be set in conjunction with the {@link AccountSet#flags()} field.
*
* @return An {@link Optional} of type {@link AccountSetFlag} representing the flag to disable on this account.
*/
- @JsonProperty("ClearFlag")
+ @JsonIgnore
Optional clearFlag();
+ /**
+ * A flag to disable for this account, as an {@link UnsignedInteger}.
+ *
+ * Developers should prefer setting {@link #clearFlag()} and leaving this field empty when constructing
+ * a new {@link AccountSet}. This field is used to serialize and deserialize the {@code "ClearFlag"} field in JSON,
+ * as some {@link AccountSet} transactions on the XRPL set the "ClearFlag" field to a number that is not recognized as
+ * an asf flag by rippled. Without this field, xrpl4j would fail to deserialize those transactions, as
+ * {@link AccountSetFlag} does not support arbitrary integer values.
+ *
+ * Additionally, using this field as the source of truth for JSON serialization/deserialization rather than
+ * {@link #clearFlag()} allows developers to recompute the hash of a transaction that was deserialized from a rippled
+ * RPC/WS result accurately. An alternative to this field would be to add an enum variant to {@link AccountSetFlag}
+ * for unknown values, but binary serializing an {@link AccountSet} that was constructed by deserializing JSON would
+ * result in a different binary blob than what exists on ledger.
+ *
+ * @return An {@link Optional} {@link UnsignedInteger}.
+ */
+ @JsonProperty("ClearFlag")
+ Optional clearFlagRawValue();
+
+ /**
+ * Normalization method to try to get {@link #clearFlag()}and {@link #clearFlagRawValue()} to match.
+ *
+ * If neither field is present, there is nothing to do.
+ * If both fields are present, there is nothing to do, but we will check that {@link #clearFlag()}'s
+ * underlying value equals {@link #clearFlagRawValue()}.
+ * If {@link #clearFlag()} is present but {@link #clearFlagRawValue()} is empty, we set
+ * {@link #clearFlagRawValue()} to the underlying value of {@link #clearFlag()}.
+ * If {@link #clearFlag()} is empty and {@link #clearFlagRawValue()} is present, we will set
+ * {@link #clearFlag()} to the {@link AccountSetFlag} variant associated with {@link #clearFlagRawValue()}, or leave
+ * {@link #clearFlag()} empty if {@link #clearFlagRawValue()} does not map to an {@link AccountSetFlag}.
+ *
+ * @return A normalized {@link AccountSet}.
+ */
+ @Value.Check
+ default AccountSet normalizeClearFlag() {
+ if (!clearFlag().isPresent() && !clearFlagRawValue().isPresent()) {
+ // If both are empty, nothing to do.
+ return this;
+ } else if (clearFlag().isPresent() && clearFlagRawValue().isPresent()) {
+ // Both will be present if:
+ // 1. A developer set them both manually (in the builder)
+ // 2. This normalize method has already been called.
+
+ // We should still check that the clearFlagRawValue matches the inner value of AccountSetFlag.
+ Preconditions.checkState(
+ clearFlag().get().getValue() == clearFlagRawValue().get().longValue(),
+ String.format("clearFlag and clearFlagRawValue should be equivalent, but clearFlag's underlying " +
+ "value was %s and clearFlagRawValue was %s",
+ clearFlag().get().getValue(),
+ clearFlagRawValue().get().longValue()
+ )
+ );
+ return this;
+ } else if (clearFlag().isPresent() && !clearFlagRawValue().isPresent()) {
+ // This can only happen if the developer only set clearFlag(). In this case, we need to set clearFlagRawValue to
+ // match clearFlag.
+ return AccountSet.builder().from(this)
+ .clearFlagRawValue(UnsignedInteger.valueOf(clearFlag().get().getValue()))
+ .build();
+ } else { // clearFlag not present and clearFlagRawValue is present
+ // This can happen if:
+ // 1. A developer sets clearFlagRawValue manually in the builder
+ // 2. JSON has ClearFlag and jackson sets clearFlagRawValue.
+ // This value will never be negative due to XRPL representing this kind of flag as an unsigned number,
+ // so no lower bound check is required.
+ if (clearFlagRawValue().get().longValue() <= AccountSetFlag.MAX_VALUE) {
+ // Set clearFlag to clearFlagRawValue if clearFlagRawValue matches a valid AccountSetFlag variant.
+ return AccountSet.builder().from(this)
+ .clearFlag(AccountSetFlag.forValue(clearFlagRawValue().get().intValue()))
+ .build();
+ } else {
+ // Otherwise, leave clearFlag empty.
+ return this;
+ }
+ }
+ }
+
/**
* Unique identifier of a flag to enable for this account.
*
- * Because the preferred way of setting account flags is with {@link AccountSetFlag}s, this field should not be set
- * in conjunction with the {@link AccountSet#flags()} field.
+ *
If this field is empty, developers should check if {@link #setFlagRawValue()} is also empty. If
+ * {@link #setFlagRawValue()} is present, it means that the {@code ClearFlag} field of the transaction was not a
+ * valid {@link AccountSetFlag} but was still present in a validated transaction on ledger.
+ *
+ * Because the preferred way of setting account flags is with {@link AccountSetFlag}s, this field should not be
+ * set in conjunction with the {@link AccountSet#flags()} field.
*
* @return An {@link Optional} of type {@link AccountSetFlag} representing the flag to enable on this account.
*/
- @JsonProperty("SetFlag")
+ @JsonIgnore
Optional setFlag();
+ /**
+ * A flag to disable for this account, as an {@link UnsignedInteger}.
+ *
+ * Developers should prefer setting {@link #setFlag()} and leaving this field empty when constructing
+ * a new {@link AccountSet}. This field is used to serialize and deserialize the {@code "ClearFlag"} field in JSON,
+ * as some {@link AccountSet} transactions on the XRPL set the "ClearFlag" field to a number that is not recognized as
+ * an asf flag by rippled. Without this field, xrpl4j would fail to deserialize those transactions, as
+ * {@link AccountSetFlag} does not support arbitrary integer values.
+ *
+ * Additionally, using this field as the source of truth for JSON serialization/deserialization rather than
+ * {@link #setFlag()} allows developers to recompute the hash of a transaction that was deserialized from a rippled
+ * RPC/WS result accurately. An alternative to this field would be to add an enum variant to {@link AccountSetFlag}
+ * for unknown values, but binary serializing an {@link AccountSet} that was constructed by deserializing JSON would
+ * result in a different binary blob than what exists on ledger.
+ *
+ * @return An {@link Optional} {@link UnsignedInteger}
+ */
+ @JsonProperty("SetFlag")
+ Optional setFlagRawValue();
+
+ /**
+ * Normalization method to try to get {@link #setFlag()}and {@link #setFlagRawValue()} to match.
+ *
+ * If neither field is present, there is nothing to do.
+ * If both fields are present, there is nothing to do, but we will check that {@link #setFlag()}'s
+ * underlying value equals {@link #setFlagRawValue()}.
+ * If {@link #setFlag()} is present but {@link #setFlagRawValue()} is empty, we set
+ * {@link #setFlagRawValue()} to the underlying value of {@link #setFlag()}.
+ * If {@link #setFlag()} is empty and {@link #setFlagRawValue()} is present, we will set
+ * {@link #setFlag()} to the {@link AccountSetFlag} variant associated with {@link #setFlagRawValue()}, or leave
+ * {@link #setFlag()} empty if {@link #setFlagRawValue()} does not map to an {@link AccountSetFlag}.
+ *
+ * @return A normalized {@link AccountSet}.
+ */
+ @Value.Check
+ default AccountSet normalizeSetFlag() {
+ if (!setFlag().isPresent() && !setFlagRawValue().isPresent()) {
+ // If both are empty, nothing to do.
+ return this;
+ } else if (setFlag().isPresent() && setFlagRawValue().isPresent()) {
+ // Both will be present if:
+ // 1. A developer set them both manually (in the builder)
+ // 2. This normalize method has already been called.
+
+ // We should still check that the setFlagRawValue matches the inner value of AccountSetFlag.
+ Preconditions.checkState(
+ setFlag().get().getValue() == setFlagRawValue().get().longValue(),
+ String.format("setFlag and setFlagRawValue should be equivalent, but setFlag's underlying " +
+ "value was %s and setFlagRawValue was %s",
+ setFlag().get().getValue(),
+ setFlagRawValue().get().longValue()
+ )
+ );
+ return this;
+ } else if (setFlag().isPresent() && !setFlagRawValue().isPresent()) {
+ // This can only happen if the developer only set setFlag(). In this case, we need to set setFlagRawValue to
+ // match setFlag.
+ return AccountSet.builder().from(this)
+ .setFlagRawValue(UnsignedInteger.valueOf(setFlag().get().getValue()))
+ .build();
+ } else { // setFlag is empty and setFlagRawValue is present
+ // This can happen if:
+ // 1. A developer sets setFlagRawValue manually in the builder
+ // 2. JSON has ClearFlag and jackson sets setFlagRawValue.
+ // This value will never be negative due to XRPL representing this kind of flag as an unsigned number,
+ // so no lower bound check is required.
+ if (setFlagRawValue().get().longValue() <= AccountSetFlag.MAX_VALUE) {
+ // Set setFlag to setFlagRawValue if setFlagRawValue matches a valid AccountSetFlag variant.
+ return AccountSet.builder().from(this)
+ .setFlag(AccountSetFlag.forValue(setFlagRawValue().get().intValue()))
+ .build();
+ } else {
+ // Otherwise, leave setFlag empty.
+ return this;
+ }
+ }
+ }
+
/**
* The hex string of the lowercase ASCII of the domain for the account. For example, the domain example.com would be
* represented as "6578616D706C652E636F6D".
@@ -132,8 +299,8 @@ default AccountSetTransactionFlags flags() {
Optional tickSize();
/**
- * Sets an alternate account that is allowed to mint NFTokens on this
- * account's behalf using NFTokenMint's `Issuer` field.
+ * Sets an alternate account that is allowed to mint NFTokens on this account's behalf using NFTokenMint's `Issuer`
+ * field.
*
* @return An {@link Optional} field of type {@link Address}.
*/
@@ -206,8 +373,8 @@ 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.
+ * This flag will do nothing but exists to accurately deserialize AccountSet transactions whose {@code SetFlag} or
+ * {@code ClearFlag} fields are zero.
*/
NONE(0),
/**
@@ -276,7 +443,15 @@ enum AccountSetFlag {
/**
* Block incoming Trustlines.
*/
- DISALLOW_INCOMING_TRUSTLINE(15);
+ DISALLOW_INCOMING_TRUSTLINE(15),
+ /**
+ * Enable clawback on the account's trustlines.
+ *
+ * This value will be marked {@link Beta} until the Clawback amendment is enabled on mainnet. Its API is subject
+ * to change.
+ */
+ @Beta
+ ALLOW_TRUSTLINE_CLAWBACK(16);
final int value;
@@ -284,6 +459,12 @@ enum AccountSetFlag {
this.value = value;
}
+ /**
+ * The maximum underlying value of AccountSetFlags. This is useful for the normalization methods of AccountSet
+ * so that adding a new AccountSetFlag does not require a change to those normalization functions.
+ */
+ static final int MAX_VALUE = Collections.max(Arrays.asList(AccountSetFlag.values())).getValue();
+
/**
* To deserialize enums with integer values, you need to specify this factory method with the {@link JsonCreator}
* annotation, otherwise Jackson treats the JSON integer value as an ordinal.
@@ -291,6 +472,7 @@ enum AccountSetFlag {
* @param value The int value of the flag.
*
* @return The {@link AccountSetFlag} for the given integer value.
+ *
* @see "https://github.com/FasterXML/jackson-databind/issues/1850"
*/
@JsonCreator
diff --git a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/AmmBid.java b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/AmmBid.java
new file mode 100644
index 000000000..24872f827
--- /dev/null
+++ b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/AmmBid.java
@@ -0,0 +1,94 @@
+package org.xrpl.xrpl4j.model.transactions;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import com.google.common.annotations.Beta;
+import org.immutables.value.Value;
+import org.xrpl.xrpl4j.model.flags.TransactionFlags;
+import org.xrpl.xrpl4j.model.ledger.AuthAccountWrapper;
+import org.xrpl.xrpl4j.model.ledger.Issue;
+
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * Object mapping for the AMMBid transaction.
+ *
+ * This class will be marked {@link Beta} until the AMM amendment is enabled on mainnet. Its API is subject to
+ * change.
+ */
+@Value.Immutable
+@JsonSerialize(as = ImmutableAmmBid.class)
+@JsonDeserialize(as = ImmutableAmmBid.class)
+@Beta
+public interface AmmBid extends Transaction {
+
+ /**
+ * Construct a {@code AmmBid} builder.
+ *
+ * @return An {@link ImmutableAmmBid.Builder}.
+ */
+ static ImmutableAmmBid.Builder builder() {
+ return ImmutableAmmBid.builder();
+ }
+
+ /**
+ * Set of {@link TransactionFlags}s for this {@link AmmBid}, which only allows the {@code tfFullyCanonicalSig} flag,
+ * which is deprecated.
+ *
+ * The value of the flags cannot be set manually, but exists for JSON serialization/deserialization only and for
+ * proper signature computation in rippled.
+ *
+ * @return Always {@link TransactionFlags#EMPTY}.
+ */
+ @JsonProperty("Flags")
+ @Value.Default
+ default TransactionFlags flags() {
+ return TransactionFlags.EMPTY;
+ }
+
+ /**
+ * The definition for one of the assets in the AMM's pool.
+ *
+ * @return An {@link Issue}.
+ */
+ @JsonProperty("Asset")
+ Issue asset();
+
+ /**
+ * The definition for the other asset in the AMM's pool.
+ *
+ * @return An {@link Issue}.
+ */
+ @JsonProperty("Asset2")
+ Issue asset2();
+
+ /**
+ * Pay at least this amount for the slot. Setting this value higher makes it harder for others to outbid you. If
+ * omitted, pay the minimum necessary to win the bid.
+ *
+ * @return An optionally present {@link IssuedCurrencyAmount}.
+ */
+ @JsonProperty("BidMin")
+ Optional bidMin();
+
+ /**
+ * Pay at most this amount for the slot. If the cost to win the bid is higher than this amount, the transaction fails.
+ * If omitted, pay as much as necessary to win the bid.
+ *
+ * @return An optionally present {@link IssuedCurrencyAmount}.
+ */
+ @JsonProperty("BidMax")
+ Optional bidMax();
+
+ /**
+ * A list of up to 4 additional accounts that you allow to trade at the discounted fee. This cannot include the
+ * address of the transaction sender
+ *
+ * @return A {@link List}
+ */
+ @JsonProperty("AuthAccounts")
+ List authAccounts();
+
+}
diff --git a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/AmmCreate.java b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/AmmCreate.java
new file mode 100644
index 000000000..dab53fa96
--- /dev/null
+++ b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/AmmCreate.java
@@ -0,0 +1,71 @@
+package org.xrpl.xrpl4j.model.transactions;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import com.google.common.annotations.Beta;
+import org.immutables.value.Value;
+import org.xrpl.xrpl4j.model.flags.Flags;
+import org.xrpl.xrpl4j.model.flags.TransactionFlags;
+
+/**
+ * Object mapping for the AMMCreate transaction.
+ *
+ * This class will be marked {@link Beta} until the AMM amendment is enabled on mainnet. Its API is subject to
+ * change.
+ */
+@Value.Immutable
+@JsonSerialize(as = ImmutableAmmCreate.class)
+@JsonDeserialize(as = ImmutableAmmCreate.class)
+@Beta
+public interface AmmCreate extends Transaction {
+
+ /**
+ * Construct a {@code AmmCreate} builder.
+ *
+ * @return An {@link ImmutableAmmCreate.Builder}.
+ */
+ static ImmutableAmmCreate.Builder builder() {
+ return ImmutableAmmCreate.builder();
+ }
+
+ /**
+ * Set of {@link TransactionFlags}s for this {@link AmmCreate}, which only allows the {@code tfFullyCanonicalSig}
+ * flag, which is deprecated.
+ *
+ * The value of the flags cannot be set manually, but exists for JSON serialization/deserialization only and for
+ * proper signature computation in rippled.
+ *
+ * @return Always {@link TransactionFlags#EMPTY}.
+ */
+ @JsonProperty("Flags")
+ @Value.Default
+ default TransactionFlags flags() {
+ return TransactionFlags.EMPTY;
+ }
+
+ /**
+ * The first of the two assets to fund this AMM with.
+ *
+ * @return A {@link CurrencyAmount}.
+ */
+ @JsonProperty("Amount")
+ CurrencyAmount amount();
+
+ /**
+ * The second of the two assets to fund this AMM with.
+ *
+ * @return A {@link CurrencyAmount}.
+ */
+ @JsonProperty("Amount2")
+ CurrencyAmount amount2();
+
+ /**
+ * The fee to charge for trades against this AMM instance.
+ *
+ * @return A {@link TradingFee}.
+ */
+ @JsonProperty("TradingFee")
+ TradingFee tradingFee();
+
+}
diff --git a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/AmmDelete.java b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/AmmDelete.java
new file mode 100644
index 000000000..6af820fb3
--- /dev/null
+++ b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/AmmDelete.java
@@ -0,0 +1,61 @@
+package org.xrpl.xrpl4j.model.transactions;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import com.google.common.annotations.Beta;
+import org.immutables.value.Value;
+import org.immutables.value.Value.Immutable;
+import org.xrpl.xrpl4j.model.flags.TransactionFlags;
+import org.xrpl.xrpl4j.model.ledger.Issue;
+
+/**
+ * Object mapping for the AMMDelete transaction.
+ *
+ *
This class will be marked {@link Beta} until the AMM amendment is enabled on mainnet. Its API is subject to
+ * change.
+ */
+@Immutable
+@JsonSerialize(as = ImmutableAmmDelete.class)
+@JsonDeserialize(as = ImmutableAmmDelete.class)
+@Beta
+public interface AmmDelete extends Transaction {
+
+ /**
+ * Construct a {@code AmmDelete} builder.
+ *
+ * @return An {@link ImmutableAmmDelete.Builder}.
+ */
+ static ImmutableAmmDelete.Builder builder() {
+ return ImmutableAmmDelete.builder();
+ }
+
+ /**
+ * Set of {@link TransactionFlags}s for this {@link AmmDelete}, which only allows the {@code tfFullyCanonicalSig}
+ * flag, which is deprecated.
+ *
+ * @return Always {@link TransactionFlags#EMPTY}.
+ */
+ @JsonProperty("Flags")
+ @Value.Default
+ default TransactionFlags flags() {
+ return TransactionFlags.EMPTY;
+ }
+
+ /**
+ * The definition for one of the assets in the AMM's pool.
+ *
+ * @return An {@link Issue}.
+ */
+ @JsonProperty("Asset")
+ Issue asset();
+
+ /**
+ * The definition for the other asset in the AMM's pool.
+ *
+ * @return An {@link Issue}.
+ */
+ @JsonProperty("Asset2")
+ Issue asset2();
+
+}
diff --git a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/AmmDeposit.java b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/AmmDeposit.java
new file mode 100644
index 000000000..1336e27a6
--- /dev/null
+++ b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/AmmDeposit.java
@@ -0,0 +1,100 @@
+package org.xrpl.xrpl4j.model.transactions;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import com.google.common.annotations.Beta;
+import org.immutables.value.Value;
+import org.xrpl.xrpl4j.model.flags.AmmDepositFlags;
+import org.xrpl.xrpl4j.model.ledger.Issue;
+
+import java.util.Optional;
+
+/**
+ * Object mapping for the AMMDeposit transaction.
+ *
+ * This class will be marked {@link Beta} until the AMM amendment is enabled on mainnet. Its API is subject to
+ * change.
+ */
+@Value.Immutable
+@JsonSerialize(as = ImmutableAmmDeposit.class)
+@JsonDeserialize(as = ImmutableAmmDeposit.class)
+@Beta
+public interface AmmDeposit extends Transaction {
+
+ /**
+ * Construct a {@code AmmDeposit} builder.
+ *
+ * @return An {@link ImmutableAmmDeposit.Builder}.
+ */
+ static ImmutableAmmDeposit.Builder builder() {
+ return ImmutableAmmDeposit.builder();
+ }
+
+ /**
+ * A {@link AmmDepositFlags} for this transaction. This field must be set manually.
+ *
+ * @return A {@link AmmDepositFlags} for this transaction.
+ */
+ @JsonProperty("Flags")
+ AmmDepositFlags flags();
+
+ /**
+ * The definition for one of the assets in the AMM's pool.
+ *
+ * @return An {@link Issue}.
+ */
+ @JsonProperty("Asset")
+ Issue asset();
+
+ /**
+ * The definition for the other asset in the AMM's pool.
+ *
+ * @return An {@link Issue}.
+ */
+ @JsonProperty("Asset2")
+ Issue asset2();
+
+ /**
+ * The amount of one asset to deposit to the AMM. If present, this must match the type of one of the assets (tokens or
+ * XRP) in the AMM's pool.
+ *
+ * @return An optionally present {@link CurrencyAmount}.
+ */
+ @JsonProperty("Amount")
+ Optional amount();
+
+ /**
+ * The amount of another asset to add to the AMM. If present, this must match the type of the other asset in the AMM's
+ * pool and cannot be the same asset as Amount.
+ *
+ * @return An optionally present {@link CurrencyAmount}.
+ */
+ @JsonProperty("Amount2")
+ Optional amount2();
+
+ /**
+ * The maximum effective price, in the deposit asset, to pay for each LP Token received.
+ *
+ * @return An optionally present {@link CurrencyAmount}.
+ */
+ @JsonProperty("EPrice")
+ Optional effectivePrice();
+
+ /**
+ * How many of the AMM's LP Tokens to buy.
+ *
+ * @return An optionally present {@link IssuedCurrencyAmount}.
+ */
+ @JsonProperty("LPTokenOut")
+ Optional lpTokenOut();
+
+ /**
+ * An optional {@link TradingFee} to set on the AMM instance. This field is only honored if the AMM's LP token balance
+ * is zero, and can only be set if flags is {@link AmmDepositFlags#TWO_ASSET_IF_EMPTY}.
+ *
+ * @return An {@link Optional} {@link TradingFee}.
+ */
+ @JsonProperty("TradingFee")
+ Optional tradingFee();
+}
diff --git a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/AmmVote.java b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/AmmVote.java
new file mode 100644
index 000000000..9742cfefb
--- /dev/null
+++ b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/AmmVote.java
@@ -0,0 +1,72 @@
+package org.xrpl.xrpl4j.model.transactions;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import com.google.common.annotations.Beta;
+import org.immutables.value.Value;
+import org.xrpl.xrpl4j.model.flags.TransactionFlags;
+import org.xrpl.xrpl4j.model.ledger.Issue;
+
+/**
+ * Object mapping for the AMMVote transaction.
+ *
+ * This class will be marked {@link Beta} until the AMM amendment is enabled on mainnet. Its API is subject to
+ * change.
+ */
+@Value.Immutable
+@JsonSerialize(as = ImmutableAmmVote.class)
+@JsonDeserialize(as = ImmutableAmmVote.class)
+@Beta
+public interface AmmVote extends Transaction {
+
+ /**
+ * Construct a {@code AmmVote} builder.
+ *
+ * @return An {@link ImmutableAmmVote.Builder}.
+ */
+ static ImmutableAmmVote.Builder builder() {
+ return ImmutableAmmVote.builder();
+ }
+
+ /**
+ * Set of {@link TransactionFlags}s for this {@link AmmVote}, which only allows the {@code tfFullyCanonicalSig} flag,
+ * which is deprecated.
+ *
+ * The value of the flags cannot be set manually, but exists for JSON serialization/deserialization only and for
+ * proper signature computation in rippled.
+ *
+ * @return Always {@link TransactionFlags#EMPTY}.
+ */
+ @JsonProperty("Flags")
+ @Value.Default
+ default TransactionFlags flags() {
+ return TransactionFlags.EMPTY;
+ }
+
+ /**
+ * The definition for one of the assets in the AMM's pool.
+ *
+ * @return An {@link Issue}.
+ */
+ @JsonProperty("Asset")
+ Issue asset();
+
+ /**
+ * The definition for the other asset in the AMM's pool.
+ *
+ * @return An {@link Issue}.
+ */
+ @JsonProperty("Asset2")
+ Issue asset2();
+
+ /**
+ * The proposed fee to vote for.
+ *
+ * @return A {@link TradingFee}.
+ */
+ @JsonProperty("TradingFee")
+ TradingFee tradingFee();
+
+
+}
diff --git a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/AmmWithdraw.java b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/AmmWithdraw.java
new file mode 100644
index 000000000..3e0998fde
--- /dev/null
+++ b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/AmmWithdraw.java
@@ -0,0 +1,94 @@
+package org.xrpl.xrpl4j.model.transactions;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import com.google.common.annotations.Beta;
+import com.google.common.base.Preconditions;
+import org.immutables.value.Value;
+import org.immutables.value.Value.Immutable;
+import org.xrpl.xrpl4j.model.flags.AmmWithdrawFlags;
+import org.xrpl.xrpl4j.model.ledger.Issue;
+
+import java.util.Optional;
+
+/**
+ * Object mapping for the AMMWithdraw transaction.
+ *
+ *
This class will be marked {@link Beta} until the AMM amendment is enabled on mainnet. Its API is subject to
+ * change.
+ */
+@Immutable
+@JsonSerialize(as = ImmutableAmmWithdraw.class)
+@JsonDeserialize(as = ImmutableAmmWithdraw.class)
+@Beta
+public interface AmmWithdraw extends Transaction {
+
+ /**
+ * Construct a {@code AmmWithdraw} builder.
+ *
+ * @return An {@link ImmutableAmmWithdraw.Builder}.
+ */
+ static ImmutableAmmWithdraw.Builder builder() {
+ return ImmutableAmmWithdraw.builder();
+ }
+
+ /**
+ * A {@link AmmWithdrawFlags} for this transaction.
+ *
+ * @return A {@link AmmWithdrawFlags} for this transaction.
+ */
+ @JsonProperty("Flags")
+ AmmWithdrawFlags flags();
+
+ /**
+ * The definition for one of the assets in the AMM's pool.
+ *
+ * @return An {@link Issue}.
+ */
+ @JsonProperty("Asset")
+ Issue asset();
+
+ /**
+ * The definition for the other asset in the AMM's pool.
+ *
+ * @return An {@link Issue}.
+ */
+ @JsonProperty("Asset2")
+ Issue asset2();
+
+ /**
+ * The amount of one asset to deposit to the AMM. If present, this must match the type of one of the assets (tokens or
+ * XRP) in the AMM's pool.
+ *
+ * @return An optionally present {@link CurrencyAmount}.
+ */
+ @JsonProperty("Amount")
+ Optional amount();
+
+ /**
+ * The amount of another asset to add to the AMM. If present, this must match the type of the other asset in the AMM's
+ * pool and cannot be the same asset as Amount.
+ *
+ * @return An optionally present {@link CurrencyAmount}.
+ */
+ @JsonProperty("Amount2")
+ Optional amount2();
+
+ /**
+ * The maximum effective price, in the deposit asset, to pay for each LP Token received.
+ *
+ * @return An optionally present {@link CurrencyAmount}.
+ */
+ @JsonProperty("EPrice")
+ Optional effectivePrice();
+
+ /**
+ * How many of the AMM's LP Tokens to buy.
+ *
+ * @return An optionally present {@link IssuedCurrencyAmount}.
+ */
+ @JsonProperty("LPTokensIn")
+ Optional lpTokensIn();
+
+}
diff --git a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/Clawback.java b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/Clawback.java
new file mode 100644
index 000000000..8c5c3bdcf
--- /dev/null
+++ b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/Clawback.java
@@ -0,0 +1,55 @@
+package org.xrpl.xrpl4j.model.transactions;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import com.google.common.annotations.Beta;
+import org.immutables.value.Value;
+import org.immutables.value.Value.Immutable;
+import org.xrpl.xrpl4j.model.flags.TransactionFlags;
+
+/**
+ * Clawback an issued currency that exists on a Trustline.
+ *
+ * This class will be marked {@link Beta} until the Clawback amendment is enabled on mainnet. Its API is subject
+ * to change.
+ */
+@Immutable
+@JsonSerialize(as = ImmutableClawback.class)
+@JsonDeserialize(as = ImmutableClawback.class)
+@Beta
+public interface Clawback extends Transaction {
+
+ /**
+ * Construct a {@code Clawback} builder.
+ *
+ * @return An {@link ImmutableClawback.Builder}.
+ */
+ static ImmutableClawback.Builder builder() {
+ return ImmutableClawback.builder();
+ }
+
+ /**
+ * Set of {@link TransactionFlags}s for this {@link Clawback}, which only allows the
+ * {@code tfFullyCanonicalSig} flag, which is deprecated.
+ *
+ * @return Always {@link TransactionFlags#EMPTY}.
+ */
+ @JsonProperty("Flags")
+ @Value.Default
+ default TransactionFlags flags() {
+ return TransactionFlags.EMPTY;
+ }
+
+ /**
+ * Indicates the amount being clawed back, as well as the counterparty from which the amount is being clawed back
+ * from. This amount must not exceed the holder's balance and must be greater than zero. The issuer in this amount
+ * must not be the same as the source account of this transaction.
+ *
+ * @return An {@link IssuedCurrencyAmount} indicating the amount to clawback.
+ */
+ @JsonProperty("Amount")
+ IssuedCurrencyAmount amount();
+
+
+}
diff --git a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/EscrowFinish.java b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/EscrowFinish.java
index 379d6a833..9aa5d8464 100644
--- a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/EscrowFinish.java
+++ b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/EscrowFinish.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.
@@ -20,18 +20,28 @@
* =========================LICENSE_END==================================
*/
+import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.google.common.base.Preconditions;
+import com.google.common.io.BaseEncoding;
import com.google.common.primitives.UnsignedInteger;
import com.google.common.primitives.UnsignedLong;
import com.ripple.cryptoconditions.Condition;
+import com.ripple.cryptoconditions.CryptoConditionReader;
+import com.ripple.cryptoconditions.CryptoConditionWriter;
import com.ripple.cryptoconditions.Fulfillment;
+import com.ripple.cryptoconditions.der.DerEncodingException;
import org.immutables.value.Value;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import org.xrpl.xrpl4j.model.flags.TransactionFlags;
import org.xrpl.xrpl4j.model.immutables.FluentCompareTo;
+import org.xrpl.xrpl4j.model.transactions.AccountSet.AccountSetFlag;
+import java.util.Arrays;
+import java.util.Locale;
import java.util.Objects;
import java.util.Optional;
@@ -43,6 +53,8 @@
@JsonDeserialize(as = ImmutableEscrowFinish.class)
public interface EscrowFinish extends Transaction {
+ Logger logger = LoggerFactory.getLogger(EscrowFinish.class);
+
/**
* Construct a builder for this class.
*
@@ -62,6 +74,7 @@ static ImmutableEscrowFinish.Builder builder() {
* purposes.
*
* @return An {@link XrpCurrencyAmount} representing the computed fee.
+ *
* @see "https://xrpl.org/escrowfinish.html"
*/
static XrpCurrencyAmount computeFee(final XrpCurrencyAmount currentLedgerFeeDrops, final Fulfillment fulfillment) {
@@ -78,8 +91,8 @@ static XrpCurrencyAmount computeFee(final XrpCurrencyAmount currentLedgerFeeDrop
}
/**
- * Set of {@link TransactionFlags}s for this {@link EscrowFinish}, which only allows the
- * {@code tfFullyCanonicalSig} flag, which is deprecated.
+ * Set of {@link TransactionFlags}s for this {@link EscrowFinish}, which only allows the {@code tfFullyCanonicalSig}
+ * flag, which is deprecated.
*
* The value of the flags cannot be set manually, but exists for JSON serialization/deserialization only and for
* proper signature computation in rippled.
@@ -111,34 +124,204 @@ default TransactionFlags flags() {
/**
* Hex value matching the previously-supplied PREIMAGE-SHA-256 crypto-condition of the held payment.
*
+ *
If this field is empty, developers should check if {@link #conditionRawValue()} is also empty. If
+ * {@link #conditionRawValue()} is present, it means that the {@code "Condition"} field of the transaction was not a
+ * well-formed crypto-condition but was still present in a transaction on ledger.
+ *
* @return An {@link Optional} of type {@link Condition} containing the escrow condition.
*/
- @JsonProperty("Condition")
+ @JsonIgnore
Optional condition();
/**
- * Hex value of the PREIMAGE-SHA-256 crypto-condition fulfillment matching the held payment's {@code condition}.
+ * The raw, hex-encoded PREIMAGE-SHA-256 crypto-condition of the escrow.
+ *
+ * Developers should prefer setting {@link #condition()} and leaving this field empty when constructing a new
+ * {@link EscrowFinish}. This field is used to serialize and deserialize the {@code "Condition"} field in JSON, the
+ * XRPL will sometimes include an {@link EscrowFinish} in its ledger even if the crypto condition is malformed.
+ * Without this field, xrpl4j would fail to deserialize those transactions, as {@link #condition()} is typed as a
+ * {@link Condition}, which tries to decode the condition from DER.
+ *
+ * Note that a similar field does not exist on {@link EscrowCreate},
+ * {@link org.xrpl.xrpl4j.model.ledger.EscrowObject}, or
+ * {@link org.xrpl.xrpl4j.model.transactions.metadata.MetaEscrowObject} because {@link EscrowCreate}s with
+ * malformed conditions will never be included in a ledger by the XRPL. Because of this fact, an
+ * {@link org.xrpl.xrpl4j.model.ledger.EscrowObject} and
+ * {@link org.xrpl.xrpl4j.model.transactions.metadata.MetaEscrowObject} will also never contain a malformed
+ * crypto condition.
+ *
+ * @return An {@link Optional} {@link String} containing the hex-encoded PREIMAGE-SHA-256 condition.
+ */
+ @JsonProperty("Condition")
+ Optional conditionRawValue();
+
+ /**
+ * Hex value of the PREIMAGE-SHA-256 crypto-condition fulfillment matching the held payment's {@link #condition()}.
+ *
+ * If this field is empty, developers should check if {@link #fulfillmentRawValue()} is also empty. If
+ * {@link #fulfillmentRawValue()} is present, it means that the {@code "Fulfillment"} field of the transaction was not
+ * a well-formed crypto-condition fulfillment but was still present in a transaction on ledger.
*
* @return An {@link Optional} of type {@link Fulfillment} containing the fulfillment for the escrow's condition.
*/
- @JsonProperty("Fulfillment")
+ @JsonIgnore
Optional> fulfillment();
/**
- * Validate fields.
+ * The raw, hex-encoded value of the PREIMAGE-SHA-256 crypto-condition fulfillment matching the held payment's
+ * {@link #condition()}.
+ *
+ * Developers should prefer setting {@link #fulfillment()} and leaving this field empty when constructing a new
+ * {@link EscrowFinish}. This field is used to serialize and deserialize the {@code "Fulfillment"} field in JSON, the
+ * XRPL will sometimes include an {@link EscrowFinish} in its ledger even if the crypto fulfillment is malformed.
+ * Without this field, xrpl4j would fail to deserialize those transactions, as {@link #fulfillment()} is typed as a
+ * {@link Fulfillment}, which tries to decode the fulfillment from DER.
+ *
+ * @return An {@link Optional} {@link String} containing the hex-encoded PREIMAGE-SHA-256 fulfillment.
+ */
+ @JsonProperty("Fulfillment")
+ Optional fulfillmentRawValue();
+
+ /**
+ * Normalization method to try to get {@link #condition()} and {@link #conditionRawValue()} to match.
+ *
+ * If neither field is present, there is nothing to do.
+ * If both fields are present, there is nothing to do, but we will check that {@link #condition()}'s
+ * underlying value equals {@link #conditionRawValue()}.
+ * If {@link #condition()} is present but {@link #conditionRawValue()} is empty, we set
+ * {@link #conditionRawValue()} to the underlying value of {@link #condition()}.
+ * If {@link #condition()} is empty and {@link #conditionRawValue()} is present, we will set
+ * {@link #condition()} to the {@link Condition} representing the raw condition value, or leave
+ * {@link #condition()} empty if {@link #conditionRawValue()} is a malformed {@link Condition}.
+ *
+ * @return A normalized {@link EscrowFinish}.
*/
@Value.Check
- default void check() {
- fulfillment().ifPresent(f -> {
- UnsignedLong feeInDrops = fee().value();
- Preconditions.checkState(condition().isPresent(),
- "If a fulfillment is specified, the corresponding condition must also be specified.");
- Preconditions.checkState(FluentCompareTo.is(feeInDrops).greaterThanEqualTo(UnsignedLong.valueOf(330)),
- "If a fulfillment is specified, the fee must be set to 330 or greater.");
+ default EscrowFinish normalizeCondition() {
+ try {
+ if (!condition().isPresent() && !conditionRawValue().isPresent()) {
+ // If both are empty, nothing to do.
+ return this;
+ } else if (condition().isPresent() && conditionRawValue().isPresent()) {
+ // Both will be present if:
+ // 1. A developer set them both manually (in the builder)
+ // 2. This method has already been called.
+
+ // We should check that the condition()'s value matches the raw value.
+ Preconditions.checkState(
+ Arrays.equals(CryptoConditionWriter.writeCondition(condition().get()),
+ BaseEncoding.base16().decode(conditionRawValue().get())),
+ "condition and conditionRawValue should be equivalent if both are present."
+ );
+ return this;
+ } else if (condition().isPresent() && !conditionRawValue().isPresent()) {
+ // This can only happen if the developer only set condition() because condition() will never be set
+ // after deserializing from JSON. In this case, we need to set conditionRawValue to match setFlag.
+ return EscrowFinish.builder().from(this)
+ .conditionRawValue(BaseEncoding.base16().encode(CryptoConditionWriter.writeCondition(condition().get())))
+ .build();
+ } else { // condition is empty and conditionRawValue is present
+ // This can happen if:
+ // 1. A developer sets conditionRawValue manually in the builder
+ // 2. JSON has Condition and Jackson sets conditionRawValue
+
+ // In this case, we should try to read conditionRawValue to a Condition. If that fails, condition()
+ // will remain empty, otherwise we will set condition().
+ try {
+ Condition condition = CryptoConditionReader.readCondition(
+ BaseEncoding.base16().decode(conditionRawValue().get().toUpperCase(Locale.US))
+ );
+ return EscrowFinish.builder().from(this)
+ .condition(condition)
+ .build();
+ } catch (DerEncodingException | IllegalArgumentException e) {
+ logger.warn(
+ "EscrowFinish Condition was malformed. conditionRawValue() will contain the condition value, but " +
+ "condition() will be empty: {}",
+ e.getMessage(),
+ e
+ );
+ return this;
+ }
}
- );
- condition().ifPresent($ -> Preconditions.checkState(fulfillment().isPresent(),
- "If a condition is specified, the corresponding fulfillment must also be specified."));
+
+ } catch (DerEncodingException e) {
+ // This should never happen. CryptoconditionWriter.writeCondition errantly declares that it can throw
+ // a DerEncodingException, but nowhere in its implementation does it throw.
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Normalization method to try to get {@link #fulfillment()} and {@link #fulfillmentRawValue()} to match.
+ *
+ * If neither field is present, there is nothing to do.
+ * If both fields are present, there is nothing to do, but we will check that {@link #fulfillment()}'s
+ * underlying value equals {@link #fulfillmentRawValue()}.
+ * If {@link #fulfillment()} is present but {@link #fulfillmentRawValue()} is empty, we set
+ * {@link #fulfillmentRawValue()} to the underlying value of {@link #fulfillment()}.
+ * If {@link #fulfillment()} is empty and {@link #fulfillmentRawValue()} is present, we will set
+ * {@link #fulfillment()} to the {@link Fulfillment} representing the raw fulfillment value, or leave
+ * {@link #fulfillment()} empty if {@link #fulfillmentRawValue()} is a malformed {@link Fulfillment}.
+ *
+ * @return A normalized {@link EscrowFinish}.
+ */
+ @Value.Check
+ default EscrowFinish normalizeFulfillment() {
+ try {
+ if (!fulfillment().isPresent() && !fulfillmentRawValue().isPresent()) {
+ // If both are empty, nothing to do.
+ return this;
+ } else if (fulfillment().isPresent() && fulfillmentRawValue().isPresent()) {
+ // Both will be present if:
+ // 1. A developer set them both manually (in the builder)
+ // 2. This method has already been called.
+
+ // We should check that the fulfillment()'s value matches the raw value.
+ Preconditions.checkState(
+ Arrays.equals(CryptoConditionWriter.writeFulfillment(fulfillment().get()),
+ BaseEncoding.base16().decode(fulfillmentRawValue().get())),
+ "fulfillment and fulfillmentRawValue should be equivalent if both are present."
+ );
+ return this;
+ } else if (fulfillment().isPresent() && !fulfillmentRawValue().isPresent()) {
+ // This can only happen if the developer only set fulfillment() because fulfillment() will never be set
+ // after deserializing from JSON. In this case, we need to set fulfillmentRawValue to match setFlag.
+ return EscrowFinish.builder().from(this)
+ .fulfillmentRawValue(
+ BaseEncoding.base16().encode(CryptoConditionWriter.writeFulfillment(fulfillment().get()))
+ )
+ .build();
+ } else { // fulfillment is empty and fulfillmentRawValue is present
+ // This can happen if:
+ // 1. A developer sets fulfillmentRawValue manually in the builder
+ // 2. JSON has Condition and Jackson sets fulfillmentRawValue
+
+ // In this case, we should try to read fulfillmentRawValue to a Condition. If that fails, fulfillment()
+ // will remain empty, otherwise we will set fulfillment().
+ try {
+ Fulfillment> fulfillment = CryptoConditionReader.readFulfillment(
+ BaseEncoding.base16().decode(fulfillmentRawValue().get().toUpperCase(Locale.US))
+ );
+ return EscrowFinish.builder().from(this)
+ .fulfillment(fulfillment)
+ .build();
+ } catch (DerEncodingException | IllegalArgumentException e) {
+ logger.warn(
+ "EscrowFinish Fulfillment was malformed. fulfillmentRawValue() will contain the fulfillment value, " +
+ "but fulfillment() will be empty: {}",
+ e.getMessage(),
+ e
+ );
+ return this;
+ }
+ }
+
+ } catch (DerEncodingException e) {
+ // This should never happen. CryptoconditionWriter.writeCondition errantly declares that it can throw
+ // a DerEncodingException, but nowhere in its implementation does it throw.
+ throw new RuntimeException(e);
+ }
}
}
diff --git a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/SignerListSet.java b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/SignerListSet.java
index e6bb6eac3..9128e2f7f 100644
--- a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/SignerListSet.java
+++ b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/SignerListSet.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,8 +31,8 @@
import java.util.List;
/**
- * The SignerListSet transaction creates, replaces, or removes a list of signers that can be used
- * to multi-sign a {@link Transaction}.
+ * The SignerListSet transaction creates, replaces, or removes a list of signers that can be used to multi-sign a
+ * {@link Transaction}.
*/
@Value.Immutable
@JsonSerialize(as = ImmutableSignerListSet.class)
@@ -49,8 +49,8 @@ static ImmutableSignerListSet.Builder builder() {
}
/**
- * Set of {@link TransactionFlags}s for this {@link SignerListSet}, which only allows the
- * {@code tfFullyCanonicalSig} flag, which is deprecated.
+ * Set of {@link TransactionFlags}s for this {@link SignerListSet}, which only allows the {@code tfFullyCanonicalSig}
+ * flag, which is deprecated.
*
* The value of the flags cannot be set manually, but exists for JSON serialization/deserialization only and for
* proper signature computation in rippled.
@@ -64,8 +64,8 @@ default TransactionFlags flags() {
}
/**
- * A target number for the signer weights. A multi-signature from this list is valid only if the sum weights of
- * the signatures provided is greater than or equal to this value. To delete a signer list, use the value 0.
+ * A target number for the signer weights. A multi-signature from this list is valid only if the sum weights of the
+ * signatures provided is greater than or equal to this value. To delete a signer list, use the value 0.
*
* @return An {@link UnsignedInteger} representing the singer quorum.
*/
@@ -73,10 +73,10 @@ default TransactionFlags flags() {
UnsignedInteger signerQuorum();
/**
- * (Omitted when deleting) Array of {@link org.xrpl.xrpl4j.model.ledger.SignerEntry} objects, indicating the
- * addresses and weights of signers in this list. This signer list must have at least 1 member and no more
- * than 8 members. No {@link Address} may appear more than once in the list, nor may the {@link #account()}
- * submitting the transaction appear in the list.
+ * (Omitted when deleting) Array of {@link org.xrpl.xrpl4j.model.ledger.SignerEntry} objects, indicating the addresses
+ * and weights of signers in this list. This signer list must have at least 1 member and no more than 8 members. No
+ * {@link Address} may appear more than once in the list, nor may the {@link #account()} submitting the transaction
+ * appear in the list.
*
* @return A {@link List} of {@link SignerEntryWrapper}s.
*/
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 3eca5a090..1d756cf96 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
@@ -71,6 +71,13 @@ public interface Transaction {
.put(ImmutableTrustSet.class, TransactionType.TRUST_SET)
.put(ImmutableTicketCreate.class, TransactionType.TICKET_CREATE)
.put(ImmutableUnlModify.class, TransactionType.UNL_MODIFY)
+ .put(ImmutableAmmBid.class, TransactionType.AMM_BID)
+ .put(ImmutableAmmCreate.class, TransactionType.AMM_CREATE)
+ .put(ImmutableAmmDeposit.class, TransactionType.AMM_DEPOSIT)
+ .put(ImmutableAmmVote.class, TransactionType.AMM_VOTE)
+ .put(ImmutableAmmWithdraw.class, TransactionType.AMM_WITHDRAW)
+ .put(ImmutableAmmDelete.class, TransactionType.AMM_DELETE)
+ .put(ImmutableClawback.class, TransactionType.CLAWBACK)
.build();
/**
diff --git a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/TransactionType.java b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/TransactionType.java
index 1d01502ae..87a293c00 100644
--- a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/TransactionType.java
+++ b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/TransactionType.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.
@@ -21,6 +21,7 @@
*/
import com.fasterxml.jackson.annotation.JsonValue;
+import com.google.common.annotations.Beta;
/**
* Enumeration of the types of Transactions on the XRP Ledger.
@@ -160,7 +161,70 @@ public enum TransactionType {
/**
* The {@link TransactionType} for the {@link UnlModify} transaction.
*/
- UNL_MODIFY("UNLModify");
+ UNL_MODIFY("UNLModify"),
+
+ /**
+ * The {@link TransactionType} for the {@link Clawback} transaction.
+ *
+ *
This constant will be marked {@link Beta} until the Clawback amendment is enabled on mainnet. Its API is subject
+ * to change.
+ */
+ @Beta
+ CLAWBACK("Clawback"),
+
+ /**
+ * The {@link TransactionType} for the {@link AmmBid} transaction.
+ *
+ * This constant will be marked {@link Beta} until the AMM amendment is enabled on mainnet. Its API is subject to
+ * change.
+ */
+ @Beta
+ AMM_BID("AMMBid"),
+
+ /**
+ * The {@link TransactionType} for the {@link AmmCreate} transaction.
+ *
+ * This constant will be marked {@link Beta} until the AMM amendment is enabled on mainnet. Its API is subject to
+ * change.
+ */
+ @Beta
+ AMM_CREATE("AMMCreate"),
+
+ /**
+ * The {@link TransactionType} for the {@link AmmDeposit} transaction.
+ *
+ * This constant will be marked {@link Beta} until the AMM amendment is enabled on mainnet. Its API is subject to
+ * change.
+ */
+ @Beta
+ AMM_DEPOSIT("AMMDeposit"),
+
+ /**
+ * The {@link TransactionType} for the {@link AmmVote} transaction.
+ *
+ * This constant will be marked {@link Beta} until the AMM amendment is enabled on mainnet. Its API is subject to
+ * change.
+ */
+ @Beta
+ AMM_VOTE("AMMVote"),
+
+ /**
+ * The {@link TransactionType} for the {@link AmmWithdraw} transaction.
+ *
+ * This constant will be marked {@link Beta} until the AMM amendment is enabled on mainnet. Its API is subject to
+ * change.
+ */
+ @Beta
+ AMM_WITHDRAW("AMMWithdraw"),
+
+ /**
+ * The {@link TransactionType} for the {@link AmmDelete} transaction.
+ *
+ * This constant will be marked {@link Beta} until the AMM amendment is enabled on mainnet. Its API is subject to
+ * change.
+ */
+ @Beta
+ AMM_DELETE("AMMDelete");
private final String value;
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 3e887b29a..d746b27f7 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
@@ -23,6 +23,7 @@
import com.fasterxml.jackson.annotation.JsonRawValue;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import com.google.common.annotations.Beta;
import com.google.common.base.Preconditions;
import com.google.common.io.BaseEncoding;
import com.google.common.primitives.UnsignedInteger;
@@ -42,8 +43,12 @@
import org.xrpl.xrpl4j.model.jackson.modules.NfTokenIdDeserializer;
import org.xrpl.xrpl4j.model.jackson.modules.NfTokenIdSerializer;
import org.xrpl.xrpl4j.model.jackson.modules.NfTokenUriSerializer;
+import org.xrpl.xrpl4j.model.jackson.modules.TradingFeeDeserializer;
+import org.xrpl.xrpl4j.model.jackson.modules.TradingFeeSerializer;
import org.xrpl.xrpl4j.model.jackson.modules.TransferFeeDeserializer;
import org.xrpl.xrpl4j.model.jackson.modules.TransferFeeSerializer;
+import org.xrpl.xrpl4j.model.jackson.modules.VoteWeightDeserializer;
+import org.xrpl.xrpl4j.model.jackson.modules.VoteWeightSerializer;
import org.xrpl.xrpl4j.model.jackson.modules.XrpCurrencyAmountDeserializer;
import org.xrpl.xrpl4j.model.jackson.modules.XrpCurrencyAmountSerializer;
@@ -339,8 +344,8 @@ 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.
+ * 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
@@ -412,4 +417,77 @@ public static NetworkId of(long networkId) {
return NetworkId.of(UnsignedInteger.valueOf(networkId));
}
}
+
+ /**
+ * A wrapped {@link com.google.common.primitives.UnsignedInteger} containing the TransferFee.
+ *
+ *
This class will be marked {@link com.google.common.annotations.Beta} until the AMM amendment is enabled on
+ * mainnet. Its API is subject to change.
+ */
+ @Value.Immutable
+ @Wrapped
+ @JsonSerialize(as = TradingFee.class, using = TradingFeeSerializer.class)
+ @JsonDeserialize(as = TradingFee.class, using = TradingFeeDeserializer.class)
+ @Beta
+ abstract static class _TradingFee extends Wrapper implements Serializable {
+
+ @Override
+ public String toString() {
+ return this.value().toString();
+ }
+
+ /**
+ * Construct {@link TradingFee} as a percentage value.
+ *
+ * @param percent The trading fee, as a {@link BigDecimal}.
+ *
+ * @return A {@link TradingFee}.
+ */
+ public static TradingFee ofPercent(BigDecimal percent) {
+ Preconditions.checkArgument(
+ Math.max(0, percent.stripTrailingZeros().scale()) <= 3,
+ "Percent value should have a maximum of 3 decimal places."
+ );
+ return TradingFee.of(UnsignedInteger.valueOf(percent.scaleByPowerOfTen(3).toBigIntegerExact()));
+ }
+
+ /**
+ * Get the {@link TradingFee} as a {@link BigDecimal}.
+ *
+ * @return A {@link BigDecimal}.
+ */
+ public BigDecimal bigDecimalValue() {
+ return BigDecimal.valueOf(value().longValue(), 3);
+ }
+
+ }
+
+ /**
+ * A wrapped {@link com.google.common.primitives.UnsignedInteger} containing the VoteWeight.
+ *
+ * This class will be marked {@link com.google.common.annotations.Beta} until the AMM amendment is enabled on
+ * mainnet. Its API is subject to change.
+ */
+ @Value.Immutable
+ @Wrapped
+ @JsonSerialize(as = VoteWeight.class, using = VoteWeightSerializer.class)
+ @JsonDeserialize(as = VoteWeight.class, using = VoteWeightDeserializer.class)
+ @Beta
+ abstract static class _VoteWeight extends Wrapper implements Serializable {
+
+ @Override
+ public String toString() {
+ return this.value().toString();
+ }
+
+ /**
+ * Get the {@link VoteWeight} as a {@link BigDecimal}.
+ *
+ * @return A {@link BigDecimal}.
+ */
+ public BigDecimal bigDecimalValue() {
+ return BigDecimal.valueOf(value().longValue(), 3);
+ }
+
+ }
}
diff --git a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/metadata/AffectedNode.java b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/metadata/AffectedNode.java
index 16de8dc46..43a12427a 100644
--- a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/metadata/AffectedNode.java
+++ b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/metadata/AffectedNode.java
@@ -22,6 +22,7 @@ public interface AffectedNode {
* @param createdNodeConsumer A {@link Consumer} that is called if this instance is of type {@link CreatedNode}.
* @param modifiedNodeConsumer A {@link Consumer} that is called if this instance is of type {@link ModifiedNode}.
* @param deletedNodeConsumer A {@link Consumer} that is called if this instance is of type {@link DeletedNode}.
+ * @param An instance that extends {@link MetaLedgerObject}.
*/
default void handle(
final Consumer> createdNodeConsumer,
@@ -50,7 +51,9 @@ default void handle(
* @param modifiedNodeMapper A {@link Function} that is called if this instance is of type {@link ModifiedNode}.
* @param deletedNodeMapper A {@link Function} that is called if this instance is of type {@link DeletedNode}.
* @param The type of object to return after mapping.
- * @return A {@link R} that is constructed by the appropriate mapper function.
+ * @param An instance that extends {@link MetaLedgerObject}.
+ *
+ * @return An {@link R} that is constructed by the appropriate mapper function.
*/
default R map(
final Function, R> createdNodeMapper,
diff --git a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/metadata/MetaAmmObject.java b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/metadata/MetaAmmObject.java
new file mode 100644
index 000000000..0274920f5
--- /dev/null
+++ b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/metadata/MetaAmmObject.java
@@ -0,0 +1,116 @@
+package org.xrpl.xrpl4j.model.transactions.metadata;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import com.google.common.annotations.Beta;
+import org.immutables.value.Value;
+import org.immutables.value.Value.Immutable;
+import org.xrpl.xrpl4j.model.flags.Flags;
+import org.xrpl.xrpl4j.model.ledger.AuctionSlot;
+import org.xrpl.xrpl4j.model.ledger.Issue;
+import org.xrpl.xrpl4j.model.ledger.VoteEntryWrapper;
+import org.xrpl.xrpl4j.model.transactions.Address;
+import org.xrpl.xrpl4j.model.transactions.IssuedCurrencyAmount;
+import org.xrpl.xrpl4j.model.transactions.TradingFee;
+
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+/**
+ * Represents an AMM ledger object, which describes a single Automated Market Maker instance.
+ *
+ * This class will be marked {@link Beta} until the AMM amendment is enabled on mainnet. Its API is subject to
+ * change.
+ */
+@Immutable
+@JsonSerialize(as = ImmutableMetaAmmObject.class)
+@JsonDeserialize(as = ImmutableMetaAmmObject.class)
+@Beta
+public interface MetaAmmObject extends MetaLedgerObject {
+
+ /**
+ * A bit-map of boolean flags. No flags are defined for {@link MetaAmmObject}, so this value is always 0.
+ *
+ * @return Always {@link Flags#UNSET}.
+ */
+ @JsonProperty("Flags")
+ @Value.Derived
+ default Flags flags() {
+ return Flags.UNSET;
+ }
+
+ /**
+ * The definition for one of the two assets this AMM holds.
+ *
+ * @return An {@link Issue}.
+ */
+ @JsonProperty("Asset")
+ Optional asset();
+
+ /**
+ * The definition for the other asset this AMM holds.
+ *
+ * @return An {@link Issue}.
+ */
+ @JsonProperty("Asset2")
+ Optional asset2();
+
+ /**
+ * The address of the special account that holds this AMM's assets.
+ *
+ * @return An {@link Address}.
+ */
+ @JsonProperty("Account")
+ Optional account();
+
+ /**
+ * Details of the current owner of the auction slot.
+ *
+ * @return A {@link MetaAuctionSlot}.
+ */
+ @JsonProperty("AuctionSlot")
+ Optional auctionSlot();
+
+ /**
+ * The total outstanding balance of liquidity provider tokens from this AMM instance. The holders of these tokens can
+ * vote on the AMM's trading fee in proportion to their holdings, or redeem the tokens for a share of the AMM's assets
+ * which grows with the trading fees collected.
+ *
+ * @return An {@link IssuedCurrencyAmount}.
+ */
+ @JsonProperty("LPTokenBalance")
+ Optional lpTokenBalance();
+
+ /**
+ * The percentage fee to be charged for trades against this AMM instance, in units of 1/10,000. The maximum value is
+ * 1000, for a 1% fee.
+ *
+ * @return A {@link TradingFee}.
+ */
+ @JsonProperty("TradingFee")
+ Optional tradingFee();
+
+ /**
+ * A list of vote objects, representing votes on the pool's trading fee.
+ *
+ * @return A {@link List} of {@link MetaVoteEntryWrapper}s.
+ */
+ @JsonProperty("VoteSlots")
+ List voteSlots();
+
+ /**
+ * Unwraps the {@link MetaVoteEntryWrapper}s in {@link #voteSlots()} for easier access to {@link MetaVoteEntry}s.
+ *
+ * @return A {@link List} of {@link MetaVoteEntry}.
+ */
+ @JsonIgnore
+ @Value.Derived
+ default List voteSlotsUnwrapped() {
+ return voteSlots().stream()
+ .map(MetaVoteEntryWrapper::voteEntry)
+ .collect(Collectors.toList());
+ }
+}
diff --git a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/metadata/MetaAuctionSlot.java b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/metadata/MetaAuctionSlot.java
new file mode 100644
index 000000000..f61c74c66
--- /dev/null
+++ b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/metadata/MetaAuctionSlot.java
@@ -0,0 +1,89 @@
+package org.xrpl.xrpl4j.model.transactions.metadata;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import com.google.common.annotations.Beta;
+import com.google.common.primitives.UnsignedInteger;
+import org.immutables.value.Value;
+import org.immutables.value.Value.Immutable;
+import org.xrpl.xrpl4j.model.ledger.AmmObject;
+import org.xrpl.xrpl4j.model.ledger.ImmutableAuctionSlot;
+import org.xrpl.xrpl4j.model.transactions.Address;
+import org.xrpl.xrpl4j.model.transactions.IssuedCurrencyAmount;
+import org.xrpl.xrpl4j.model.transactions.TradingFee;
+
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+/**
+ * Represents an AuctionSlot object in an {@link AmmObject}, containing details of the current owner of the auction
+ * slot.
+ *
+ * This class will be marked {@link Beta} until the AMM amendment is enabled on mainnet. Its API is subject to
+ * change.
+ */
+@Immutable
+@JsonSerialize(as = ImmutableMetaAuctionSlot.class)
+@JsonDeserialize(as = ImmutableMetaAuctionSlot.class)
+@Beta
+public interface MetaAuctionSlot {
+
+ /**
+ * The current owner of this auction slot.
+ *
+ * @return An {@link Address}.
+ */
+ @JsonProperty("Account")
+ Optional account();
+
+ /**
+ * A list of at most 4 additional accounts that are authorized to trade at the discounted fee for this AMM instance.
+ *
+ * @return A {@link List} of {@link MetaAuthAccountWrapper}s.
+ */
+ @JsonProperty("AuthAccounts")
+ List authAccounts();
+
+ /**
+ * Extracts all the addresses found in the {@link MetaAuthAccount}s found in {@link #authAccounts()}.
+ *
+ * @return A {@link List} of {@link Address}.
+ */
+ @JsonIgnore
+ @Value.Derived
+ default List authAccountsAddresses() {
+ return authAccounts().stream()
+ .map(MetaAuthAccountWrapper::authAccount)
+ .map(MetaAuthAccount::account)
+ .collect(Collectors.toList());
+ }
+
+ /**
+ * The trading fee to be charged to the auction owner. By default this is 0, meaning that the auction owner can trade
+ * at no fee instead of the standard fee for this AMM.
+ *
+ * @return A {@link TradingFee}.
+ */
+ @JsonProperty("DiscountedFee")
+ Optional discountedFee();
+
+ /**
+ * The amount the auction owner paid to win this slot, in LP Tokens.
+ *
+ * @return An {@link IssuedCurrencyAmount}.
+ */
+ @JsonProperty("Price")
+ Optional price();
+
+ /**
+ * The time when this slot expires, in seconds since the Ripple Epoch.
+ *
+ * @return An {@link UnsignedInteger}
+ */
+ @JsonProperty("Expiration")
+ Optional expiration();
+
+}
diff --git a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/metadata/MetaAuthAccount.java b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/metadata/MetaAuthAccount.java
new file mode 100644
index 000000000..5f997cad4
--- /dev/null
+++ b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/metadata/MetaAuthAccount.java
@@ -0,0 +1,32 @@
+package org.xrpl.xrpl4j.model.transactions.metadata;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import com.google.common.annotations.Beta;
+import org.immutables.value.Value;
+import org.immutables.value.Value.Immutable;
+import org.xrpl.xrpl4j.model.ledger.ImmutableAuthAccount;
+import org.xrpl.xrpl4j.model.transactions.Address;
+
+/**
+ * An account that is authorized to trade at the discounted fee for an AMM instance.
+ *
+ * This class will be marked {@link Beta} until the AMM amendment is enabled on mainnet. Its API is subject to
+ * change.
+ */
+@Immutable
+@JsonSerialize(as = ImmutableMetaAuthAccount.class)
+@JsonDeserialize(as = ImmutableMetaAuthAccount.class)
+@Beta
+public interface MetaAuthAccount {
+
+ /**
+ * The address of the account.
+ *
+ * @return An {@link Address}.
+ */
+ @JsonProperty("Account")
+ Address account();
+
+}
diff --git a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/metadata/MetaAuthAccountWrapper.java b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/metadata/MetaAuthAccountWrapper.java
new file mode 100644
index 000000000..fe1c190f6
--- /dev/null
+++ b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/metadata/MetaAuthAccountWrapper.java
@@ -0,0 +1,31 @@
+package org.xrpl.xrpl4j.model.transactions.metadata;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import com.google.common.annotations.Beta;
+import org.immutables.value.Value;
+import org.immutables.value.Value.Immutable;
+import org.xrpl.xrpl4j.model.ledger.ImmutableAuthAccountWrapper;
+
+/**
+ * A wrapper around {@link MetaAuthAccount}s.
+ *
+ * This class will be marked {@link Beta} until the AMM amendment is enabled on mainnet. Its API is subject to
+ * change.
+ */
+@Immutable
+@JsonSerialize(as = ImmutableMetaAuthAccountWrapper.class)
+@JsonDeserialize(as = ImmutableMetaAuthAccountWrapper.class)
+@Beta
+public interface MetaAuthAccountWrapper {
+
+ /**
+ * An {@link MetaAuthAccount}.
+ *
+ * @return An {@link MetaAuthAccount}.
+ */
+ @JsonProperty("AuthAccount")
+ MetaAuthAccount authAccount();
+
+}
diff --git a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/metadata/MetaLedgerEntryType.java b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/metadata/MetaLedgerEntryType.java
index b060db762..f1f07f9fd 100644
--- a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/metadata/MetaLedgerEntryType.java
+++ b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/metadata/MetaLedgerEntryType.java
@@ -25,6 +25,7 @@ public interface MetaLedgerEntryType {
MetaLedgerEntryType SIGNER_LIST = MetaLedgerEntryType.of("SignerList");
MetaLedgerEntryType TICKET = MetaLedgerEntryType.of("Ticket");
MetaLedgerEntryType NFTOKEN_PAGE = MetaLedgerEntryType.of("NFTokenPage");
+ MetaLedgerEntryType AMM = MetaLedgerEntryType.of("AMM");
/**
* Construct a new {@link MetaLedgerEntryType} from a {@link String}.
diff --git a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/metadata/MetaVoteEntry.java b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/metadata/MetaVoteEntry.java
new file mode 100644
index 000000000..4e529e71d
--- /dev/null
+++ b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/metadata/MetaVoteEntry.java
@@ -0,0 +1,52 @@
+package org.xrpl.xrpl4j.model.transactions.metadata;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import com.google.common.annotations.Beta;
+import org.immutables.value.Value;
+import org.immutables.value.Value.Immutable;
+import org.xrpl.xrpl4j.model.ledger.ImmutableVoteEntry;
+import org.xrpl.xrpl4j.model.transactions.Address;
+import org.xrpl.xrpl4j.model.transactions.TradingFee;
+import org.xrpl.xrpl4j.model.transactions.VoteWeight;
+
+import java.util.Optional;
+
+/**
+ * Describes a vote for the trading fee on an AMM by an LP.
+ *
+ * This class will be marked {@link Beta} until the AMM amendment is enabled on mainnet. Its API is subject to
+ * change.
+ */
+@Immutable
+@JsonSerialize(as = ImmutableMetaVoteEntry.class)
+@JsonDeserialize(as = ImmutableMetaVoteEntry.class)
+@Beta
+public interface MetaVoteEntry {
+
+ /**
+ * The address of the LP who voted.
+ *
+ * @return An {@link Address}.
+ */
+ @JsonProperty("Account")
+ Optional account();
+
+ /**
+ * The trading fee that the LP voted for.
+ *
+ * @return A {@link TradingFee}.
+ */
+ @JsonProperty("TradingFee")
+ Optional tradingFee();
+
+ /**
+ * The weight of the LP's vote.
+ *
+ * @return The {@link VoteWeight}.
+ */
+ @JsonProperty("VoteWeight")
+ Optional voteWeight();
+
+}
diff --git a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/metadata/MetaVoteEntryWrapper.java b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/metadata/MetaVoteEntryWrapper.java
new file mode 100644
index 000000000..b92551f0c
--- /dev/null
+++ b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/metadata/MetaVoteEntryWrapper.java
@@ -0,0 +1,31 @@
+package org.xrpl.xrpl4j.model.transactions.metadata;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import com.google.common.annotations.Beta;
+import org.immutables.value.Value;
+import org.immutables.value.Value.Immutable;
+import org.xrpl.xrpl4j.model.ledger.ImmutableVoteEntryWrapper;
+
+/**
+ * A wrapper around a {@link MetaVoteEntry}.
+ *
+ * This class will be marked {@link Beta} until the AMM amendment is enabled on mainnet. Its API is subject to
+ * change.
+ */
+@Immutable
+@JsonSerialize(as = ImmutableMetaVoteEntryWrapper.class)
+@JsonDeserialize(as = ImmutableMetaVoteEntryWrapper.class)
+@Beta
+public interface MetaVoteEntryWrapper {
+
+ /**
+ * A {@link MetaVoteEntry}.
+ *
+ * @return A {@link MetaVoteEntry}.
+ */
+ @JsonProperty("VoteEntry")
+ MetaVoteEntry voteEntry();
+
+}
diff --git a/xrpl4j-core/src/main/resources/definitions.json b/xrpl4j-core/src/main/resources/definitions.json
index 1911bd47a..b6b48f440 100644
--- a/xrpl4j-core/src/main/resources/definitions.json
+++ b/xrpl4j-core/src/main/resources/definitions.json
@@ -22,6 +22,7 @@
"UInt384": 22,
"UInt512": 23,
"Issue": 24,
+ "XChainBridge": 25,
"Transaction": 10001,
"LedgerEntry": 10002,
"Validation": 10003,
@@ -35,8 +36,11 @@
"Ticket": 84,
"SignerList": 83,
"Offer": 111,
+ "Bridge": 105,
"LedgerHashes": 104,
"Amendments": 102,
+ "XChainOwnedClaimID": 113,
+ "XChainOwnedCreateAccountClaimID": 116,
"FeeSettings": 115,
"Escrow": 117,
"PayChannel": 120,
@@ -233,6 +237,16 @@
"type": "UInt8"
}
],
+ [
+ "WasLockingChainSend",
+ {
+ "nth": 19,
+ "isVLEncoded": false,
+ "isSerialized": true,
+ "isSigningField": true,
+ "type": "UInt8"
+ }
+ ],
[
"LedgerEntryType",
{
@@ -983,6 +997,36 @@
"type": "UInt64"
}
],
+ [
+ "XChainClaimID",
+ {
+ "nth": 20,
+ "isVLEncoded": false,
+ "isSerialized": true,
+ "isSigningField": true,
+ "type": "UInt64"
+ }
+ ],
+ [
+ "XChainAccountCreateCount",
+ {
+ "nth": 21,
+ "isVLEncoded": false,
+ "isSerialized": true,
+ "isSigningField": true,
+ "type": "UInt64"
+ }
+ ],
+ [
+ "XChainAccountClaimCount",
+ {
+ "nth": 22,
+ "isVLEncoded": false,
+ "isSerialized": true,
+ "isSigningField": true,
+ "type": "UInt64"
+ }
+ ],
[
"EmailHash",
{
@@ -1583,6 +1627,26 @@
"type": "Amount"
}
],
+ [
+ "SignatureReward",
+ {
+ "nth": 29,
+ "isVLEncoded": false,
+ "isSerialized": true,
+ "isSigningField": true,
+ "type": "Amount"
+ }
+ ],
+ [
+ "MinAccountCreateAmount",
+ {
+ "nth": 30,
+ "isVLEncoded": false,
+ "isSerialized": true,
+ "isSigningField": true,
+ "type": "Amount"
+ }
+ ],
[
"LPTokenBalance",
{
@@ -1933,6 +1997,66 @@
"type": "AccountID"
}
],
+ [
+ "OtherChainSource",
+ {
+ "nth": 18,
+ "isVLEncoded": true,
+ "isSerialized": true,
+ "isSigningField": true,
+ "type": "AccountID"
+ }
+ ],
+ [
+ "OtherChainDestination",
+ {
+ "nth": 19,
+ "isVLEncoded": true,
+ "isSerialized": true,
+ "isSigningField": true,
+ "type": "AccountID"
+ }
+ ],
+ [
+ "AttestationSignerAccount",
+ {
+ "nth": 20,
+ "isVLEncoded": true,
+ "isSerialized": true,
+ "isSigningField": true,
+ "type": "AccountID"
+ }
+ ],
+ [
+ "AttestationRewardAccount",
+ {
+ "nth": 21,
+ "isVLEncoded": true,
+ "isSerialized": true,
+ "isSigningField": true,
+ "type": "AccountID"
+ }
+ ],
+ [
+ "LockingChainDoor",
+ {
+ "nth": 22,
+ "isVLEncoded": true,
+ "isSerialized": true,
+ "isSigningField": true,
+ "type": "AccountID"
+ }
+ ],
+ [
+ "IssuingChainDoor",
+ {
+ "nth": 23,
+ "isVLEncoded": true,
+ "isSerialized": true,
+ "isSigningField": true,
+ "type": "AccountID"
+ }
+ ],
[
"Indexes",
{
@@ -1983,6 +2107,26 @@
"type": "PathSet"
}
],
+ [
+ "LockingChainIssue",
+ {
+ "nth": 1,
+ "isVLEncoded": false,
+ "isSerialized": true,
+ "isSigningField": true,
+ "type": "Issue"
+ }
+ ],
+ [
+ "IssuingChainIssue",
+ {
+ "nth": 2,
+ "isVLEncoded": false,
+ "isSerialized": true,
+ "isSigningField": true,
+ "type": "Issue"
+ }
+ ],
[
"Asset",
{
@@ -2003,6 +2147,16 @@
"type": "Issue"
}
],
+ [
+ "XChainBridge",
+ {
+ "nth": 1,
+ "isVLEncoded": false,
+ "isSerialized": true,
+ "isSigningField": true,
+ "type": "XChainBridge"
+ }
+ ],
[
"TransactionMetaData",
{
@@ -2243,6 +2397,46 @@
"type": "STObject"
}
],
+ [
+ "XChainClaimProofSig",
+ {
+ "nth": 28,
+ "isVLEncoded": false,
+ "isSerialized": true,
+ "isSigningField": true,
+ "type": "STObject"
+ }
+ ],
+ [
+ "XChainCreateAccountProofSig",
+ {
+ "nth": 29,
+ "isVLEncoded": false,
+ "isSerialized": true,
+ "isSigningField": true,
+ "type": "STObject"
+ }
+ ],
+ [
+ "XChainClaimAttestationCollectionElement",
+ {
+ "nth": 30,
+ "isVLEncoded": false,
+ "isSerialized": true,
+ "isSigningField": true,
+ "type": "STObject"
+ }
+ ],
+ [
+ "XChainCreateAccountAttestationCollectionElement",
+ {
+ "nth": 31,
+ "isVLEncoded": false,
+ "isSerialized": true,
+ "isSigningField": true,
+ "type": "STObject"
+ }
+ ],
[
"Signers",
{
@@ -2393,6 +2587,26 @@
"type": "STArray"
}
],
+ [
+ "XChainClaimAttestations",
+ {
+ "nth": 21,
+ "isVLEncoded": false,
+ "isSerialized": true,
+ "isSigningField": true,
+ "type": "STArray"
+ }
+ ],
+ [
+ "XChainCreateAccountAttestations",
+ {
+ "nth": 22,
+ "isVLEncoded": false,
+ "isSerialized": true,
+ "isSigningField": true,
+ "type": "STArray"
+ }
+ ],
[
"AuthAccounts",
{
@@ -2461,6 +2675,12 @@
"temSEQ_AND_TICKET": -263,
"temBAD_NFTOKEN_TRANSFER_FEE": -262,
"temBAD_AMM_TOKENS": -261,
+ "temXCHAIN_EQUAL_DOOR_ACCOUNTS": -260,
+ "temXCHAIN_BAD_PROOF": -259,
+ "temXCHAIN_BRIDGE_BAD_ISSUES": -258,
+ "temXCHAIN_BRIDGE_NONDOOR_OWNER": -257,
+ "temXCHAIN_BRIDGE_BAD_MIN_ACCOUNT_CREATE_AMOUNT": -256,
+ "temXCHAIN_BRIDGE_BAD_REWARD_AMOUNT": -255,
"tefFAILURE": -199,
"tefALREADY": -198,
@@ -2497,6 +2717,7 @@
"terQUEUED": -89,
"terPRE_TICKET": -88,
"terNO_AMM": -87,
+ "terSUBMITTED": -86,
"tesSUCCESS": 0,
@@ -2538,6 +2759,7 @@
"tecKILLED": 150,
"tecHAS_OBLIGATIONS": 151,
"tecTOO_SOON": 152,
+ "tecHOOK_ERROR": 153,
"tecMAX_SEQUENCE_REACHED": 154,
"tecNO_SUITABLE_NFTOKEN_PAGE": 155,
"tecNFTOKEN_BUY_SELL_MISMATCH": 156,
@@ -2549,7 +2771,28 @@
"tecUNFUNDED_AMM": 162,
"tecAMM_BALANCE": 163,
"tecAMM_FAILED": 164,
- "tecAMM_INVALID_TOKENS": 165
+ "tecAMM_INVALID_TOKENS": 165,
+ "tecAMM_EMPTY": 166,
+ "tecAMM_NOT_EMPTY": 167,
+ "tecAMM_ACCOUNT": 168,
+ "tecINCOMPLETE": 169,
+ "tecXCHAIN_BAD_TRANSFER_ISSUE": 170,
+ "tecXCHAIN_NO_CLAIM_ID": 171,
+ "tecXCHAIN_BAD_CLAIM_ID": 172,
+ "tecXCHAIN_CLAIM_NO_QUORUM": 173,
+ "tecXCHAIN_PROOF_UNKNOWN_KEY": 174,
+ "tecXCHAIN_CREATE_ACCOUNT_NONXRP_ISSUE": 175,
+ "tecXCHAIN_WRONG_CHAIN": 176,
+ "tecXCHAIN_REWARD_MISMATCH": 177,
+ "tecXCHAIN_NO_SIGNERS_LIST": 178,
+ "tecXCHAIN_SENDING_ACCOUNT_MISMATCH": 179,
+ "tecXCHAIN_INSUFF_CREATE_AMOUNT": 180,
+ "tecXCHAIN_ACCOUNT_CREATE_PAST": 181,
+ "tecXCHAIN_ACCOUNT_CREATE_TOO_MANY": 182,
+ "tecXCHAIN_PAYMENT_FAILED": 183,
+ "tecXCHAIN_SELF_COMMIT": 184,
+ "tecXCHAIN_BAD_PUBLIC_KEY_ACCOUNT_PAIR": 185,
+ "tecXCHAIN_CREATE_ACCOUNT_DISABLED": 186
},
"TRANSACTION_TYPES": {
"Invalid": -1,
@@ -2587,6 +2830,15 @@
"AMMWithdraw": 37,
"AMMVote": 38,
"AMMBid": 39,
+ "AMMDelete": 40,
+ "XChainCreateClaimID": 41,
+ "XChainCommit": 42,
+ "XChainClaim": 43,
+ "XChainAccountCreateCommit": 44,
+ "XChainAddClaimAttestation": 45,
+ "XChainAddAccountCreateAttestation": 46,
+ "XChainModifyBridge": 47,
+ "XChainCreateBridge": 48,
"EnableAmendment": 100,
"SetFee": 101,
"UNLModify": 102
diff --git a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/codec/addresses/PrivateKeyCodecTest.java b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/codec/addresses/PrivateKeyCodecTest.java
new file mode 100644
index 000000000..ef9e97f1a
--- /dev/null
+++ b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/codec/addresses/PrivateKeyCodecTest.java
@@ -0,0 +1,90 @@
+package org.xrpl.xrpl4j.codec.addresses;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.xrpl.xrpl4j.crypto.TestConstants;
+import org.xrpl.xrpl4j.crypto.keys.Passphrase;
+import org.xrpl.xrpl4j.crypto.keys.Seed;
+
+/**
+ * Unit tests for {@link PrivateKeyCodec}.
+ */
+class PrivateKeyCodecTest extends AbstractCodecTest {
+
+ private PrivateKeyCodec privateKeyCodec;
+
+ @BeforeEach
+ void setUp() {
+ privateKeyCodec = PrivateKeyCodec.getInstance();
+ }
+
+ @Test
+ public void encodeDecodeEdNodePrivate() {
+ testEncodeDecode(
+ prefixedNodePrivateKey -> privateKeyCodec.encodeNodePrivateKey(prefixedNodePrivateKey),
+ prefixedNodePrivateKey -> privateKeyCodec.decodeNodePrivateKey(prefixedNodePrivateKey),
+ TestConstants.getEdPrivateKey().naturalBytes(),
+ "paZHnTCvwm4GsxZ7qiA2nUBKE2DLnCoDWYqyocVZfVEZx3kvA4u"
+ );
+ }
+
+ /**
+ * These values come from the rippled codebase in Seed_test.cpp.
+ */
+ @Test
+ public void encodeDecodeNodePrivateFromRippled() {
+ Seed seed = Seed.ed25519SeedFromPassphrase(Passphrase.of("masterpassphrase"));
+
+ testEncodeDecode(
+ prefixedNodePrivateKey -> privateKeyCodec.encodeNodePrivateKey(prefixedNodePrivateKey),
+ prefixedNodePrivateKey -> privateKeyCodec.decodeNodePrivateKey(prefixedNodePrivateKey),
+ seed.deriveKeyPair().privateKey().naturalBytes(),
+ "paKv46LztLqK3GaKz1rG2nQGN6M4JLyRtxFBYFTw4wAVHtGys36"
+ );
+ }
+
+ @Test
+ public void encodeDecodeEdAccountPrivateKey() {
+ testEncodeDecode(
+ prefixedAccountPrivateKey -> privateKeyCodec.encodeAccountPrivateKey(prefixedAccountPrivateKey),
+ prefixedAccountPrivateKey -> privateKeyCodec.decodeAccountPrivateKey(prefixedAccountPrivateKey),
+ TestConstants.getEdPrivateKey().naturalBytes(),
+ "pwSmRvZy1c55Kb5tCpBZyq41noSmPn7ynFzUHu1MaoGLAP1VfrT"
+ );
+ }
+
+ /**
+ * These values come from the rippled codebase in Seed_test.cpp.
+ */
+ @Test
+ public void encodeDecodeAccountPrivateKeyFromRippled() {
+ Seed seed = Seed.secp256k1SeedFromPassphrase(Passphrase.of("masterpassphrase"));
+
+ testEncodeDecode(
+ prefixedNodePrivateKey -> privateKeyCodec.encodeAccountPrivateKey(prefixedNodePrivateKey),
+ prefixedNodePrivateKey -> privateKeyCodec.decodeAccountPrivateKey(prefixedNodePrivateKey),
+ seed.deriveKeyPair().privateKey().naturalBytes(),
+ "p9JfM6HHi64m6mvB6v5k7G2b1cXzGmYiCNJf6GHPKvFTWdeRVjh"
+ );
+ }
+
+ @Test
+ public void encodeDecodeEcNodePrivate() {
+ testEncodeDecode(
+ prefixedNodePrivate -> privateKeyCodec.encodeNodePrivateKey(prefixedNodePrivate),
+ prefixedNodePrivate -> privateKeyCodec.decodeNodePrivateKey(prefixedNodePrivate),
+ TestConstants.getEcPrivateKey().naturalBytes(),
+ "pa1UHARsPMiuDqrJLwFhzcJokoHgyiuaxgPhUGYhkG5ArCfG2vt"
+ );
+ }
+
+ @Test
+ public void encodeDecodeEcAccountPrivateKey() {
+ testEncodeDecode(
+ prefixedAccountPrivateKey -> privateKeyCodec.encodeAccountPrivateKey(prefixedAccountPrivateKey),
+ prefixedNodePrivateKey -> privateKeyCodec.decodeAccountPrivateKey(prefixedNodePrivateKey),
+ TestConstants.getEcPrivateKey().naturalBytes(),
+ "pwkgeQKfaDDMV7w59LhhuEWMbpX3HG2iXxXGgZuij2j6z1RYY7n"
+ );
+ }
+}
\ No newline at end of file
diff --git a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/codec/addresses/UnsignedByteArrayTest.java b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/codec/addresses/UnsignedByteArrayTest.java
index 3512d66aa..f2d6badd7 100644
--- a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/codec/addresses/UnsignedByteArrayTest.java
+++ b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/codec/addresses/UnsignedByteArrayTest.java
@@ -22,9 +22,7 @@
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertThrows;
-import static org.xrpl.xrpl4j.codec.addresses.UnsignedByteArray.of;
-import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import java.util.ArrayList;
@@ -38,27 +36,35 @@ public class UnsignedByteArrayTest {
static byte MAX_BYTE = (byte) 255;
- @Test
- public void ofByteArray() {
+ /////////
+ // of(byte[])
+ /////////
- assertThat(of(new byte[] {0}).hexValue()).isEqualTo("00");
- assertThat(of(new byte[] {MAX_BYTE}).hexValue()).isEqualTo("FF");
- assertThat(of(new byte[] {0, MAX_BYTE}).hexValue()).isEqualTo("00FF");
- assertThat(of(new byte[] {MAX_BYTE, 0}).hexValue()).isEqualTo("FF00");
- assertThat(of(new byte[] {MAX_BYTE, MAX_BYTE}).hexValue()).isEqualTo("FFFF");
+ @Test
+ void ofByteArray() {
+ assertThat(UnsignedByteArray.of(new byte[] {0}).hexValue()).isEqualTo("00");
+ assertThat(UnsignedByteArray.of(new byte[] {MAX_BYTE}).hexValue()).isEqualTo("FF");
+ assertThat(UnsignedByteArray.of(new byte[] {0, MAX_BYTE}).hexValue()).isEqualTo("00FF");
+ assertThat(UnsignedByteArray.of(new byte[] {MAX_BYTE, 0}).hexValue()).isEqualTo("FF00");
+ assertThat(UnsignedByteArray.of(new byte[] {MAX_BYTE, MAX_BYTE}).hexValue()).isEqualTo("FFFF");
}
+ /////////
+ // of(UnsignedByteArray)
+ /////////
+
@Test
- public void ofUnsignedByteArray() {
- assertThat(of(UnsignedByte.of(0)).hexValue()).isEqualTo("00");
- assertThat(of(UnsignedByte.of(MAX_BYTE)).hexValue()).isEqualTo("FF");
- assertThat(of(UnsignedByte.of(0), UnsignedByte.of((MAX_BYTE))).hexValue()).isEqualTo("00FF");
- assertThat(of(UnsignedByte.of(MAX_BYTE), UnsignedByte.of((0))).hexValue()).isEqualTo("FF00");
- assertThat(of(UnsignedByte.of(MAX_BYTE), UnsignedByte.of((MAX_BYTE))).hexValue()).isEqualTo("FFFF");
+ void ofUnsignedByteArray() {
+ assertThat(UnsignedByteArray.of(UnsignedByte.of(0)).hexValue()).isEqualTo("00");
+ assertThat(UnsignedByteArray.of(UnsignedByte.of(MAX_BYTE)).hexValue()).isEqualTo("FF");
+ assertThat(UnsignedByteArray.of(UnsignedByte.of(0), UnsignedByte.of((MAX_BYTE))).hexValue()).isEqualTo("00FF");
+ assertThat(UnsignedByteArray.of(UnsignedByte.of(MAX_BYTE), UnsignedByte.of((0))).hexValue()).isEqualTo("FF00");
+ assertThat(UnsignedByteArray.of(UnsignedByte.of(MAX_BYTE), UnsignedByte.of((MAX_BYTE))).hexValue())
+ .isEqualTo("FFFF");
}
@Test
- public void lowerCaseOrUpperCase() {
+ void lowerCaseOrUpperCase() {
assertThat(UnsignedByteArray.fromHex("Ff").hexValue()).isEqualTo("FF");
assertThat(UnsignedByteArray.fromHex("00fF").hexValue()).isEqualTo("00FF");
assertThat(UnsignedByteArray.fromHex("00ff").hexValue()).isEqualTo("00FF");
@@ -70,44 +76,44 @@ public void lowerCaseOrUpperCase() {
}
@Test
- public void empty() {
- assertThat(UnsignedByteArray.empty()).isEqualTo(of(new byte[] {}));
+ void empty() {
+ assertThat(UnsignedByteArray.empty()).isEqualTo(UnsignedByteArray.of(new byte[] {}));
assertThat(UnsignedByteArray.empty().length()).isEqualTo(0);
assertThat(
- UnsignedByteArray.empty().equals(of(new byte[] {}))
+ UnsignedByteArray.empty().equals(UnsignedByteArray.of(new byte[] {}))
).isTrue();
}
@Test
- public void length() {
+ void length() {
final int size = 2;
- assertThat(of(new byte[] {0, MAX_BYTE}).length()).isEqualTo(size);
- assertThat(of(new byte[] {0, 1}).length()).isEqualTo(UnsignedByteArray.ofSize(size).length());
+ assertThat(UnsignedByteArray.of(new byte[] {0, MAX_BYTE}).length()).isEqualTo(size);
+ assertThat(UnsignedByteArray.of(new byte[] {0, 1}).length()).isEqualTo(UnsignedByteArray.ofSize(size).length());
assertThat(UnsignedByteArray.ofSize(size).length()).isEqualTo(size);
}
@Test
- public void ofSize() {
+ void ofSize() {
final int size = 2;
- assertThat(UnsignedByteArray.ofSize(size)).isEqualTo(of(new byte[] {0, 0}));
+ assertThat(UnsignedByteArray.ofSize(size)).isEqualTo(UnsignedByteArray.of(new byte[] {0, 0}));
assertThat(UnsignedByteArray.ofSize(size).length()).isEqualTo(size);
- assertThat(UnsignedByteArray.ofSize(size).length()).isEqualTo(of(new byte[] {0, 0}).length());
- assertThat(UnsignedByteArray.ofSize(size).equals(of(new byte[] {0, 0}))).isTrue();
+ assertThat(UnsignedByteArray.ofSize(size).length()).isEqualTo(UnsignedByteArray.of(new byte[] {0, 0}).length());
+ assertThat(UnsignedByteArray.ofSize(size).equals(UnsignedByteArray.of(new byte[] {0, 0}))).isTrue();
}
@Test
- public void get() {
+ void get() {
byte[] byteArray = new byte[] {0, 1, 2};
- UnsignedByteArray array = of(byteArray);
+ UnsignedByteArray array = UnsignedByteArray.of(byteArray);
assertThat(array.get(0)).isEqualTo(array.getUnsignedBytes().get(0));
assertThat(array.get(0).asInt()).isEqualTo(byteArray[0]);
assertThat(array.get(1).asInt()).isEqualTo(byteArray[1]);
}
@Test
- public void appendByte() {
- UnsignedByteArray array1 = of(new byte[] {0, 1});
- UnsignedByteArray array2 = of(new byte[] {0, 1, 9});
+ void appendByte() {
+ UnsignedByteArray array1 = UnsignedByteArray.of(new byte[] {0, 1});
+ UnsignedByteArray array2 = UnsignedByteArray.of(new byte[] {0, 1, 9});
int initialLength = array1.length();
assertThat(array1.append(UnsignedByte.of(9))).isEqualTo(array2);
assertThat(array1.length() - 1).isEqualTo(initialLength);
@@ -115,17 +121,17 @@ public void appendByte() {
}
@Test
- public void appendByteArray() {
- UnsignedByteArray array1 = of(new byte[] {0, 1});
- UnsignedByteArray array2 = of(new byte[] {0, 1, 8, 9});
+ void appendByteArray() {
+ UnsignedByteArray array1 = UnsignedByteArray.of(new byte[] {0, 1});
+ UnsignedByteArray array2 = UnsignedByteArray.of(new byte[] {0, 1, 8, 9});
int initialLength = array1.length();
- assertThat(array1.append(of(new byte[] {8, 9}))).isEqualTo(array2);
+ assertThat(array1.append(UnsignedByteArray.of(new byte[] {8, 9}))).isEqualTo(array2);
assertThat(array1.length()).isEqualTo(initialLength + 2);
assertThat(array2.length()).isEqualTo(initialLength + 2);
}
@Test
- public void fill() {
+ void fill() {
List unsignedBytes1 = new ArrayList<>();
List unsignedBytes2 = Arrays.asList(UnsignedByte.of(0), UnsignedByte.of(0));
List filledBytes = UnsignedByteArray.fill(2);
@@ -136,46 +142,46 @@ public void fill() {
}
@Test
- public void set() {
- UnsignedByteArray array1 = of(new byte[] {0, 1});
- UnsignedByteArray array2 = of(new byte[] {0, 9});
+ void set() {
+ UnsignedByteArray array1 = UnsignedByteArray.of(new byte[] {0, 1});
+ UnsignedByteArray array2 = UnsignedByteArray.of(new byte[] {0, 9});
assertThat(array1).isNotEqualTo(array2);
array1.set(1, UnsignedByte.of(9));
assertThat(array1).isEqualTo(array2);
}
@Test
- public void slice() {
- UnsignedByteArray array1 = of(new byte[] {0, 8, 9, 1});
- UnsignedByteArray array2 = of(new byte[] {8, 9});
+ void slice() {
+ UnsignedByteArray array1 = UnsignedByteArray.of(new byte[] {0, 8, 9, 1});
+ UnsignedByteArray array2 = UnsignedByteArray.of(new byte[] {8, 9});
assertThat(array1).isNotEqualTo(array2);
assertThat(array1.slice(1, 3)).isEqualTo(array2);
assertThrows(IndexOutOfBoundsException.class, () -> array1.slice(1, 5));
}
@Test
- public void hashcode() {
- UnsignedByteArray array1 = of(new byte[] {0, 1});
- assertThat(array1).isNotEqualTo(of(new byte[] {8, 9}));
- assertThat(array1.hashCode()).isNotEqualTo(of(new byte[] {8, 9}).hashCode());
+ void hashcode() {
+ UnsignedByteArray array1 = UnsignedByteArray.of(new byte[] {0, 1});
+ assertThat(array1).isNotEqualTo(UnsignedByteArray.of(new byte[] {8, 9}));
+ assertThat(array1.hashCode()).isNotEqualTo(UnsignedByteArray.of(new byte[] {8, 9}).hashCode());
assertThat(array1.hashCode()).isEqualTo(array1.hashCode());
- assertThat(array1.hashCode()).isEqualTo(of(new byte[] {0, 1}).hashCode());
+ assertThat(array1.hashCode()).isEqualTo(UnsignedByteArray.of(new byte[] {0, 1}).hashCode());
}
@Test
- public void unsignedByteArrayToString() {
- UnsignedByteArray array1 = of(new byte[] {0, 1});
- UnsignedByteArray array2 = of(new byte[] {8, 9});
+ void unsignedByteArrayToString() {
+ UnsignedByteArray array1 = UnsignedByteArray.of(new byte[] {0, 1});
+ UnsignedByteArray array2 = UnsignedByteArray.of(new byte[] {8, 9});
assertThat(array1.toString()).isEqualTo(array1.toString());
assertThat(array1).isNotEqualTo(array2);
assertThat(array1.toString()).isEqualTo(array2.toString());
}
@Test
- public void unsignedByteArrayEqualsTest() {
- UnsignedByteArray array1 = of(new byte[] {0, MAX_BYTE});
- UnsignedByteArray array2 = of(new byte[] {MAX_BYTE, 0});
- UnsignedByteArray array3 = of(new byte[] {0, MAX_BYTE});
+ void unsignedByteArrayEqualsTest() {
+ UnsignedByteArray array1 = UnsignedByteArray.of(new byte[] {0, MAX_BYTE});
+ UnsignedByteArray array2 = UnsignedByteArray.of(new byte[] {MAX_BYTE, 0});
+ UnsignedByteArray array3 = UnsignedByteArray.of(new byte[] {0, MAX_BYTE});
UnsignedByteArray array4 = array1;
assertThat(array1.equals(array1)).isTrue();
@@ -194,7 +200,7 @@ public void unsignedByteArrayEqualsTest() {
@Test
void destroy() {
- UnsignedByteArray uba = of(new byte[] {0, MAX_BYTE});
+ UnsignedByteArray uba = UnsignedByteArray.of(new byte[] {0, MAX_BYTE});
uba.destroy();
assertThat(uba.isDestroyed()).isTrue();
assertThat(uba.toByteArray()).isEqualTo(new byte[0]);
diff --git a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/codec/addresses/UnsignedByteTest.java b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/codec/addresses/UnsignedByteTest.java
index 17f394fce..8c6c94039 100644
--- a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/codec/addresses/UnsignedByteTest.java
+++ b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/codec/addresses/UnsignedByteTest.java
@@ -21,15 +21,27 @@
*/
import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.assertThrows;
import org.junit.jupiter.api.Test;
+import org.mockito.internal.matchers.Null;
import java.math.BigInteger;
+/**
+ * Unit tests for {@link UnsignedByte}.
+ */
public class UnsignedByteTest {
@Test
- public void hexValue() {
+ void constructorTests() {
+ assertThrows(IllegalArgumentException.class, () -> UnsignedByte.of(-1));
+ assertThrows(NullPointerException.class, () -> UnsignedByte.of((UnsignedByte) null));
+ assertThat(UnsignedByte.of(UnsignedByte.of(0))).isEqualTo(UnsignedByte.of(0));
+ }
+
+ @Test
+ void hexValue() {
assertThat(UnsignedByte.of(0).hexValue()).isEqualTo("00");
assertThat(UnsignedByte.of(127).hexValue()).isEqualTo("7F");
assertThat(UnsignedByte.of(128).hexValue()).isEqualTo("80");
@@ -38,7 +50,7 @@ public void hexValue() {
}
@Test
- public void isNthBitSetAllZero() {
+ void isNthBitSetAllZero() {
UnsignedByte value = UnsignedByte.of(0);
for (int i = 1; i <= 8; i++) {
assertThat(value.isNthBitSet(i)).isFalse();
@@ -46,7 +58,7 @@ public void isNthBitSetAllZero() {
}
@Test
- public void isNthBitSetAllSet() {
+ void isNthBitSetAllSet() {
UnsignedByte value = UnsignedByte.of(new BigInteger("11111111", 2).intValue());
for (int i = 1; i <= 8; i++) {
assertThat(value.isNthBitSet(i)).isTrue();
@@ -54,7 +66,7 @@ public void isNthBitSetAllSet() {
}
@Test
- public void isNthBitSetEveryOther() {
+ void isNthBitSetEveryOther() {
UnsignedByte value = UnsignedByte.of(new BigInteger("10101010", 2).intValue());
assertThat(value.isNthBitSet(1)).isTrue();
assertThat(value.isNthBitSet(2)).isFalse();
@@ -67,7 +79,7 @@ public void isNthBitSetEveryOther() {
}
@Test
- public void intValue() {
+ void intValue() {
assertThat(UnsignedByte.of(0x00).asInt()).isEqualTo(0);
assertThat(UnsignedByte.of(0x0F).asInt()).isEqualTo(15);
assertThat(UnsignedByte.of(0xFF).asInt()).isEqualTo(255);
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 dc476683d..717c80019 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
@@ -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.
diff --git a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/codec/binary/XrplBinaryCodecTest.java b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/codec/binary/XrplBinaryCodecTest.java
index 4b581dbb3..cf394bb73 100644
--- a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/codec/binary/XrplBinaryCodecTest.java
+++ b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/codec/binary/XrplBinaryCodecTest.java
@@ -28,11 +28,15 @@
import com.google.common.primitives.UnsignedInteger;
import org.assertj.core.api.Assertions;
import org.assertj.core.util.Lists;
+import org.json.JSONException;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
+import org.skyscreamer.jsonassert.JSONAssert;
+import org.skyscreamer.jsonassert.JSONCompareMode;
import org.xrpl.xrpl4j.codec.fixtures.FixtureUtils;
+import org.xrpl.xrpl4j.codec.fixtures.codec.CodecFixture;
import org.xrpl.xrpl4j.codec.fixtures.data.WholeObject;
import org.xrpl.xrpl4j.crypto.keys.PublicKey;
import org.xrpl.xrpl4j.crypto.signing.Signature;
@@ -71,6 +75,10 @@ private static Stream dataDrivenFixtures() throws IOException {
.map(Arguments::of);
}
+ private static Stream transactionCodecFixtures() throws IOException {
+ return FixtureUtils.getCodecFixtures().transactions().stream().map(Arguments::of);
+ }
+
@Test
void encodeDecodeSimple() throws JsonProcessingException {
assertThat(encoder.encode(SIMPLE_JSON)).isEqualTo(SIMPLE_HEX);
@@ -452,4 +460,13 @@ void dataDriven(WholeObject wholeObject) throws IOException {
assertThat(encoder.encode(wholeObject.txJson().toString())).isEqualTo(wholeObject.expectedHex());
}
+ @ParameterizedTest
+ @MethodSource("transactionCodecFixtures")
+ void transactionFixtureTests(CodecFixture codecFixture) throws JsonProcessingException, JSONException {
+ assertThat(encoder.encode(codecFixture.json().toString())).isEqualTo(codecFixture.binary());
+ JSONAssert.assertEquals(
+ encoder.decode(codecFixture.binary()), codecFixture.json().toString(), JSONCompareMode.STRICT
+ );
+ }
+
}
diff --git a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/crypto/TestConstants.java b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/crypto/TestConstants.java
index 2abd02fb5..534fb6d5f 100644
--- a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/crypto/TestConstants.java
+++ b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/crypto/TestConstants.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.
@@ -21,10 +21,12 @@
*/
import com.google.common.io.BaseEncoding;
+import org.xrpl.xrpl4j.codec.addresses.KeyType;
import org.xrpl.xrpl4j.codec.addresses.UnsignedByteArray;
import org.xrpl.xrpl4j.crypto.keys.PrivateKey;
import org.xrpl.xrpl4j.crypto.keys.PublicKey;
import org.xrpl.xrpl4j.model.transactions.Address;
+import org.xrpl.xrpl4j.model.transactions.Hash256;
/**
* Constants used for testing.
@@ -36,20 +38,46 @@ public interface TestConstants {
String ED_PUBLIC_KEY_B58 = "aKEusmsH9dJvjfeEg8XhDfpEgmhcK1epAtFJfAQbACndz5mUA73B";
PublicKey ED_PUBLIC_KEY = PublicKey.fromBase16EncodedPublicKey(ED_PUBLIC_KEY_HEX);
- String ED_PRIVATE_KEY_HEX = "EDB224AFDCCEC7AA4E245E35452585D4FBBE37519BCA3929578BFC5BBD4640E163";
- String ED_PRIVATE_KEY_B58 = "pDcQTi2uFBAzQ7cY2mYQtk9QuQBoLU6rJypEf8EYPQoouh";
- PrivateKey ED_PRIVATE_KEY = PrivateKey.of(UnsignedByteArray.of(BaseEncoding.base16().decode(ED_PRIVATE_KEY_HEX)));
+ String ED_PRIVATE_KEY_HEX = "B224AFDCCEC7AA4E245E35452585D4FBBE37519BCA3929578BFC5BBD4640E163";
+ String ED_PRIVATE_KEY_WITH_PREFIX_HEX = "ED" + ED_PRIVATE_KEY_HEX;
+
+ /**
+ * Helper method to return a newly constructed {@link PrivateKey} with its own copy of bytes.
+ *
+ * @return A {@link PrivateKey}.
+ */
+ static PrivateKey getEdPrivateKey() {
+ return PrivateKey.fromNaturalBytes(
+ UnsignedByteArray.of(BaseEncoding.base16().decode(ED_PRIVATE_KEY_HEX)),
+ KeyType.ED25519
+ );
+ }
// Secp256k1 Public Key
String EC_PUBLIC_KEY_HEX = "027535A4E90B2189CF9885563F45C4F454B3BFAB21930089C3878A9427B4D648D9";
String EC_PUBLIC_KEY_B58 = "aB4ifx88a26RYRSSzeKW8HpbXfbpzQFRsX6dMNmMwEVHUTKzfWdk";
PublicKey EC_PUBLIC_KEY = PublicKey.fromBase16EncodedPublicKey(EC_PUBLIC_KEY_HEX);
- String EC_PRIVATE_KEY_HEX = "00DAD3C2B4BF921398932C889DE5335F89D90249355FC6FFB73F1256D2957F9F17";
- String EC_PRIVATE_KEY_B58 = "rEjDwJp2Pm3NrUtcf8v17jWopvqPJxyi5RTrDfhcJcWSi";
- PrivateKey EC_PRIVATE_KEY = PrivateKey.of(UnsignedByteArray.of(BaseEncoding.base16().decode(EC_PRIVATE_KEY_HEX)));
+ String EC_PRIVATE_KEY_HEX = "DAD3C2B4BF921398932C889DE5335F89D90249355FC6FFB73F1256D2957F9F17";
+ String EC_PRIVATE_KEY_WITH_PREFIX_HEX = "00" + EC_PRIVATE_KEY_HEX;
+
+ /**
+ * Helper method to return a newly constructed {@link PrivateKey} with its own copy of bytes.
+ *
+ * @return A {@link PrivateKey}.
+ */
+ static PrivateKey getEcPrivateKey() {
+ return PrivateKey.fromNaturalBytes(
+ UnsignedByteArray.of(BaseEncoding.base16().decode(EC_PRIVATE_KEY_HEX)), KeyType.SECP256K1
+ );
+ }
// Both generated from Passphrase.of("hello")
Address ED_ADDRESS = Address.of("rwGWYtRR6jJJJq7FKQg74YwtkiPyUqJ466");
Address EC_ADDRESS = Address.of("rD8ATvjj9mfnFuYYTGRNb9DygnJW9JNN1C");
+
+ /**
+ * A sample {@link Hash256}.
+ */
+ Hash256 HASH_256 = Hash256.of("6B1011EF3BC3ED619B15979EF75C1C60D9181F3DDE641AD3019318D3900CEE2E");
}
diff --git a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/crypto/keys/PrivateKeyTest.java b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/crypto/keys/PrivateKeyTest.java
index 57145e87a..9900d87eb 100644
--- a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/crypto/keys/PrivateKeyTest.java
+++ b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/crypto/keys/PrivateKeyTest.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.
@@ -21,95 +21,468 @@
*/
import static org.assertj.core.api.Assertions.assertThat;
-import static org.xrpl.xrpl4j.crypto.TestConstants.EC_PRIVATE_KEY;
-import static org.xrpl.xrpl4j.crypto.TestConstants.ED_PRIVATE_KEY;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.xrpl.xrpl4j.crypto.TestConstants.EC_PRIVATE_KEY_HEX;
+import static org.xrpl.xrpl4j.crypto.TestConstants.EC_PRIVATE_KEY_WITH_PREFIX_HEX;
+import static org.xrpl.xrpl4j.crypto.TestConstants.ED_PRIVATE_KEY_HEX;
+import static org.xrpl.xrpl4j.crypto.TestConstants.ED_PRIVATE_KEY_WITH_PREFIX_HEX;
+import com.google.common.base.Preconditions;
+import com.google.common.io.BaseEncoding;
import org.junit.jupiter.api.Test;
-import org.xrpl.xrpl4j.codec.addresses.Base58;
import org.xrpl.xrpl4j.codec.addresses.KeyType;
+import org.xrpl.xrpl4j.codec.addresses.UnsignedByteArray;
import org.xrpl.xrpl4j.crypto.TestConstants;
+import org.xrpl.xrpl4j.crypto.signing.bc.Secp256k1;
/**
* Unit tests for {@link PrivateKey}.
*/
-public class PrivateKeyTest {
+@SuppressWarnings("deprecation")
+class PrivateKeyTest {
+
+ ////////////////////
+ // of
+ ////////////////////
+
+ @Test
+ void testOfWithNull() {
+ assertThrows(NullPointerException.class, () -> PrivateKey.of(null));
+ }
+
+ @Test
+ void testEcOf() {
+ UnsignedByteArray thirtyThreeBytes = UnsignedByteArray.of(
+ BaseEncoding.base16().decode(EC_PRIVATE_KEY_WITH_PREFIX_HEX));
+ assertThat(PrivateKey.of(thirtyThreeBytes)).isEqualTo(TestConstants.getEcPrivateKey());
+
+ UnsignedByteArray thirtyTwoBytes = UnsignedByteArray.of(thirtyThreeBytes.slice(1, 33).toByteArray());
+ IllegalArgumentException exception = assertThrows(
+ IllegalArgumentException.class, () -> PrivateKey.of(thirtyTwoBytes)
+ );
+ assertThat(exception.getMessage()).isEqualTo(
+ "The `fromPrefixedBytes` function requires input length of 33 bytes, but 32 were supplied."
+ );
+ }
+
+ @Test
+ void testEdOf() {
+ UnsignedByteArray thirtyThreeBytes = UnsignedByteArray.of(
+ BaseEncoding.base16().decode(ED_PRIVATE_KEY_WITH_PREFIX_HEX));
+ assertThat(PrivateKey.of(thirtyThreeBytes)).isEqualTo(TestConstants.getEdPrivateKey());
+
+ UnsignedByteArray thirtyTwoBytes = UnsignedByteArray.of(thirtyThreeBytes.slice(1, 33).toByteArray());
+ IllegalArgumentException exception = assertThrows(
+ IllegalArgumentException.class, () -> PrivateKey.of(thirtyTwoBytes)
+ );
+ assertThat(exception.getMessage()).isEqualTo(
+ "The `fromPrefixedBytes` function requires input length of 33 bytes, but 32 were supplied."
+ );
+ }
+
+ @Test
+ void testOfWithLessWithEmpty() {
+ IllegalArgumentException exception = assertThrows(
+ IllegalArgumentException.class, () -> PrivateKey.of(UnsignedByteArray.empty())
+ );
+ assertThat(exception.getMessage())
+ .isEqualTo("The `fromPrefixedBytes` function requires input length of 33 bytes, but 0 were supplied.");
+ }
+
+ @Test
+ void testEdOfWithLessThan32Bytes() {
+ UnsignedByteArray twoBytes = UnsignedByteArray.of(new byte[] {(byte) 0xED, (byte) 0xFF});
+ IllegalArgumentException exception = assertThrows(
+ IllegalArgumentException.class, () -> PrivateKey.of(twoBytes)
+ );
+ assertThat(exception.getMessage()).isEqualTo(
+ "The `fromPrefixedBytes` function requires input length of 33 bytes, but 2 were supplied."
+ );
+ }
+
+ @Test
+ void testEcOfWithLessThan32Bytes() {
+ UnsignedByteArray twoBytes = UnsignedByteArray.of(new byte[] {(byte) 0x00, (byte) 0xFF});
+ IllegalArgumentException exception = assertThrows(
+ IllegalArgumentException.class, () -> PrivateKey.of(twoBytes)
+ );
+ assertThat(exception.getMessage()).isEqualTo(
+ "The `fromPrefixedBytes` function requires input length of 33 bytes, but 2 were supplied."
+ );
+ }
+
+ ///////////////////
+ // fromNaturalBytes
+ ///////////////////
+
+ @Test
+ void testFromNaturalBytesWithNull() {
+ assertThrows(NullPointerException.class, () -> PrivateKey.fromNaturalBytes(null, KeyType.ED25519));
+ assertThrows(NullPointerException.class, () -> PrivateKey.fromNaturalBytes(UnsignedByteArray.empty(), null));
+ }
+
+ @Test
+ void testEcFromNaturalBytesWithEmpty() {
+ IllegalArgumentException exception = assertThrows(
+ IllegalArgumentException.class, () -> PrivateKey.fromNaturalBytes(UnsignedByteArray.empty(), KeyType.SECP256K1)
+ );
+ assertThat(exception.getMessage())
+ .isEqualTo("Byte values passed to this constructor must be 32 bytes long, with no prefix.");
+ }
+
+ @Test
+ void testEdFromNaturalBytesWithEmpty() {
+ IllegalArgumentException exception = assertThrows(
+ IllegalArgumentException.class, () -> PrivateKey.fromNaturalBytes(UnsignedByteArray.empty(), KeyType.ED25519)
+ );
+ assertThat(exception.getMessage())
+ .isEqualTo("Byte values passed to this constructor must be 32 bytes long, with no prefix.");
+ }
+
+ @Test
+ void testEcFromNaturalBytes() {
+ UnsignedByteArray thirtyThreeBytes = UnsignedByteArray.of(
+ BaseEncoding.base16().decode(EC_PRIVATE_KEY_WITH_PREFIX_HEX)
+ );
+ IllegalArgumentException exception = assertThrows(
+ IllegalArgumentException.class, () -> PrivateKey.fromNaturalBytes(thirtyThreeBytes, KeyType.SECP256K1)
+ );
+ assertThat(exception.getMessage())
+ .isEqualTo("Byte values passed to this constructor must be 32 bytes long, with no prefix.");
+
+ UnsignedByteArray thirtyTwoBytes = UnsignedByteArray.of(thirtyThreeBytes.slice(1, 33).toByteArray());
+ assertThat(PrivateKey.fromNaturalBytes(thirtyTwoBytes, KeyType.SECP256K1)).isEqualTo(
+ TestConstants.getEcPrivateKey()
+ );
+ }
+
+ @Test
+ void testEdFromNaturalBytes() {
+ UnsignedByteArray thirtyThreeBytes = UnsignedByteArray.of(
+ BaseEncoding.base16().decode(ED_PRIVATE_KEY_WITH_PREFIX_HEX));
+ IllegalArgumentException exception = assertThrows(
+ IllegalArgumentException.class, () -> PrivateKey.fromNaturalBytes(thirtyThreeBytes, KeyType.ED25519)
+ );
+ assertThat(exception.getMessage())
+ .isEqualTo("Byte values passed to this constructor must be 32 bytes long, with no prefix.");
+
+ UnsignedByteArray thirtyTwoBytes = UnsignedByteArray.of(thirtyThreeBytes.slice(1, 33).toByteArray());
+ assertThat(PrivateKey.fromNaturalBytes(thirtyTwoBytes, KeyType.ED25519)).isEqualTo(TestConstants.getEdPrivateKey());
+ }
+
+ @Test
+ void testEdFromNaturalBytesWithLessThan32Bytes() {
+ UnsignedByteArray twoBytes = UnsignedByteArray.of(new byte[] {(byte) 0xED, (byte) 0xFF});
+ IllegalArgumentException exception = assertThrows(
+ IllegalArgumentException.class, () -> PrivateKey.fromNaturalBytes(twoBytes, KeyType.ED25519)
+ );
+ assertThat(exception.getMessage())
+ .isEqualTo("Byte values passed to this constructor must be 32 bytes long, with no prefix.");
+ }
+
+ @Test
+ void testEcFromNaturalBytesWithLessThan32Bytes() {
+ UnsignedByteArray twoBytes = UnsignedByteArray.of(new byte[] {(byte) 0x00, (byte) 0xFF});
+ IllegalArgumentException exception = assertThrows(
+ IllegalArgumentException.class, () -> PrivateKey.fromNaturalBytes(twoBytes, KeyType.SECP256K1)
+ );
+ assertThat(exception.getMessage())
+ .isEqualTo("Byte values passed to this constructor must be 32 bytes long, with no prefix.");
+ }
+
+ ////////////////////
+ // fromPrefixedBytes
+ ////////////////////
+
+ @Test
+ void testFromPrefixedBytesWithNull() {
+ assertThrows(NullPointerException.class, () -> PrivateKey.fromPrefixedBytes(null));
+ }
+
+ @Test
+ void testEcFromPrefixedBytes() {
+ UnsignedByteArray thirtyThreeBytes = UnsignedByteArray.of(
+ BaseEncoding.base16().decode(EC_PRIVATE_KEY_WITH_PREFIX_HEX)
+ );
+ assertThat(PrivateKey.fromPrefixedBytes(thirtyThreeBytes)).isEqualTo(TestConstants.getEcPrivateKey());
+
+ UnsignedByteArray thirtyTwoBytes = UnsignedByteArray.of(thirtyThreeBytes.slice(1, 33).toByteArray());
+ IllegalArgumentException exception = assertThrows(
+ IllegalArgumentException.class, () -> PrivateKey.fromPrefixedBytes(thirtyTwoBytes)
+ );
+ assertThat(exception.getMessage()).isEqualTo(
+ "The `fromPrefixedBytes` function requires input length of 33 bytes, but 32 were supplied."
+ );
+ }
+
+ @Test
+ void testEdFromPrefixedBytes() {
+ UnsignedByteArray thirtyThreeBytes = UnsignedByteArray.of(
+ BaseEncoding.base16().decode(ED_PRIVATE_KEY_WITH_PREFIX_HEX)
+ );
+ assertThat(PrivateKey.fromPrefixedBytes(thirtyThreeBytes)).isEqualTo(TestConstants.getEdPrivateKey());
+
+ UnsignedByteArray thirtyTwoBytes = UnsignedByteArray.of(thirtyThreeBytes.slice(1, 33).toByteArray());
+ IllegalArgumentException exception = assertThrows(
+ IllegalArgumentException.class, () -> PrivateKey.fromPrefixedBytes(thirtyTwoBytes)
+ );
+ assertThat(exception.getMessage()).isEqualTo(
+ "The `fromPrefixedBytes` function requires input length of 33 bytes, but 32 were supplied."
+ );
+ }
+
+ @Test
+ void testEdFromPrefixedBytesWithLessThan32Bytes() {
+ UnsignedByteArray twoBytes = UnsignedByteArray.of(new byte[] {(byte) 0xED, (byte) 0xFF});
+ IllegalArgumentException exception = assertThrows(
+ IllegalArgumentException.class, () -> PrivateKey.fromPrefixedBytes(twoBytes)
+ );
+ assertThat(exception.getMessage()).isEqualTo(
+ "The `fromPrefixedBytes` function requires input length of 33 bytes, but 2 were supplied."
+ );
+ }
+
+ @Test
+ void testEcFromPrefixedBytesWithLessThan32Bytes() {
+ UnsignedByteArray twoBytes = UnsignedByteArray.of(new byte[] {(byte) 0x00, (byte) 0xFF});
+ IllegalArgumentException exception = assertThrows(
+ IllegalArgumentException.class, () -> PrivateKey.fromPrefixedBytes(twoBytes)
+ );
+ assertThat(exception.getMessage()).isEqualTo(
+ "The `fromPrefixedBytes` function requires input length of 33 bytes, but 2 were supplied."
+ );
+ }
+
+ @Test
+ void testEdFromPrefixedBytesWith0Bytes() {
+ IllegalArgumentException exception = assertThrows(
+ IllegalArgumentException.class, () -> PrivateKey.fromPrefixedBytes(UnsignedByteArray.empty())
+ );
+ assertThat(exception.getMessage()).isEqualTo(
+ "The `fromPrefixedBytes` function requires input length of 33 bytes, but 0 were supplied."
+ );
+ }
+
+ @Test
+ void testEcFromPrefixedBytesWithLess0Bytes() {
+ IllegalArgumentException exception = assertThrows(
+ IllegalArgumentException.class, () -> PrivateKey.fromPrefixedBytes(UnsignedByteArray.empty())
+ );
+ assertThat(exception.getMessage()).isEqualTo(
+ "The `fromPrefixedBytes` function requires input length of 33 bytes, but 0 were supplied."
+ );
+ }
+
+ @Test
+ void testFromPrefixedBytesWithInvalidPrefix() {
+ final byte[] invalidPrefixBytes = BaseEncoding.base16().decode(
+ "20000000000000000000000000000000" + // <-- 16 bytes
+ "0000000000000000000000000000000000"); // <-- 17 bytes
+ IllegalArgumentException exception = assertThrows(
+ IllegalArgumentException.class, () -> PrivateKey.fromPrefixedBytes(UnsignedByteArray.of(invalidPrefixBytes))
+ );
+ assertThat(exception.getMessage()).isEqualTo(
+ "PrivateKey construction requires 32 natural bytes plug a one-byte prefix value of either `0xED` for " +
+ "ed25519 private keys or `0x00` for secp256k1 private keys. Input byte length was 33 bytes with a prefixByte " +
+ "value of `0x20`"
+ );
+ }
+
+ ///////////////////
+ // Constants
+ ///////////////////
@Test
- public void valueEd25519() {
- assertThat(Base58.encode(ED_PRIVATE_KEY.value().toByteArray())).isEqualTo(TestConstants.ED_PRIVATE_KEY_B58);
+ void testConstants() {
+ assertThat(PrivateKey.ED2559_PREFIX.asInt()).isEqualTo(0xED);
+ assertThat(PrivateKey.ED2559_PREFIX.asByte()).isEqualTo((byte) 0xED);
+
+ assertThat(PrivateKey.SECP256K1_PREFIX.asInt()).isEqualTo(0x00);
+ assertThat(PrivateKey.SECP256K1_PREFIX.asByte()).isEqualTo((byte) 0x00);
}
+ ///////////////////
+ // value tests ==> [value, valueWithPrefixedBytes, valueWithNaturalBytes]
+ ///////////////////
+
@Test
- public void valueSecp256k1() {
- assertThat(Base58.encode(EC_PRIVATE_KEY.value().toByteArray())).isEqualTo(TestConstants.EC_PRIVATE_KEY_B58);
+ void valueForEd25519() {
+ assertThat(TestConstants.getEdPrivateKey().value().hexValue()) // <-- Overtly test .value()
+ .isEqualTo(ED_PRIVATE_KEY_WITH_PREFIX_HEX);
+ assertThat(TestConstants.getEdPrivateKey().prefixedBytes().hexValue()).isEqualTo(
+ ED_PRIVATE_KEY_WITH_PREFIX_HEX
+ );
+ assertThat(TestConstants.getEdPrivateKey().naturalBytes().hexValue()).isEqualTo(ED_PRIVATE_KEY_HEX);
}
@Test
- public void keyTypeEd25519() {
- assertThat(ED_PRIVATE_KEY.keyType()).isEqualTo(KeyType.ED25519);
+ void valueForSecp256k1() {
+ assertThat(TestConstants.getEcPrivateKey().value().hexValue()) // <-- Overtly test .value()
+ .isEqualTo(EC_PRIVATE_KEY_WITH_PREFIX_HEX);
+ assertThat(TestConstants.getEcPrivateKey().prefixedBytes().hexValue()).isEqualTo(
+ EC_PRIVATE_KEY_WITH_PREFIX_HEX
+ );
+ assertThat(TestConstants.getEcPrivateKey().naturalBytes().hexValue()).isEqualTo(EC_PRIVATE_KEY_HEX);
}
+ ///////////////////
+ // Misc
+ ///////////////////
+
@Test
- public void keyTypeSecp256k1() {
- assertThat(EC_PRIVATE_KEY.keyType()).isEqualTo(KeyType.SECP256K1);
+ void keyTypeEd25519() {
+ assertThat(TestConstants.getEdPrivateKey().keyType()).isEqualTo(KeyType.ED25519);
+ }
+
+ @Test
+ void keyTypeSecp256k1() {
+ assertThat(TestConstants.getEcPrivateKey().keyType()).isEqualTo(KeyType.SECP256K1);
}
@Test
void destroy() {
- PrivateKey privateKey = PrivateKey.of(ED_PRIVATE_KEY.value());
+ PrivateKey privateKey = PrivateKey.fromPrefixedBytes(TestConstants.getEdPrivateKey().prefixedBytes());
assertThat(privateKey.isDestroyed()).isFalse();
privateKey.destroy();
assertThat(privateKey.isDestroyed()).isTrue();
- assertThat(privateKey.value().hexValue()).isEqualTo("");
+ assertThat(privateKey.prefixedBytes().hexValue()).isEqualTo("");
privateKey.destroy();
assertThat(privateKey.isDestroyed()).isTrue();
- assertThat(privateKey.value().hexValue()).isEqualTo("");
+ assertThat(privateKey.value().hexValue()).isEqualTo(""); // <-- Overtly test .value()
+ assertThat(privateKey.naturalBytes().hexValue()).isEqualTo("");
+ assertThat(privateKey.prefixedBytes().hexValue()).isEqualTo("");
+
+ assertThat(privateKey.value()).isEqualTo(UnsignedByteArray.empty()); // <-- Overtly test .value()
+ assertThat(privateKey.naturalBytes()).isEqualTo(UnsignedByteArray.empty());
+ assertThat(privateKey.prefixedBytes()).isEqualTo(UnsignedByteArray.empty());
- privateKey = PrivateKey.of(EC_PRIVATE_KEY.value());
+ privateKey = PrivateKey.fromPrefixedBytes(TestConstants.getEcPrivateKey().prefixedBytes());
assertThat(privateKey.isDestroyed()).isFalse();
privateKey.destroy();
assertThat(privateKey.isDestroyed()).isTrue();
- assertThat(privateKey.value().hexValue()).isEqualTo("");
+ assertThat(privateKey.prefixedBytes().hexValue()).isEqualTo("");
privateKey.destroy();
assertThat(privateKey.isDestroyed()).isTrue();
- assertThat(privateKey.value().hexValue()).isEqualTo("");
+
+ assertThat(privateKey.value().hexValue()).isEqualTo(""); // <-- Overtly test .value()
+ assertThat(privateKey.naturalBytes().hexValue()).isEqualTo("");
+ assertThat(privateKey.prefixedBytes().hexValue()).isEqualTo("");
+
+ assertThat(privateKey.value()).isEqualTo(UnsignedByteArray.empty()); // <-- Overtly test .value()
+ assertThat(privateKey.naturalBytes()).isEqualTo(UnsignedByteArray.empty());
+ assertThat(privateKey.prefixedBytes()).isEqualTo(UnsignedByteArray.empty());
}
@Test
void equals() {
- assertThat(ED_PRIVATE_KEY).isEqualTo(ED_PRIVATE_KEY);
- assertThat(ED_PRIVATE_KEY).isNotEqualTo(EC_PRIVATE_KEY);
- assertThat(EC_PRIVATE_KEY).isNotEqualTo(new Object());
+ PrivateKey privateKey = TestConstants.getEdPrivateKey();
+ assertThat(privateKey).isEqualTo(privateKey); // <-- To cover reference equality in .equals
+
+ assertThat(TestConstants.getEdPrivateKey()).isEqualTo(TestConstants.getEdPrivateKey());
+ assertThat(TestConstants.getEdPrivateKey()).isEqualTo(
+ PrivateKey.fromPrefixedBytes(TestConstants.getEdPrivateKey().prefixedBytes())
+ );
+ assertThat(TestConstants.getEdPrivateKey()).isEqualTo(
+ PrivateKey.fromNaturalBytes(TestConstants.getEdPrivateKey().naturalBytes(), KeyType.ED25519)
+ );
+ assertThat(TestConstants.getEdPrivateKey()).isNotEqualTo(TestConstants.getEcPrivateKey());
+ assertThat(TestConstants.getEcPrivateKey()).isNotEqualTo(new Object());
- assertThat(EC_PRIVATE_KEY).isEqualTo(EC_PRIVATE_KEY);
- assertThat(EC_PRIVATE_KEY).isNotEqualTo(ED_PRIVATE_KEY);
- assertThat(EC_PRIVATE_KEY).isNotEqualTo(new Object());
+ assertThat(TestConstants.getEcPrivateKey()).isEqualTo(TestConstants.getEcPrivateKey());
+ assertThat(TestConstants.getEcPrivateKey()).isEqualTo(
+ PrivateKey.fromPrefixedBytes(TestConstants.getEcPrivateKey().prefixedBytes())
+ );
+ assertThat(TestConstants.getEcPrivateKey()).isEqualTo(
+ PrivateKey.fromNaturalBytes(TestConstants.getEcPrivateKey().naturalBytes(), KeyType.SECP256K1)
+ );
+
+ assertThat(TestConstants.getEcPrivateKey()).isNotEqualTo(TestConstants.getEdPrivateKey());
+ assertThat(TestConstants.getEcPrivateKey()).isNotEqualTo(new Object());
}
@Test
void testHashcode() {
- assertThat(ED_PRIVATE_KEY.hashCode()).isEqualTo(ED_PRIVATE_KEY.hashCode());
- assertThat(ED_PRIVATE_KEY.hashCode()).isNotEqualTo(EC_PRIVATE_KEY.hashCode());
+ assertThat(TestConstants.getEdPrivateKey().hashCode()).isEqualTo(TestConstants.getEdPrivateKey().hashCode());
+ assertThat(TestConstants.getEdPrivateKey().hashCode()).isNotEqualTo(TestConstants.getEcPrivateKey().hashCode());
- assertThat(EC_PRIVATE_KEY.hashCode()).isEqualTo(EC_PRIVATE_KEY.hashCode());
- assertThat(EC_PRIVATE_KEY.hashCode()).isNotEqualTo(ED_PRIVATE_KEY.hashCode());
+ assertThat(TestConstants.getEcPrivateKey().hashCode()).isEqualTo(TestConstants.getEcPrivateKey().hashCode());
+ assertThat(TestConstants.getEcPrivateKey().hashCode()).isNotEqualTo(TestConstants.getEdPrivateKey().hashCode());
}
@Test
void testToString() {
- assertThat(ED_PRIVATE_KEY.toString()).isEqualTo(
+ assertThat(TestConstants.getEdPrivateKey().toString()).isEqualTo(
"PrivateKey{" +
- "value=[redacted], " +
+ "value=[redacted]," +
+ "keyType=ED25519," +
"destroyed=false" +
"}"
);
- assertThat(EC_PRIVATE_KEY.toString()).isEqualTo(
+ assertThat(TestConstants.getEcPrivateKey().toString()).isEqualTo(
"PrivateKey{" +
- "value=[redacted], " +
+ "value=[redacted]," +
+ "keyType=SECP256K1," +
"destroyed=false" +
"}"
);
}
+ /**
+ * Ensure that no part of a {@link PrivateKey} is mutable from the outside (except for {@link PrivateKey#destroy()}.
+ */
+ @Test
+ void testImmutability() {
+ // Never changes, used to compare later in this test.
+ final UnsignedByteArray controlBytes = UnsignedByteArray.of(
+ BaseEncoding.base16().decode("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF")
+ );
+ Preconditions.checkArgument(controlBytes.length() == 32); // <-- Start from a known value.
+
+ UnsignedByteArray bytes32 = UnsignedByteArray.of(
+ BaseEncoding.base16().decode("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF")
+ );
+ Preconditions.checkArgument(bytes32.length() == 32); // <-- Start from a known value.
+ Preconditions.checkArgument(bytes32.equals(controlBytes));
+
+ ///////////////
+ // The tests
+ ///////////////
+
+ // Check that mutating the result of PrivateKey.fromNaturalBytes() doesn't enable inner mutability of the original.
+ PrivateKey testPrivateKey = PrivateKey.fromNaturalBytes(bytes32, KeyType.ED25519); // <-- The crux of the test
+ Preconditions.checkArgument(testPrivateKey.naturalBytes().equals(controlBytes)); // <-- Sanity check
+ testPrivateKey.destroy(); // <-- Set all bytes to 0
+ assertThat(testPrivateKey.naturalBytes()).isNotEqualTo(controlBytes); // <-- The test
+
+ // Check that mutating the result of PrivateKey.fromPrefixedBytes() doesn't enable inner mutability of the original.
+ testPrivateKey = PrivateKey.fromPrefixedBytes( // <-- The crux of the test
+ Secp256k1.withZeroPrefixPadding(bytes32, 33)
+ );
+ Preconditions.checkArgument(testPrivateKey.naturalBytes().equals(controlBytes)); // <-- Sanity check
+ testPrivateKey.destroy(); // <-- Set all bytes to 0
+ assertThat(testPrivateKey.naturalBytes()).isNotEqualTo(controlBytes); // <-- The test
+
+ // Check that mutating the result of PrivateKey.naturalBytes() doesn't enable inner mutability of the original.
+ testPrivateKey = PrivateKey.fromNaturalBytes(bytes32, KeyType.SECP256K1);
+ Preconditions.checkArgument(testPrivateKey.naturalBytes().equals(controlBytes)); // <-- Sanity check
+ UnsignedByteArray valueBytes = testPrivateKey.naturalBytes(); // <-- The crux of the test
+ valueBytes.destroy(); // <-- Set all bytes to 0
+ assertThat(testPrivateKey.naturalBytes()).isEqualTo(controlBytes); // <-- The test
+
+ // Check that mutating the result of PrivateKey.prefixedBytes() doesn't enable inner mutability of the original.
+ testPrivateKey = PrivateKey.fromNaturalBytes(bytes32, KeyType.SECP256K1);
+ Preconditions.checkArgument(testPrivateKey.naturalBytes().equals(controlBytes)); // <-- Sanity check
+ valueBytes = testPrivateKey.prefixedBytes(); // <-- The crux of the test
+ valueBytes.destroy(); // <-- Set all bytes to 0
+ assertThat(testPrivateKey.naturalBytes()).isEqualTo(controlBytes); // <-- The test
+
+ // Check that PrivateKey.value() doesn't enable inner mutability.
+ testPrivateKey = PrivateKey.fromNaturalBytes(bytes32, KeyType.SECP256K1);
+ Preconditions.checkArgument(testPrivateKey.naturalBytes().equals(controlBytes)); // <-- Sanity check
+ valueBytes = testPrivateKey.value(); // <-- The crux of the test
+ valueBytes.destroy(); // <-- Set all bytes to 0
+ assertThat(testPrivateKey.naturalBytes()).isEqualTo(controlBytes); // <-- The test
+ }
+
}
diff --git a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/crypto/keys/PublicKeyTest.java b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/crypto/keys/PublicKeyTest.java
index ed9d9b304..0a072fb29 100644
--- a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/crypto/keys/PublicKeyTest.java
+++ b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/crypto/keys/PublicKeyTest.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.
@@ -21,6 +21,7 @@
*/
import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.xrpl.xrpl4j.crypto.TestConstants.EC_PUBLIC_KEY;
import static org.xrpl.xrpl4j.crypto.TestConstants.EC_PUBLIC_KEY_B58;
import static org.xrpl.xrpl4j.crypto.TestConstants.EC_PUBLIC_KEY_HEX;
@@ -32,6 +33,7 @@
import org.junit.jupiter.api.Test;
import org.xrpl.xrpl4j.codec.addresses.KeyType;
import org.xrpl.xrpl4j.codec.addresses.UnsignedByteArray;
+import org.xrpl.xrpl4j.codec.addresses.exceptions.EncodeException;
import org.xrpl.xrpl4j.model.jackson.ObjectMapperFactory;
import org.xrpl.xrpl4j.model.transactions.Address;
@@ -40,6 +42,15 @@
*/
public class PublicKeyTest {
+ @Test
+ public void fromBase58EncodedStringEd25519WithTooFewBytes() {
+ UnsignedByteArray twoBytes = UnsignedByteArray.of(new byte[] {(byte) 0xED, (byte) 0xFF});
+ EncodeException exception = assertThrows(
+ EncodeException.class, () -> PublicKey.fromBase16EncodedPublicKey(twoBytes.hexValue())
+ );
+ assertThat(exception.getMessage()).isEqualTo("Length of bytes does not match expectedLength of 33.");
+ }
+
@Test
public void fromBase58EncodedStringEd25519() {
assertThat(PublicKey.fromBase58EncodedPublicKey(ED_PUBLIC_KEY_B58).base58Value()).isEqualTo(ED_PUBLIC_KEY_B58);
@@ -196,7 +207,7 @@ void jsonSerializeAndDeserializeMultiSignKey() throws JsonProcessingException {
PublicKey actual = ObjectMapperFactory.create().readValue(json, PublicKey.class);
assertThat(actual.base16Value()).isEqualTo("");
}
-
+
@Test
void jsonSerializeAndDeserializeEc() throws JsonProcessingException {
String json = ObjectMapperFactory.create().writeValueAsString(EC_PUBLIC_KEY);
@@ -211,4 +222,14 @@ void deriveAddress() {
assertThat(ED_PUBLIC_KEY.deriveAddress().value()).isEqualTo("rwGWYtRR6jJJJq7FKQg74YwtkiPyUqJ466");
assertThat(EC_PUBLIC_KEY.deriveAddress().value()).isEqualTo("rD8ATvjj9mfnFuYYTGRNb9DygnJW9JNN1C");
}
+
+ ///////////////////
+ // Constants
+ ///////////////////
+
+ @Test
+ void testConstants() {
+ assertThat(PublicKey.ED2559_PREFIX.asInt()).isEqualTo(0xED);
+ assertThat(PublicKey.ED2559_PREFIX.asByte()).isEqualTo((byte) 0xED);
+ }
}
diff --git a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/crypto/keys/SeedTest.java b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/crypto/keys/SeedTest.java
index b59c0bebd..4e4777415 100644
--- a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/crypto/keys/SeedTest.java
+++ b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/crypto/keys/SeedTest.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.
@@ -29,24 +29,19 @@
import org.xrpl.xrpl4j.codec.addresses.KeyType;
import org.xrpl.xrpl4j.codec.addresses.UnsignedByteArray;
import org.xrpl.xrpl4j.codec.addresses.exceptions.DecodeException;
-import org.xrpl.xrpl4j.crypto.keys.Base58EncodedSecret;
-import org.xrpl.xrpl4j.crypto.keys.Entropy;
-import org.xrpl.xrpl4j.crypto.keys.KeyPair;
-import org.xrpl.xrpl4j.crypto.keys.Passphrase;
-import org.xrpl.xrpl4j.crypto.keys.PrivateKey;
-import org.xrpl.xrpl4j.crypto.keys.PublicKey;
-import org.xrpl.xrpl4j.crypto.keys.Seed;
import org.xrpl.xrpl4j.crypto.keys.Seed.DefaultSeed;
+import java.math.BigInteger;
import javax.security.auth.DestroyFailedException;
/**
* Unit tests for {@link Seed}.
*/
+@SuppressWarnings("deprecation")
public class SeedTest {
- private Seed edSeed = Seed.ed25519SeedFromPassphrase(Passphrase.of("hello"));
- private Seed ecSeed = Seed.secp256k1SeedFromPassphrase(Passphrase.of("hello"));
+ private final Seed edSeed = Seed.ed25519SeedFromPassphrase(Passphrase.of("hello"));
+ private final Seed ecSeed = Seed.secp256k1SeedFromPassphrase(Passphrase.of("hello"));
@Test
void constructorWithNullSeed() {
@@ -58,10 +53,7 @@ void constructorWithNullSeed() {
@Test
void constructorWithNullUnsignedByteArray() {
- assertThrows(NullPointerException.class, () -> {
- UnsignedByteArray nullUba = null;
- new DefaultSeed(nullUba);
- });
+ assertThrows(NullPointerException.class, () -> new DefaultSeed((UnsignedByteArray) null));
}
@Test
@@ -126,6 +118,7 @@ void testSecp256k1SeedFromPassphraseWithNull() {
@Test
public void testEd25519SeedFromPassphrase() throws DestroyFailedException {
+ //noinspection OptionalGetWithoutIsPresent
assertThat(edSeed.decodedSeed().type().get()).isEqualTo(KeyType.ED25519);
assertThat(BaseEncoding.base64().encode(edSeed.decodedSeed().bytes().toByteArray()))
.isEqualTo("m3HSJL1i83hdltRq0+o9cw==");
@@ -136,6 +129,7 @@ public void testEd25519SeedFromPassphrase() throws DestroyFailedException {
@Test
public void testSecp256k1SeedFromPassphrase() throws DestroyFailedException {
+ //noinspection OptionalGetWithoutIsPresent
assertThat(ecSeed.decodedSeed().type().get()).isEqualTo(KeyType.SECP256K1);
assertThat(BaseEncoding.base64().encode(ecSeed.decodedSeed().bytes().toByteArray()))
.isEqualTo("m3HSJL1i83hdltRq0+o9cw==");
@@ -163,10 +157,110 @@ void seedFromBase58EncodedSecretSecp256k1() {
assertThat(seed.decodedSeed().bytes().hexValue()).isEqualTo("0102030405060708090A0B0C0D0E0F10");
}
+ /**
+ * Verify {@link Seed#deriveKeyPair()} using a seed that would have produced a 32 byte private key prior to fixing
+ * #486.
+ *
+ * @see "https://github.com/XRPLF/xrpl4j/issues/486"
+ */
+ @Test
+ void deriveKeyPairFor32ByteEcSeed() {
+ // Without explicit padding added by xrpl4j, but instead due to twos-complement padding, this seed would have
+ // (prior to fixing #486) produced a 32 byte private key. We validate here that it produces the correct output
+ // for either value() call.
+ final Seed ecSeedFor32BytePrivateKey = Seed.secp256k1SeedFromEntropy(
+ Entropy.of(BaseEncoding.base16().decode("B7E99C6BB9786494238D2BA84EABE854"))
+ );
+ final PrivateKey privateKey = ecSeedFor32BytePrivateKey.deriveKeyPair().privateKey();
+ assertThat(privateKey.value().hexValue()) // <- Overtly test .value()
+ .isEqualTo("007030CBD40D6961E625AD73159A4B463AA42B4E88CC2248AC49E1EDCB50AF2924");
+ assertThat(privateKey.prefixedBytes().hexValue())
+ .isEqualTo("007030CBD40D6961E625AD73159A4B463AA42B4E88CC2248AC49E1EDCB50AF2924");
+ assertThat(privateKey.naturalBytes().hexValue())
+ .isEqualTo("7030CBD40D6961E625AD73159A4B463AA42B4E88CC2248AC49E1EDCB50AF2924");
+ }
+
+ /**
+ * Verify {@link Seed#deriveKeyPair()} using a seed that would have produced a 33 byte private key prior to fixing
+ * #486.
+ *
+ * @see "https://github.com/XRPLF/xrpl4j/issues/486"
+ */
+ @Test
+ void deriveKeyPairFor33ByteEcSeed() {
+ // Without explicit padding added by xrpl4j, but instead due to twos-complement padding, this seed would have
+ // (prior to fixing #486) produced a 33 byte private key. We validate here that it produces the correct output
+ // for either value() call.
+ final Seed ecSeedFor32BytePrivateKey = Seed.secp256k1SeedFromEntropy(
+ Entropy.of(BaseEncoding.base16().decode("358C75D5AD5E2FF5E19256978F18F0B9"))
+ );
+ final PrivateKey privateKey = ecSeedFor32BytePrivateKey.deriveKeyPair().privateKey();
+ assertThat(privateKey.value().hexValue()) // <- Overtly test .value()
+ .isEqualTo("00FBD60C9C99BA3D4706A449C30E4D61DCC3811E23EF69291F98A886CEC6A8B0B5");
+ assertThat(privateKey.prefixedBytes().hexValue())
+ .isEqualTo("00FBD60C9C99BA3D4706A449C30E4D61DCC3811E23EF69291F98A886CEC6A8B0B5");
+ assertThat(privateKey.naturalBytes().hexValue())
+ .isEqualTo("FBD60C9C99BA3D4706A449C30E4D61DCC3811E23EF69291F98A886CEC6A8B0B5");
+ }
+
+ /**
+ * Verify that BigInteger construction behaves the same no matter how many zero-byte prefix pads are used.
+ */
+ @Test
+ void verifyBigIntegerConstructionForSecp256k1PrivateKeys() {
+ // This seed will generate a secp256k1 SecretKey D value of
+ // 53AC3F62A5A6E598C7D1E31AB92587C56823A1BE5C21E53ABE9D9A722E5236 (which is only 31 bytes long).
+ Seed ecSeedFor31BytePrivateKey = Seed.fromBase58EncodedSecret(
+ Base58EncodedSecret.of("shZrqKhyGi5Gyvrv5AJawNMuR2WaN")
+ );
+
+ assertThat(BaseEncoding.base16().decode("53AC3F62A5A6E598C7D1E31AB92587C56823A1BE5C21E53ABE9D9A722E5236").length)
+ .isEqualTo(31);
+ assertThat(ecSeedFor31BytePrivateKey.deriveKeyPair().privateKey().naturalBytes().toByteArray().length)
+ .isEqualTo(32);
+ assertThat(ecSeedFor31BytePrivateKey.deriveKeyPair().privateKey().prefixedBytes().toByteArray().length)
+ .isEqualTo(33);
+
+ // Assert that all BigIntegers are equivalent (i.e., that the constructor ignores zero-byte pads)
+ BigInteger shortBigInt = new BigInteger(
+ BaseEncoding.base16().decode("53AC3F62A5A6E598C7D1E31AB92587C56823A1BE5C21E53ABE9D9A722E5236")
+ );
+ BigInteger unPaddedBitInt = new BigInteger(
+ 1, ecSeedFor31BytePrivateKey.deriveKeyPair().privateKey().naturalBytes().toByteArray()
+ );
+ BigInteger paddedBitInt = new BigInteger(
+ 1, ecSeedFor31BytePrivateKey.deriveKeyPair().privateKey().prefixedBytes().toByteArray()
+ );
+
+ assertThat(shortBigInt).isEqualTo(unPaddedBitInt);
+ assertThat(shortBigInt).isEqualTo(paddedBitInt);
+ assertThat(paddedBitInt).isEqualTo(unPaddedBitInt);
+ }
+
+ /**
+ * Verify {@link Seed#deriveKeyPair()} using a seed that would have produced a 31 byte private key (prior to fixing
+ * #486) generates expected results.
+ *
+ * @see "https://github.com/XRPLF/xrpl4j/issues/486"
+ */
+ @Test
+ void deriveKeyPairFor31ByteEcSeed() {
+ Seed ecSeedFor31BytePrivateKey = Seed.fromBase58EncodedSecret(
+ Base58EncodedSecret.of("shZrqKhyGi5Gyvrv5AJawNMuR2WaN")
+ );
+ final PrivateKey privateKey = ecSeedFor31BytePrivateKey.deriveKeyPair().privateKey();
+ assertThat(privateKey.value().hexValue()) // <- Overtly test .value()
+ .isEqualTo("000053AC3F62A5A6E598C7D1E31AB92587C56823A1BE5C21E53ABE9D9A722E5236");
+ assertThat(privateKey.prefixedBytes().hexValue())
+ .isEqualTo("000053AC3F62A5A6E598C7D1E31AB92587C56823A1BE5C21E53ABE9D9A722E5236");
+ assertThat(privateKey.naturalBytes().hexValue())
+ .isEqualTo("0053AC3F62A5A6E598C7D1E31AB92587C56823A1BE5C21E53ABE9D9A722E5236");
+ }
+
@Test
void testEquals() {
- assertThat(edSeed).isEqualTo(edSeed);
- assertThat(ecSeed).isEqualTo(ecSeed);
+ assertThat(edSeed.equals(edSeed)).isTrue();
+ assertThat(ecSeed.equals(ecSeed)).isTrue();
assertThat(edSeed).isNotEqualTo(ecSeed);
assertThat(ecSeed).isNotEqualTo(edSeed);
assertThat(ecSeed).isNotEqualTo(new Object());
@@ -200,7 +294,7 @@ public void deriveEd25519KeyPair() {
KeyPair keyPair = Seed.DefaultSeed.Ed25519KeyPairService.deriveKeyPair(seed);
KeyPair expectedKeyPair = KeyPair.builder()
- .privateKey(PrivateKey.of(UnsignedByteArray.of(
+ .privateKey(PrivateKey.fromPrefixedBytes(UnsignedByteArray.of(
BaseEncoding.base16().decode("ED2F1185B6F5525D7A7D2A22C1D8BAEEBEEFFE597C9010AF916EBB9447BECC5BE6"
))))
.publicKey(
@@ -232,7 +326,7 @@ public void deriveSecp256k1KeyPair() {
Seed seed = Seed.fromBase58EncodedSecret(Base58EncodedSecret.of("sp5fghtJtpUorTwvof1NpDXAzNwf5"));
KeyPair keyPair = Seed.DefaultSeed.Secp256k1KeyPairService.deriveKeyPair(seed);
KeyPair expectedKeyPair = KeyPair.builder()
- .privateKey(PrivateKey.of(UnsignedByteArray.of(
+ .privateKey(PrivateKey.fromPrefixedBytes(UnsignedByteArray.of(
BaseEncoding.base16().decode("00D78B9735C3F26501C7337B8A5727FD53A6EFDBC6AA55984F098488561F985E23"
))))
.publicKey(
@@ -249,8 +343,9 @@ public void generateSeedFromEd25519Seed() {
Seed seed = Seed.ed25519SeedFromEntropy(entropy);
assertThat(seed.deriveKeyPair().publicKey()).isEqualTo(
PublicKey.fromBase16EncodedPublicKey("ED01FA53FA5A7E77798F882ECE20B1ABC00BB358A9E55A202D0D0676BD0CE37A63"));
- assertThat(seed.deriveKeyPair().privateKey()).isEqualTo(
- PrivateKey.of(UnsignedByteArray.fromHex("EDB4C4E046826BD26190D09715FC31F4E6A728204EADD112905B08B14B7F15C4F3")));
+ assertThat(seed.deriveKeyPair().privateKey()).isEqualTo(PrivateKey.fromPrefixedBytes(
+ UnsignedByteArray.fromHex("EDB4C4E046826BD26190D09715FC31F4E6A728204EADD112905B08B14B7F15C4F3")
+ ));
assertThat(seed.deriveKeyPair().publicKey().deriveAddress().value()).isEqualTo(
"rLUEXYuLiQptky37CqLcm9USQpPiz5rkpD");
}
@@ -260,10 +355,13 @@ public void generateWalletFromSecp256k1Seed() {
Entropy entropy = Entropy.of(BaseEncoding.base16().decode("CC4E55BC556DD561CBE990E3D4EF7069"));
Seed seed = Seed.secp256k1SeedFromEntropy(entropy);
assertThat(seed.deriveKeyPair().publicKey().base16Value()).isEqualTo(
- "02FD0E8479CE8182ABD35157BB0FA17A469AF27DCB12B5DDED697C61809116A33B");
- assertThat(seed.deriveKeyPair().privateKey().value().hexValue()).isEqualTo(
- "27690792130FC12883E83AE85946B018B3BEDE6EEDCDA3452787A94FC0A17438");
+ "02FD0E8479CE8182ABD35157BB0FA17A469AF27DCB12B5DDED697C61809116A33B"
+ );
+ assertThat(seed.deriveKeyPair().privateKey().prefixedBytes().hexValue()).isEqualTo(
+ "0027690792130FC12883E83AE85946B018B3BEDE6EEDCDA3452787A94FC0A17438"
+ );
assertThat(seed.deriveKeyPair().publicKey().deriveAddress().value()).isEqualTo(
- "rByLcEZ7iwTBAK8FfjtpFuT7fCzt4kF4r2");
+ "rByLcEZ7iwTBAK8FfjtpFuT7fCzt4kF4r2"
+ );
}
}
diff --git a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/crypto/keys/bc/BcKeyUtilsTest.java b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/crypto/keys/bc/BcKeyUtilsTest.java
index 561287d17..1dbcc3a96 100644
--- a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/crypto/keys/bc/BcKeyUtilsTest.java
+++ b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/crypto/keys/bc/BcKeyUtilsTest.java
@@ -56,9 +56,10 @@ void edPrivateKeyParametersToPrivateKeyAndBack() {
// To PrivateKey
PrivateKey privateKey = BcKeyUtils.toPrivateKey(ed25519PrivateKeyParameters);
- assertThat(Base58.encode(privateKey.value().toByteArray()))
+ assertThat(Base58.encode(privateKey.prefixedBytes().toByteArray()))
.isEqualTo("pDcQTi2uFBAzQ7cY2mYQtk9QuQBoLU6rJypEf8EYPQoouh");
- assertThat(BaseEncoding.base16().encode(privateKey.value().toByteArray())).isEqualTo("ED" + ED_PRIVATE_KEY_HEX);
+ assertThat(BaseEncoding.base16().encode(privateKey.prefixedBytes().toByteArray()))
+ .isEqualTo("ED" + ED_PRIVATE_KEY_HEX);
// Convert back
Ed25519PrivateKeyParameters converted = BcKeyUtils.toEd25519PrivateKeyParams(privateKey);
@@ -73,10 +74,10 @@ void ecPrivateKeyParametersToPrivateKeyAndBack() {
// To PrivateKey
PrivateKey privateKey = BcKeyUtils.toPrivateKey(ecPrivateKeyParameters);
- assertThat(Base58.encode(privateKey.value().toByteArray()))
- .isEqualTo("EnYwxojogCYKG3F5Bf7zvcZjo76pEqKwG9wGH14JngcV");
- assertThat(BaseEncoding.base16().encode(privateKey.value().toByteArray()))
- .isEqualTo("D12D2FACA9AD92828D89683778CB8DFCCDBD6C9E92F6AB7D6065E8AACC1FF6D6");
+ assertThat(Base58.encode(privateKey.prefixedBytes().toByteArray()))
+ .isEqualTo("rEnYwxojogCYKG3F5Bf7zvcZjo76pEqKwG9wGH14JngcV");
+ assertThat(BaseEncoding.base16().encode(privateKey.prefixedBytes().toByteArray()))
+ .isEqualTo("00D12D2FACA9AD92828D89683778CB8DFCCDBD6C9E92F6AB7D6065E8AACC1FF6D6");
assertThat(BcKeyUtils.toEcPrivateKeyParams(privateKey)).usingRecursiveComparison()
.isEqualTo(ecPrivateKeyParameters);
diff --git a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/crypto/signing/AbstractSignatureServiceTest.java b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/crypto/signing/AbstractSignatureServiceTest.java
index 6abab24a9..03abb1692 100644
--- a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/crypto/signing/AbstractSignatureServiceTest.java
+++ b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/crypto/signing/AbstractSignatureServiceTest.java
@@ -193,12 +193,12 @@ public void signWithNullPrivateKey() {
@Test
public void signWithNullTransaction() {
assertThrows(NullPointerException.class,
- () -> signatureService.sign(TestConstants.ED_PRIVATE_KEY, (Transaction) null));
+ () -> signatureService.sign(TestConstants.getEdPrivateKey(), (Transaction) null));
}
@Test
public void signEd25519() {
- signatureService.sign(TestConstants.ED_PRIVATE_KEY, transactionMock);
+ signatureService.sign(TestConstants.getEdPrivateKey(), transactionMock);
verify(signatureUtilsMock, times(0)).toMultiSignableBytes(any(), any());
verify(signatureUtilsMock).toSignableBytes(transactionMock);
@@ -208,7 +208,7 @@ public void signEd25519() {
@Test
public void signSecp256k1() {
- signatureService.sign(TestConstants.EC_PRIVATE_KEY, transactionMock);
+ signatureService.sign(TestConstants.getEcPrivateKey(), transactionMock);
verify(signatureUtilsMock, times(0)).toMultiSignableBytes(any(), any());
verify(signatureUtilsMock).toSignableBytes(transactionMock);
@@ -220,7 +220,7 @@ public void signSecp256k1() {
void multiSignEd25519() {
when(signedTransactionMock.signature()).thenReturn(ed25519SignatureMock);
- final Signature signature = signatureService.multiSign(TestConstants.ED_PRIVATE_KEY, transactionMock);
+ final Signature signature = signatureService.multiSign(TestConstants.getEdPrivateKey(), transactionMock);
assertThat(signature).isEqualTo(ed25519SignatureMock);
verify(signatureUtilsMock).toMultiSignableBytes(transactionMock, TestConstants.ED_ADDRESS);
@@ -232,7 +232,7 @@ void multiSignEd25519() {
void multiSignSecp256k1() {
when(signedTransactionMock.signature()).thenReturn(secp256k1SignatureMock);
- final Signature signature = signatureService.multiSign(TestConstants.EC_PRIVATE_KEY, transactionMock);
+ final Signature signature = signatureService.multiSign(TestConstants.getEcPrivateKey(), transactionMock);
assertThat(signature).isEqualTo(secp256k1SignatureMock);
verify(signatureUtilsMock).toMultiSignableBytes(transactionMock, TestConstants.EC_ADDRESS);
@@ -252,7 +252,7 @@ public void signUnsignedClaimWithNullPrivateKey() {
@Test
public void signUnsignedClaimWithNullUnsignedClaim() {
assertThrows(NullPointerException.class,
- () -> signatureService.sign(TestConstants.ED_PRIVATE_KEY, (UnsignedClaim) null));
+ () -> signatureService.sign(TestConstants.getEdPrivateKey(), (UnsignedClaim) null));
}
@Test
@@ -260,7 +260,7 @@ public void signUnsignedClaimEd25519() {
UnsignedClaim unsignedClaimMock = mock(UnsignedClaim.class);
when(signatureUtilsMock.toSignableBytes(unsignedClaimMock)).thenReturn(UnsignedByteArray.empty());
- Signature actualSignature = signatureService.sign(TestConstants.ED_PRIVATE_KEY, unsignedClaimMock);
+ Signature actualSignature = signatureService.sign(TestConstants.getEdPrivateKey(), unsignedClaimMock);
assertThat(actualSignature).isEqualTo(ed25519SignatureMock);
verify(signatureUtilsMock, times(0)).toMultiSignableBytes(any(), any());
@@ -350,7 +350,7 @@ public void verifyMultiSecp256k1() {
@Test
public void edDsaSign() {
- Signature actual = signatureService.edDsaSign(TestConstants.ED_PRIVATE_KEY, UnsignedByteArray.empty());
+ Signature actual = signatureService.edDsaSign(TestConstants.getEdPrivateKey(), UnsignedByteArray.empty());
assertThat(actual).isEqualTo(ed25519SignatureMock);
assertThat(ed25519VerifyCalled.get()).isFalse();
@@ -364,7 +364,7 @@ public void edDsaSign() {
@Test
public void ecDsaSign() {
- Signature actual = signatureService.ecDsaSign(TestConstants.EC_PRIVATE_KEY, UnsignedByteArray.empty());
+ Signature actual = signatureService.ecDsaSign(TestConstants.getEcPrivateKey(), UnsignedByteArray.empty());
assertThat(actual).isEqualTo(secp256k1SignatureMock);
diff --git a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/crypto/signing/SignatureUtilsTest.java b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/crypto/signing/SignatureUtilsTest.java
index 0590a68c1..bd0441ff1 100644
--- a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/crypto/signing/SignatureUtilsTest.java
+++ b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/crypto/signing/SignatureUtilsTest.java
@@ -47,12 +47,25 @@
import org.xrpl.xrpl4j.codec.binary.XrplBinaryCodec;
import org.xrpl.xrpl4j.crypto.keys.PublicKey;
import org.xrpl.xrpl4j.model.client.channels.UnsignedClaim;
+import org.xrpl.xrpl4j.model.flags.AmmDepositFlags;
+import org.xrpl.xrpl4j.model.flags.AmmWithdrawFlags;
+import org.xrpl.xrpl4j.model.flags.TransactionFlags;
+import org.xrpl.xrpl4j.model.ledger.AuthAccount;
+import org.xrpl.xrpl4j.model.ledger.AuthAccountWrapper;
+import org.xrpl.xrpl4j.model.ledger.Issue;
import org.xrpl.xrpl4j.model.transactions.AccountDelete;
import org.xrpl.xrpl4j.model.transactions.AccountSet;
import org.xrpl.xrpl4j.model.transactions.Address;
+import org.xrpl.xrpl4j.model.transactions.AmmBid;
+import org.xrpl.xrpl4j.model.transactions.AmmCreate;
+import org.xrpl.xrpl4j.model.transactions.AmmDelete;
+import org.xrpl.xrpl4j.model.transactions.AmmDeposit;
+import org.xrpl.xrpl4j.model.transactions.AmmVote;
+import org.xrpl.xrpl4j.model.transactions.AmmWithdraw;
import org.xrpl.xrpl4j.model.transactions.CheckCancel;
import org.xrpl.xrpl4j.model.transactions.CheckCash;
import org.xrpl.xrpl4j.model.transactions.CheckCreate;
+import org.xrpl.xrpl4j.model.transactions.Clawback;
import org.xrpl.xrpl4j.model.transactions.DepositPreAuth;
import org.xrpl.xrpl4j.model.transactions.EscrowCancel;
import org.xrpl.xrpl4j.model.transactions.EscrowCreate;
@@ -75,6 +88,7 @@
import org.xrpl.xrpl4j.model.transactions.SignerListSet;
import org.xrpl.xrpl4j.model.transactions.SignerWrapper;
import org.xrpl.xrpl4j.model.transactions.TicketCreate;
+import org.xrpl.xrpl4j.model.transactions.TradingFee;
import org.xrpl.xrpl4j.model.transactions.Transaction;
import org.xrpl.xrpl4j.model.transactions.TrustSet;
import org.xrpl.xrpl4j.model.transactions.XrpCurrencyAmount;
@@ -573,6 +587,154 @@ void addSignatureToTicketCreate() {
addSignatureToTransactionHelper(ticketCreate);
}
+ @Test
+ void addSignatureToAmmBid() {
+ AmmBid bid = AmmBid.builder()
+ .account(sourcePublicKey.deriveAddress())
+ .asset(Issue.XRP)
+ .asset2(
+ Issue.builder()
+ .issuer(Address.of("rP9jPyP5kyvFRb6ZiRghAGw5u8SGAmU4bd"))
+ .currency("TST")
+ .build()
+ )
+ .addAuthAccounts(
+ AuthAccountWrapper.of(AuthAccount.of(Address.of("rMKXGCbJ5d8LbrqthdG46q3f969MVK2Qeg"))),
+ AuthAccountWrapper.of(AuthAccount.of(Address.of("rBepJuTLFJt3WmtLXYAxSjtBWAeQxVbncv")))
+ )
+ .fee(XrpCurrencyAmount.ofDrops(10))
+ .sequence(UnsignedInteger.valueOf(9))
+ .signingPublicKey(sourcePublicKey)
+ .build();
+
+ addSignatureToTransactionHelper(bid);
+ }
+
+ @Test
+ void addSignatureToAmmCreate() {
+ AmmCreate ammCreate = AmmCreate.builder()
+ .account(sourcePublicKey.deriveAddress())
+ .amount(
+ IssuedCurrencyAmount.builder()
+ .currency("TST")
+ .issuer(Address.of("rP9jPyP5kyvFRb6ZiRghAGw5u8SGAmU4bd"))
+ .value("25")
+ .build()
+ )
+ .amount2(XrpCurrencyAmount.ofDrops(250000000))
+ .fee(XrpCurrencyAmount.ofDrops(10))
+ .sequence(UnsignedInteger.valueOf(6))
+ .tradingFee(TradingFee.of(UnsignedInteger.valueOf(500)))
+ .signingPublicKey(sourcePublicKey)
+ .build();
+
+ addSignatureToTransactionHelper(ammCreate);
+ }
+
+ @Test
+ void addSignatureToAmmDeposit() {
+ AmmDeposit deposit = AmmDeposit.builder()
+ .account(sourcePublicKey.deriveAddress())
+ .fee(XrpCurrencyAmount.ofDrops(10))
+ .flags(AmmDepositFlags.LIMIT_LP_TOKEN)
+ .asset(Issue.XRP)
+ .asset2(
+ Issue.builder()
+ .issuer(Address.of("rP9jPyP5kyvFRb6ZiRghAGw5u8SGAmU4bd"))
+ .currency("TST")
+ .build()
+ )
+ .lpTokenOut(
+ IssuedCurrencyAmount.builder()
+ .currency("039C99CD9AB0B70B32ECDA51EAAE471625608EA2")
+ .issuer(Address.of("rE54zDvgnghAoPopCgvtiqWNq3dU5y836S"))
+ .value("100")
+ .build()
+ )
+ .signingPublicKey(sourcePublicKey)
+ .build();
+
+ addSignatureToTransactionHelper(deposit);
+ }
+
+ @Test
+ void addSignatureToAmmVote() {
+ AmmVote vote = AmmVote.builder()
+ .account(sourcePublicKey.deriveAddress())
+ .asset(Issue.XRP)
+ .asset2(
+ Issue.builder()
+ .currency("TST")
+ .issuer(Address.of("rP9jPyP5kyvFRb6ZiRghAGw5u8SGAmU4bd"))
+ .build()
+ )
+ .fee(XrpCurrencyAmount.ofDrops(10))
+ .sequence(UnsignedInteger.valueOf(8))
+ .tradingFee(TradingFee.of(UnsignedInteger.valueOf(600)))
+ .signingPublicKey(sourcePublicKey)
+ .build();
+
+ addSignatureToTransactionHelper(vote);
+ }
+
+ @Test
+ void addSignatureToAmmWithdraw() {
+ AmmWithdraw withdraw = AmmWithdraw.builder()
+ .account(sourcePublicKey.deriveAddress())
+ .fee(XrpCurrencyAmount.ofDrops(10))
+ .asset(
+ Issue.builder()
+ .issuer(Address.of("rP9jPyP5kyvFRb6ZiRghAGw5u8SGAmU4bd"))
+ .currency("TST")
+ .build()
+ )
+ .asset2(Issue.XRP)
+ .flags(AmmWithdrawFlags.WITHDRAW_ALL)
+ .signingPublicKey(sourcePublicKey)
+ .build();
+
+ addSignatureToTransactionHelper(withdraw);
+ }
+
+ @Test
+ void addSignatureToAmmDelete() {
+ AmmDelete ammDelete = AmmDelete.builder()
+ .asset(Issue.XRP)
+ .asset2(
+ Issue.builder()
+ .issuer(Address.of("rP9jPyP5kyvFRb6ZiRghAGw5u8SGAmU4bd"))
+ .currency("TST")
+ .build()
+ )
+ .account(Address.of("rJVUeRqDFNs2xqA7ncVE6ZoAhPUoaJJSQm"))
+ .fee(XrpCurrencyAmount.ofDrops(10))
+ .sequence(UnsignedInteger.valueOf(9))
+ .flags(TransactionFlags.UNSET)
+ .signingPublicKey(sourcePublicKey)
+ .build();
+
+ addSignatureToTransactionHelper(ammDelete);
+ }
+
+ @Test
+ void addSignatureToClawback() {
+ Clawback clawback = Clawback.builder()
+ .account(sourcePublicKey.deriveAddress())
+ .fee(XrpCurrencyAmount.ofDrops(10))
+ .sequence(UnsignedInteger.ONE)
+ .signingPublicKey(sourcePublicKey)
+ .amount(
+ IssuedCurrencyAmount.builder()
+ .currency("FOO")
+ .issuer(Address.of("rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW"))
+ .value("314.159")
+ .build()
+ )
+ .build();
+
+ addSignatureToTransactionHelper(clawback);
+ }
+
@Test
public void addSignatureToTransactionUnsupported() {
assertThrows(IllegalArgumentException.class, () -> addSignatureToTransactionHelper(transactionMock));
@@ -892,6 +1054,147 @@ void addMultiSignaturesToTicketCreate() {
addMultiSignatureToTransactionHelper(ticketCreate);
}
+ @Test
+ void addMultiSignaturesToClawback() {
+ Clawback clawback = Clawback.builder()
+ .account(sourcePublicKey.deriveAddress())
+ .fee(XrpCurrencyAmount.ofDrops(10))
+ .sequence(UnsignedInteger.ONE)
+ .amount(
+ IssuedCurrencyAmount.builder()
+ .currency("FOO")
+ .issuer(Address.of("rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW"))
+ .value("314.159")
+ .build()
+ )
+ .build();
+
+ addMultiSignatureToTransactionHelper(clawback);
+ }
+
+ @Test
+ void addMultiSignatureToAmmBid() {
+ AmmBid bid = AmmBid.builder()
+ .account(sourcePublicKey.deriveAddress())
+ .asset(Issue.XRP)
+ .asset2(
+ Issue.builder()
+ .issuer(Address.of("rP9jPyP5kyvFRb6ZiRghAGw5u8SGAmU4bd"))
+ .currency("TST")
+ .build()
+ )
+ .addAuthAccounts(
+ AuthAccountWrapper.of(AuthAccount.of(Address.of("rMKXGCbJ5d8LbrqthdG46q3f969MVK2Qeg"))),
+ AuthAccountWrapper.of(AuthAccount.of(Address.of("rBepJuTLFJt3WmtLXYAxSjtBWAeQxVbncv")))
+ )
+ .fee(XrpCurrencyAmount.ofDrops(10))
+ .sequence(UnsignedInteger.valueOf(9))
+ .build();
+
+ addMultiSignatureToTransactionHelper(bid);
+ }
+
+ @Test
+ void addMultiSignatureToAmmCreate() {
+ AmmCreate ammCreate = AmmCreate.builder()
+ .account(sourcePublicKey.deriveAddress())
+ .amount(
+ IssuedCurrencyAmount.builder()
+ .currency("TST")
+ .issuer(Address.of("rP9jPyP5kyvFRb6ZiRghAGw5u8SGAmU4bd"))
+ .value("25")
+ .build()
+ )
+ .amount2(XrpCurrencyAmount.ofDrops(250000000))
+ .fee(XrpCurrencyAmount.ofDrops(10))
+ .sequence(UnsignedInteger.valueOf(6))
+ .tradingFee(TradingFee.of(UnsignedInteger.valueOf(500)))
+ .build();
+
+ addMultiSignatureToTransactionHelper(ammCreate);
+ }
+
+ @Test
+ void addMultiSignatureToAmmDeposit() {
+ AmmDeposit deposit = AmmDeposit.builder()
+ .account(sourcePublicKey.deriveAddress())
+ .fee(XrpCurrencyAmount.ofDrops(10))
+ .flags(AmmDepositFlags.LIMIT_LP_TOKEN)
+ .asset(Issue.XRP)
+ .asset2(
+ Issue.builder()
+ .issuer(Address.of("rP9jPyP5kyvFRb6ZiRghAGw5u8SGAmU4bd"))
+ .currency("TST")
+ .build()
+ )
+ .lpTokenOut(
+ IssuedCurrencyAmount.builder()
+ .currency("039C99CD9AB0B70B32ECDA51EAAE471625608EA2")
+ .issuer(Address.of("rE54zDvgnghAoPopCgvtiqWNq3dU5y836S"))
+ .value("100")
+ .build()
+ )
+ .build();
+
+ addMultiSignatureToTransactionHelper(deposit);
+ }
+
+ @Test
+ void addMultiSignatureToAmmVote() {
+ AmmVote vote = AmmVote.builder()
+ .account(sourcePublicKey.deriveAddress())
+ .asset(Issue.XRP)
+ .asset2(
+ Issue.builder()
+ .currency("TST")
+ .issuer(Address.of("rP9jPyP5kyvFRb6ZiRghAGw5u8SGAmU4bd"))
+ .build()
+ )
+ .fee(XrpCurrencyAmount.ofDrops(10))
+ .sequence(UnsignedInteger.valueOf(8))
+ .tradingFee(TradingFee.of(UnsignedInteger.valueOf(600)))
+ .build();
+
+ addMultiSignatureToTransactionHelper(vote);
+ }
+
+ @Test
+ void addMultiSignatureToAmmWithdraw() {
+ AmmWithdraw withdraw = AmmWithdraw.builder()
+ .account(sourcePublicKey.deriveAddress())
+ .fee(XrpCurrencyAmount.ofDrops(10))
+ .asset(
+ Issue.builder()
+ .issuer(Address.of("rP9jPyP5kyvFRb6ZiRghAGw5u8SGAmU4bd"))
+ .currency("TST")
+ .build()
+ )
+ .asset2(Issue.XRP)
+ .flags(AmmWithdrawFlags.WITHDRAW_ALL)
+ .build();
+
+ addMultiSignatureToTransactionHelper(withdraw);
+ }
+
+ @Test
+ void addMultiSignatureToAmmDelete() {
+ AmmDelete ammDelete = AmmDelete.builder()
+ .asset(Issue.XRP)
+ .asset2(
+ Issue.builder()
+ .issuer(Address.of("rP9jPyP5kyvFRb6ZiRghAGw5u8SGAmU4bd"))
+ .currency("TST")
+ .build()
+ )
+ .account(Address.of("rJVUeRqDFNs2xqA7ncVE6ZoAhPUoaJJSQm"))
+ .fee(XrpCurrencyAmount.ofDrops(10))
+ .sequence(UnsignedInteger.valueOf(9))
+ .flags(TransactionFlags.UNSET)
+ .build();
+
+ addMultiSignatureToTransactionHelper(ammDelete);
+ }
+
@Test
public void addMultiSignaturesToTransactionUnsupported() {
when(transactionMock.transactionSignature()).thenReturn(Optional.empty());
diff --git a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/crypto/signing/bc/Secp256k1Test.java b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/crypto/signing/bc/Secp256k1Test.java
new file mode 100644
index 000000000..67c44446a
--- /dev/null
+++ b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/crypto/signing/bc/Secp256k1Test.java
@@ -0,0 +1,277 @@
+package org.xrpl.xrpl4j.crypto.signing.bc;
+
+import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import com.google.common.io.BaseEncoding;
+import org.assertj.core.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtensionContext;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.ArgumentsProvider;
+import org.junit.jupiter.params.provider.ArgumentsSource;
+import org.xrpl.xrpl4j.codec.addresses.UnsignedByte;
+import org.xrpl.xrpl4j.codec.addresses.UnsignedByteArray;
+
+import java.math.BigInteger;
+import java.util.Locale;
+import java.util.stream.Stream;
+
+/**
+ * Unit tests for {@link Secp256k1}.
+ */
+class Secp256k1Test {
+
+ /////////
+ // toUnsignedByteArray(BigInteger)
+ /////////
+
+ @Test
+ void fromBigIntegerWithInvalidInputs() {
+ assertThrows(NullPointerException.class, () -> Secp256k1.toUnsignedByteArray(
+ null, // <-- The crux of the test
+ 0
+ ));
+ assertThrows(IllegalArgumentException.class, () -> Secp256k1.toUnsignedByteArray(
+ BigInteger.valueOf(-1L), // <-- The crux of the test
+ 0
+ ));
+ assertThrows(IllegalArgumentException.class, () -> Secp256k1.toUnsignedByteArray(
+ BigInteger.valueOf(1L),
+ -1 // <-- The crux of the test
+ ));
+ }
+
+ @Test
+ void fromBigIntegerWithZeroLength() {
+ Assertions.assertThat(Secp256k1.toUnsignedByteArray(
+ BigInteger.valueOf(1L),
+ 0 // <-- The crux of the test
+ )
+ .hexValue()).isEqualTo("01");
+ }
+
+ @ParameterizedTest
+ @ArgumentsSource(BigIntegerByteEncodingsProvider.class)
+ void fromBigIntegerWithZeroPaddingBytes(
+ final String amountString,
+ final String amountToString16,
+ final String amountToByteArrayHexUnpadded,
+ final String amountToByteArrayHexPrefixPadded
+ ) {
+ final BigInteger amount = new BigInteger(amountString);
+ // NOTE `amount.toString(16)` strips off all leading 0s, even in a nibble (beware of using this in actual impl code)
+ Assertions.assertThat(amount.toString(16).toUpperCase(Locale.ENGLISH)).isEqualTo(amountToString16);
+ Assertions.assertThat(BaseEncoding.base16().encode(amount.toByteArray())).isEqualTo(amountToByteArrayHexUnpadded);
+ Assertions.assertThat(Secp256k1.toUnsignedByteArray(amount, 33).hexValue())
+ .isEqualTo(amountToByteArrayHexPrefixPadded);
+ }
+
+ @Test
+ void fromBigIntegerWithNumberGreaterThan33Bytes() {
+ final BigInteger amount = new BigInteger(
+ "194815934319126504488398097255143744553440248783815166056530734282223472643" +
+ "194815934319126504488398097255143744553440248783815166056530734282223472643");
+ // NOTE `amount.toString(16)` strips off all leading 0s, even in a nibble (beware of using this in actual impl code)
+ Assertions.assertThat(amount.toString(16).toUpperCase(Locale.ENGLISH)).isEqualTo(
+ "F3C607BB6CA7C4C335A24D0302484D16956259AC4510289E3E77A87BD72F36D1EED47F97D33F05F1715F603B45E83748DE37C087" +
+ "9DDE6060821AAAAD5003"
+ );
+ Assertions.assertThat(BaseEncoding.base16().encode(amount.toByteArray())).isEqualTo(
+ "00F3C607BB6CA7C4C335A24D0302484D16956259AC4510289E3E77A87BD72F36D1EED47F97D33F05F1715F603B45E83748DE37C087" +
+ "9DDE6060821AAAAD5003");
+ Assertions.assertThat(Secp256k1.toUnsignedByteArray(amount, 33).hexValue()).isEqualTo(
+ "00F3C607BB6CA7C4C335A24D0302484D16956259AC4510289E3E77A87BD72F36D1EED47F97D33F05F1715F603B45E83748DE37C087" +
+ "9DDE6060821AAAAD5003");
+ }
+
+ /////////////////////////
+ // withZeroPrefixPadding(UnsignedByteArray)
+ /////////////////////////
+
+ @Test
+ void withZeroPrefixPaddingWithUnsignedByteArrayWithInvalidInputs() {
+ UnsignedByteArray nullUba = null;
+ assertThrows(NullPointerException.class, () -> Secp256k1.withZeroPrefixPadding(
+ nullUba, // <-- The crux of the test
+ 0
+ ));
+
+ assertThat(Secp256k1.withZeroPrefixPadding(
+ UnsignedByteArray.of(BigInteger.valueOf(-1L).toByteArray()), // <-- The crux of the test
+ 0
+ )).isEqualTo(UnsignedByteArray.of(UnsignedByte.of(255)));
+
+ assertThrows(IllegalArgumentException.class, () -> Secp256k1.withZeroPrefixPadding(
+ UnsignedByteArray.of(BigInteger.valueOf(1L).toByteArray()),
+ -1 // <-- The crux of the test
+ ));
+ }
+
+ @Test
+ void withZeroPrefixPaddingWithUnsignedByteArrayWithZeroLength() {
+ Assertions.assertThat(Secp256k1.withZeroPrefixPadding(
+ UnsignedByteArray.of(BigInteger.valueOf(1L).toByteArray()),
+ 0 // <-- The crux of the test
+ )
+ .hexValue()).isEqualTo("01");
+ }
+
+ @ParameterizedTest
+ @ArgumentsSource(BigIntegerByteEncodingsProvider.class)
+ void withZeroPrefixPaddingWithUnsignedByteArray(
+ final String amountString,
+ final String amountToString16,
+ final String amountToByteArrayHexUnpadded,
+ final String amountToByteArrayHexPrefixPadded
+ ) {
+ final BigInteger amount = new BigInteger(amountString);
+ // NOTE `amount.toString(16)` strips off all leading 0s, even in a nibble (beware of using this in actual impl code)
+ Assertions.assertThat(amount.toString(16).toUpperCase(Locale.ENGLISH)).isEqualTo(amountToString16);
+ Assertions.assertThat(BaseEncoding.base16().encode(amount.toByteArray())).isEqualTo(amountToByteArrayHexUnpadded);
+
+ UnsignedByteArray uba = UnsignedByteArray.of(amount.toByteArray());
+ Assertions.assertThat(Secp256k1.withZeroPrefixPadding(uba, 33).hexValue())
+ .isEqualTo(amountToByteArrayHexPrefixPadded);
+ }
+
+ @Test
+ void withZeroPrefixPaddingWithUnsignedByteArrayExtend32() {
+ final byte[] bytes32 = new byte[32];
+ final UnsignedByteArray uba32 = UnsignedByteArray.of(bytes32);
+ final byte[] bytes33 = new byte[33];
+ final UnsignedByteArray uba33 = UnsignedByteArray.of(bytes33);
+
+ assertThrows(IllegalArgumentException.class, () -> Secp256k1.withZeroPrefixPadding(uba32, -1));
+ assertThat(Secp256k1.withZeroPrefixPadding(uba32, 0)).isEqualTo(uba32);
+ assertThat(Secp256k1.withZeroPrefixPadding(uba32, 1)).isEqualTo(uba32);
+ assertThat(Secp256k1.withZeroPrefixPadding(uba32, 32)).isEqualTo(uba32);
+ assertThat(Secp256k1.withZeroPrefixPadding(uba32, 33)).isEqualTo(uba33);
+ }
+
+ /////////////////////////
+ // withZeroPrefixPadding(byte[])
+ /////////////////////////
+
+ @Test
+ void withZeroPrefixPaddingWithByteArrayWithInvalidInputs() {
+ byte[] nullByteArray = null;
+ assertThrows(NullPointerException.class, () -> Secp256k1.withZeroPrefixPadding(
+ nullByteArray, // <-- The crux of the test
+ 0
+ ));
+
+ assertThat(Secp256k1.withZeroPrefixPadding(
+ BigInteger.valueOf(-1L).toByteArray(), // <-- The crux of the test
+ 0
+ )).isEqualTo(UnsignedByteArray.of(UnsignedByte.of(255)));
+
+ assertThrows(IllegalArgumentException.class, () -> Secp256k1.withZeroPrefixPadding(
+ BigInteger.valueOf(1L).toByteArray(),
+ -1 // <-- The crux of the test
+ ));
+ }
+
+ @Test
+ void withZeroPrefixPaddingWithByteArrayWithZeroLength() {
+ Assertions.assertThat(Secp256k1.withZeroPrefixPadding(
+ BigInteger.valueOf(1L).toByteArray(),
+ 0 // <-- The crux of the test
+ )
+ .hexValue()).isEqualTo("01");
+ }
+
+ @ParameterizedTest
+ @ArgumentsSource(BigIntegerByteEncodingsProvider.class)
+ void withZeroPrefixPaddingWithByteArray(
+ final String amountString,
+ final String amountToString16,
+ final String amountToByteArrayHexUnpadded,
+ final String amountToByteArrayHexPrefixPadded
+ ) {
+ final BigInteger amount = new BigInteger(amountString);
+ // NOTE `amount.toString(16)` strips off all leading 0s, even in a nibble (beware of using this in actual impl code)
+ Assertions.assertThat(amount.toString(16).toUpperCase(Locale.ENGLISH)).isEqualTo(amountToString16);
+ Assertions.assertThat(BaseEncoding.base16().encode(amount.toByteArray())).isEqualTo(amountToByteArrayHexUnpadded);
+ Assertions.assertThat(Secp256k1.withZeroPrefixPadding(amount.toByteArray(), 33).hexValue())
+ .isEqualTo(amountToByteArrayHexPrefixPadded);
+ }
+
+ @Test
+ void withZeroPrefixPaddingWithByteArrayExtend32() {
+ final byte[] bytes32 = new byte[32];
+ final UnsignedByteArray uba32 = UnsignedByteArray.of(bytes32);
+ final byte[] bytes33 = new byte[33];
+ final UnsignedByteArray uba33 = UnsignedByteArray.of(bytes33);
+
+ assertThrows(IllegalArgumentException.class, () -> Secp256k1.withZeroPrefixPadding(bytes32, -1));
+ assertThat(Secp256k1.withZeroPrefixPadding(bytes32, 0)).isEqualTo(uba32);
+ assertThat(Secp256k1.withZeroPrefixPadding(bytes32, 1)).isEqualTo(uba32);
+ assertThat(Secp256k1.withZeroPrefixPadding(bytes32, 32)).isEqualTo(uba32);
+ assertThat(Secp256k1.withZeroPrefixPadding(bytes32, 33)).isEqualTo(uba33);
+ }
+
+ /**
+ * An {@link ArgumentsProvider} that provides expected binary encodings for a variety of BigInteger representations.
+ */
+ static class BigIntegerByteEncodingsProvider implements ArgumentsProvider {
+
+ @Override
+ public Stream extends Arguments> provideArguments(ExtensionContext context) {
+ return Stream.of(
+ // A BigInteger comprised of 33 Bytes; toByteArray has 33 bytes; 0 extra padding added
+ Arguments.of(
+ "107371972967791294617431936514364612285184717182742299921102689634201232605691", // BigInt
+ "ED6262116F8D51F1FDD98C184F74CA48DDA7B049CB741F1F7A0564B88FE601FB", // .toString(16)
+ "00ED6262116F8D51F1FDD98C184F74CA48DDA7B049CB741F1F7A0564B88FE601FB", // .toByteArray()
+ "00ED6262116F8D51F1FDD98C184F74CA48DDA7B049CB741F1F7A0564B88FE601FB" // <-- Padded to 33 bytes
+ ),
+ // A BigInteger comprised of 33 Bytes; toByteArray has 33 bytes; 0 extra padding added
+ Arguments.of(
+ "84513109120471239583994879976286018548016554258021069224677925571161262209437", // BigInt
+ "BAD8B981A239980B1EC4CB901D698DDE7AA15F264D9537C7D141EE119DD5399D", // .toString(16)
+ "00BAD8B981A239980B1EC4CB901D698DDE7AA15F264D9537C7D141EE119DD5399D", // .toByteArray()
+ "00BAD8B981A239980B1EC4CB901D698DDE7AA15F264D9537C7D141EE119DD5399D" // <-- BigInteger Hex, Padded to 33 bytes
+ ),
+ // A BigInteger comprised of 32 Bytes; toByteArray has 32 bytes; 1 extra padding added
+ Arguments.of(
+ "8427551091932113544047724072139537481003293113704693219824523888925672289487", // BigInt
+ "12A1D32B744B18FA0186A44F32D9241869FA0A05B5B831F188831A07163534CF", // .toString(16)
+ "12A1D32B744B18FA0186A44F32D9241869FA0A05B5B831F188831A07163534CF", // .toByteArray()
+ "0012A1D32B744B18FA0186A44F32D9241869FA0A05B5B831F188831A07163534CF"// <-- BigInteger Hex, Padded to 33 bytes
+ ),
+ // A BigInteger comprised of 32 Bytes; toByteArray has 32 bytes; 1 extra padding added
+ Arguments.of("49026876502144691037964633390198098042987098960613207831256521276903508291997", // BigInt
+ "6C643A8EB51D365F3FF5B08C575DEA44B0D3CA5795BDD7B080A7057ABB9A319D", // .toString(16)
+ "6C643A8EB51D365F3FF5B08C575DEA44B0D3CA5795BDD7B080A7057ABB9A319D", // .toByteArray()
+ "006C643A8EB51D365F3FF5B08C575DEA44B0D3CA5795BDD7B080A7057ABB9A319D"// <-- BigInteger Hex, Padded to 33 bytes
+ ),
+ // A BigInteger comprised of 31 Bytes; toByteArray has 31 bytes; 2 extra padding added
+ Arguments.of("125364023161033659590032058970590371956067907570302268576097734468145372487", // BigInt
+ "46F41A0ECE7D0C61B5B36EA377E20621E23C13BD0ABBAEF80754180E9DDD47", // .toString(16)
+ "46F41A0ECE7D0C61B5B36EA377E20621E23C13BD0ABBAEF80754180E9DDD47", // .toByteArray()
+ "000046F41A0ECE7D0C61B5B36EA377E20621E23C13BD0ABBAEF80754180E9DDD47"// <-- BigInteger Hex, Padded to 33 bytes
+ ),
+ // A BigInteger comprised of 31 Bytes; toByteArray has 31 bytes; 2 extra padding added
+ Arguments.of("194815934319126504488398097255143744553440248783815166056530734282223472643", // BigInt
+ "6E430C9E47DFB2194C97385CC85C406DC69773145AE5DE6060821AAAAD5003", // .toString(16)
+ "6E430C9E47DFB2194C97385CC85C406DC69773145AE5DE6060821AAAAD5003", // .toByteArray()
+ "00006E430C9E47DFB2194C97385CC85C406DC69773145AE5DE6060821AAAAD5003"// <-- BigInteger Hex, Padded to 33 bytes
+ ),
+ // A BigInteger comprised of 30 Bytes; toByteArray has 30 bytes; 3 extra padding added
+ Arguments.of("116983811426126878045574354873599490265363256342001470285420476536129339", // BigInt
+ "10F32BB4E0B8B00469196EDACCCAA87A55409FF1C66330D7590449C7073B", // .toString(16)
+ "10F32BB4E0B8B00469196EDACCCAA87A55409FF1C66330D7590449C7073B", // .toByteArray()
+ "00000010F32BB4E0B8B00469196EDACCCAA87A55409FF1C66330D7590449C7073B" // <-- BigInteger Hex, Padded to 33 bytes
+ ),
+ // A BigInteger comprised of 30 Bytes; toByteArray has 30 bytes; 3 extra padding added
+ Arguments.of("95191719494323154714287471792160232496133380576373117355131561137428728", // BigInt
+ "DCADB6BD9E78F0ECE39BB26928F8E4B2A5C7F9CF62C15C1C554B5F458F8", // .toString(16)
+ "0DCADB6BD9E78F0ECE39BB26928F8E4B2A5C7F9CF62C15C1C554B5F458F8", // .toByteArray()
+ "0000000DCADB6BD9E78F0ECE39BB26928F8E4B2A5C7F9CF62C15C1C554B5F458F8" // <-- BigInteger Hex, Padded to 33 bytes
+ )
+ );
+ }
+ }
+}
\ No newline at end of file
diff --git a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/client/amm/AmmInfoRequestParamsTest.java b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/client/amm/AmmInfoRequestParamsTest.java
new file mode 100644
index 000000000..a06cbaece
--- /dev/null
+++ b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/client/amm/AmmInfoRequestParamsTest.java
@@ -0,0 +1,46 @@
+package org.xrpl.xrpl4j.model.client.amm;
+
+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.ledger.Issue;
+import org.xrpl.xrpl4j.model.transactions.Address;
+
+class AmmInfoRequestParamsTest extends AbstractJsonTest {
+
+ @Test
+ void testAssetAsset2Json() throws JSONException, JsonProcessingException {
+ AmmInfoRequestParams params = AmmInfoRequestParams.from(
+ Issue.XRP,
+ Issue.builder()
+ .currency("TST")
+ .issuer(Address.of("rP9jPyP5kyvFRb6ZiRghAGw5u8SGAmU4bd"))
+ .build()
+ );
+ String json = "{\n" +
+ " \"asset\": {\n" +
+ " \"currency\": \"XRP\"\n" +
+ " },\n" +
+ " \"asset2\": {\n" +
+ " \"currency\": \"TST\",\n" +
+ " \"issuer\": \"rP9jPyP5kyvFRb6ZiRghAGw5u8SGAmU4bd\"\n" +
+ " }\n" +
+ " }";
+
+ assertCanSerializeAndDeserialize(params, json);
+ }
+
+ @Test
+ void testAmmAccountJson() throws JSONException, JsonProcessingException {
+ AmmInfoRequestParams params = AmmInfoRequestParams.from(
+ Address.of("rP9jPyP5kyvFRb6ZiRghAGw5u8SGAmU4bd")
+ );
+
+ String json = "{\n" +
+ " \"amm_account\": \"rP9jPyP5kyvFRb6ZiRghAGw5u8SGAmU4bd\"\n" +
+ " }";
+
+ assertCanSerializeAndDeserialize(params, json);
+ }
+}
\ No newline at end of file
diff --git a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/client/amm/AmmInfoResultTest.java b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/client/amm/AmmInfoResultTest.java
new file mode 100644
index 000000000..70818b568
--- /dev/null
+++ b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/client/amm/AmmInfoResultTest.java
@@ -0,0 +1,231 @@
+package org.xrpl.xrpl4j.model.client.amm;
+
+import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
+import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy;
+
+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;
+import org.xrpl.xrpl4j.model.client.common.LedgerIndex;
+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.TradingFee;
+import org.xrpl.xrpl4j.model.transactions.VoteWeight;
+import org.xrpl.xrpl4j.model.transactions.XrpCurrencyAmount;
+
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.Locale;
+
+class AmmInfoResultTest extends AbstractJsonTest {
+
+ @Test
+ void testJsonForCurrentLedger() throws JSONException, JsonProcessingException {
+ AmmInfoResult result = AmmInfoResult.builder()
+ .amm(
+ AmmInfo.builder()
+ .account(Address.of("rU3auoTuhaPwiiod3wEXNnYogxMnYsBhze"))
+ .amount(XrpCurrencyAmount.ofDrops(11080000720L))
+ .amount2(
+ IssuedCurrencyAmount.builder()
+ .currency("USD")
+ .issuer(Address.of("rELH2VCCkjDzvygtB4nKiqGav7h53RhDiP"))
+ .value("11080.00072727936")
+ .build()
+ )
+ .auctionSlot(
+ AmmInfoAuctionSlot.builder()
+ .account(Address.of("rM7xXGzMUALmEhQ2y9FW5XG69WXwQ6xtDC"))
+ .addAuthAccounts(
+ AmmInfoAuthAccount.of(Address.of("rHq1eC9TEyEPVhRvdTPLKr3z8D5BUzcHqi")),
+ AmmInfoAuthAccount.of(Address.of("rNzgpEGUyEmQ1YGDMAiGGBvwtzbk78tcCG"))
+ )
+ .discountedFee(TradingFee.of(UnsignedInteger.ZERO))
+ .expiration(
+ ZonedDateTime.parse(
+ "2023-07-20T15:17:31+0000",
+ DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssZ", Locale.US)
+ ).withZoneSameLocal(ZoneId.of("UTC"))
+ )
+ .price(
+ IssuedCurrencyAmount.builder()
+ .currency("03930D02208264E2E40EC1B0C09E4DB96EE197B1")
+ .issuer(Address.of("rU3auoTuhaPwiiod3wEXNnYogxMnYsBhze"))
+ .value("100")
+ .build()
+ )
+ .timeInterval(UnsignedInteger.ZERO)
+ .build()
+ )
+ .lpToken(
+ IssuedCurrencyAmount.builder()
+ .currency("03930D02208264E2E40EC1B0C09E4DB96EE197B1")
+ .issuer(Address.of("rU3auoTuhaPwiiod3wEXNnYogxMnYsBhze"))
+ .value("11079900")
+ .build()
+ )
+ .tradingFee(TradingFee.of(UnsignedInteger.valueOf(225)))
+ .addVoteSlots(
+ AmmInfoVoteEntry.builder()
+ .voteWeight(VoteWeight.of(UnsignedInteger.valueOf(90)))
+ .tradingFee(TradingFee.of(UnsignedInteger.valueOf(50)))
+ .account(Address.of("rs6HZNabrZzBBjDWCwkWcSGdDH7Xsi4Z99"))
+ .build(),
+ AmmInfoVoteEntry.builder()
+ .voteWeight(VoteWeight.of(UnsignedInteger.valueOf(90)))
+ .tradingFee(TradingFee.of(UnsignedInteger.valueOf(100)))
+ .account(Address.of("rJd7rhLSaqLHEfeqAW2vYzYYkhvyE9XfBE"))
+ .build()
+ )
+ .build()
+ )
+ .ledgerCurrentIndex(LedgerIndex.of(UnsignedInteger.valueOf(102)))
+ .status("success")
+ .build();
+
+ String json = "{\"amm\": {\"account\": \"rU3auoTuhaPwiiod3wEXNnYogxMnYsBhze\",\n" +
+ " \"amount\": \"11080000720\",\n" +
+ " \"amount2\": {\"currency\": \"USD\",\n" +
+ " \"issuer\": \"rELH2VCCkjDzvygtB4nKiqGav7h53RhDiP\",\n" +
+ " \"value\": \"11080.00072727936\"},\n" +
+ " \"asset2_frozen\": false,\n" +
+ " \"asset_frozen\": false,\n" +
+ " \"auction_slot\": {\"account\": \"rM7xXGzMUALmEhQ2y9FW5XG69WXwQ6xtDC\",\n" +
+ " \"auth_accounts\": [{\"account\": \"rHq1eC9TEyEPVhRvdTPLKr3z8D5BUzcHqi\"},\n" +
+ " {\"account\": \"rNzgpEGUyEmQ1YGDMAiGGBvwtzbk78tcCG\"}],\n" +
+ " \"discounted_fee\": 0,\n" +
+ " \"expiration\": \"2023-07-20T15:17:31+0000\",\n" +
+ " \"price\": {\"currency\": \"03930D02208264E2E40EC1B0C09E4DB96EE197B1\",\n" +
+ " \"issuer\": \"rU3auoTuhaPwiiod3wEXNnYogxMnYsBhze\",\n" +
+ " \"value\": \"100\"},\n" +
+ " \"time_interval\": 0},\n" +
+ " \"lp_token\": {\"currency\": \"03930D02208264E2E40EC1B0C09E4DB96EE197B1\",\n" +
+ " \"issuer\": \"rU3auoTuhaPwiiod3wEXNnYogxMnYsBhze\",\n" +
+ " \"value\": \"11079900\"},\n" +
+ " \"trading_fee\": 225,\n" +
+ " \"vote_slots\": [{\"account\": \"rs6HZNabrZzBBjDWCwkWcSGdDH7Xsi4Z99\",\n" +
+ " \"trading_fee\": 50,\n" +
+ " \"vote_weight\": 90},\n" +
+ " {\"account\": \"rJd7rhLSaqLHEfeqAW2vYzYYkhvyE9XfBE\",\n" +
+ " \"trading_fee\": 100,\n" +
+ " \"vote_weight\": 90}]},\n" +
+ " \"ledger_current_index\": 102,\n" +
+ " \"status\": \"success\",\n" +
+ " \"validated\": false}";
+
+ assertCanSerializeAndDeserialize(result, json);
+
+ assertThat(result.ledgerCurrentIndexSafe()).isEqualTo(result.ledgerCurrentIndex().get());
+ assertThatThrownBy(result::ledgerIndexSafe).isInstanceOf(IllegalStateException.class);
+ assertThatThrownBy(result::ledgerHashSafe).isInstanceOf(IllegalStateException.class);
+ }
+
+ @Test
+ void testJsonForValidatedLedger() throws JSONException, JsonProcessingException {
+ AmmInfoResult result = AmmInfoResult.builder()
+ .amm(
+ AmmInfo.builder()
+ .account(Address.of("rU3auoTuhaPwiiod3wEXNnYogxMnYsBhze"))
+ .amount(XrpCurrencyAmount.ofDrops(11080000720L))
+ .amount2(
+ IssuedCurrencyAmount.builder()
+ .currency("USD")
+ .issuer(Address.of("rELH2VCCkjDzvygtB4nKiqGav7h53RhDiP"))
+ .value("11080.00072727936")
+ .build()
+ )
+ .asset2Frozen(false)
+ .auctionSlot(
+ AmmInfoAuctionSlot.builder()
+ .account(Address.of("rM7xXGzMUALmEhQ2y9FW5XG69WXwQ6xtDC"))
+ .addAuthAccounts(
+ AmmInfoAuthAccount.of(Address.of("rHq1eC9TEyEPVhRvdTPLKr3z8D5BUzcHqi")),
+ AmmInfoAuthAccount.of(Address.of("rNzgpEGUyEmQ1YGDMAiGGBvwtzbk78tcCG"))
+ )
+ .discountedFee(TradingFee.of(UnsignedInteger.ZERO))
+ .expiration(
+ ZonedDateTime.parse(
+ "2023-07-20T15:17:31+0000",
+ DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssZ", Locale.US)
+ ).withZoneSameLocal(ZoneId.of("UTC"))
+ )
+ .price(
+ IssuedCurrencyAmount.builder()
+ .currency("03930D02208264E2E40EC1B0C09E4DB96EE197B1")
+ .issuer(Address.of("rU3auoTuhaPwiiod3wEXNnYogxMnYsBhze"))
+ .value("100")
+ .build()
+ )
+ .timeInterval(UnsignedInteger.ZERO)
+ .build()
+ )
+ .lpToken(
+ IssuedCurrencyAmount.builder()
+ .currency("03930D02208264E2E40EC1B0C09E4DB96EE197B1")
+ .issuer(Address.of("rU3auoTuhaPwiiod3wEXNnYogxMnYsBhze"))
+ .value("11079900")
+ .build()
+ )
+ .tradingFee(TradingFee.of(UnsignedInteger.valueOf(225)))
+ .addVoteSlots(
+ AmmInfoVoteEntry.builder()
+ .voteWeight(VoteWeight.of(UnsignedInteger.valueOf(90)))
+ .tradingFee(TradingFee.of(UnsignedInteger.valueOf(50)))
+ .account(Address.of("rs6HZNabrZzBBjDWCwkWcSGdDH7Xsi4Z99"))
+ .build(),
+ AmmInfoVoteEntry.builder()
+ .voteWeight(VoteWeight.of(UnsignedInteger.valueOf(90)))
+ .tradingFee(TradingFee.of(UnsignedInteger.valueOf(100)))
+ .account(Address.of("rJd7rhLSaqLHEfeqAW2vYzYYkhvyE9XfBE"))
+ .build()
+ )
+ .build()
+ )
+ .ledgerHash(Hash256.of("93586177048F82080AB79B8D0FA76F9D93AF458551A7358D9F0EC6D790AF5CBA"))
+ .ledgerIndex(LedgerIndex.of(UnsignedInteger.valueOf(102)))
+ .status("success")
+ .validated(true)
+ .build();
+
+ String json = "{\"amm\": {\"account\": \"rU3auoTuhaPwiiod3wEXNnYogxMnYsBhze\",\n" +
+ " \"amount\": \"11080000720\",\n" +
+ " \"amount2\": {\"currency\": \"USD\",\n" +
+ " \"issuer\": \"rELH2VCCkjDzvygtB4nKiqGav7h53RhDiP\",\n" +
+ " \"value\": \"11080.00072727936\"},\n" +
+ " \"asset2_frozen\": false,\n" +
+ " \"asset_frozen\": false,\n" +
+ " \"auction_slot\": {\"account\": \"rM7xXGzMUALmEhQ2y9FW5XG69WXwQ6xtDC\",\n" +
+ " \"auth_accounts\": [{\"account\": \"rHq1eC9TEyEPVhRvdTPLKr3z8D5BUzcHqi\"},\n" +
+ " {\"account\": \"rNzgpEGUyEmQ1YGDMAiGGBvwtzbk78tcCG\"}],\n" +
+ " \"discounted_fee\": 0,\n" +
+ " \"expiration\": \"2023-07-20T15:17:31+0000\",\n" +
+ " \"price\": {\"currency\": \"03930D02208264E2E40EC1B0C09E4DB96EE197B1\",\n" +
+ " \"issuer\": \"rU3auoTuhaPwiiod3wEXNnYogxMnYsBhze\",\n" +
+ " \"value\": \"100\"},\n" +
+ " \"time_interval\": 0},\n" +
+ " \"lp_token\": {\"currency\": \"03930D02208264E2E40EC1B0C09E4DB96EE197B1\",\n" +
+ " \"issuer\": \"rU3auoTuhaPwiiod3wEXNnYogxMnYsBhze\",\n" +
+ " \"value\": \"11079900\"},\n" +
+ " \"trading_fee\": 225,\n" +
+ " \"vote_slots\": [{\"account\": \"rs6HZNabrZzBBjDWCwkWcSGdDH7Xsi4Z99\",\n" +
+ " \"trading_fee\": 50,\n" +
+ " \"vote_weight\": 90},\n" +
+ " {\"account\": \"rJd7rhLSaqLHEfeqAW2vYzYYkhvyE9XfBE\",\n" +
+ " \"trading_fee\": 100,\n" +
+ " \"vote_weight\": 90}]},\n" +
+ " \"ledger_hash\": \"93586177048F82080AB79B8D0FA76F9D93AF458551A7358D9F0EC6D790AF5CBA\",\n" +
+ " \"ledger_index\": 102,\n" +
+ " \"status\": \"success\",\n" +
+ " \"validated\": true}";
+
+ assertCanSerializeAndDeserialize(result, json);
+
+ assertThat(result.ledgerIndexSafe()).isEqualTo(result.ledgerIndex().get());
+ assertThat(result.ledgerHashSafe()).isEqualTo(result.ledgerHash().get());
+ assertThatThrownBy(result::ledgerCurrentIndexSafe).isInstanceOf(IllegalStateException.class);
+ }
+}
\ No newline at end of file
diff --git a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/client/ledger/LedgerEntryRequestParamsTest.java b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/client/ledger/LedgerEntryRequestParamsTest.java
new file mode 100644
index 000000000..1955540d3
--- /dev/null
+++ b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/client/ledger/LedgerEntryRequestParamsTest.java
@@ -0,0 +1,432 @@
+package org.xrpl.xrpl4j.model.client.ledger;
+
+import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
+import static org.xrpl.xrpl4j.crypto.TestConstants.ED_ADDRESS;
+import static org.xrpl.xrpl4j.crypto.TestConstants.HASH_256;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.google.common.primitives.UnsignedInteger;
+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.AbstractJsonTest;
+import org.xrpl.xrpl4j.model.client.XrplRequestParams;
+import org.xrpl.xrpl4j.model.client.common.LedgerSpecifier;
+import org.xrpl.xrpl4j.model.client.ledger.RippleStateLedgerEntryParams.RippleStateAccounts;
+import org.xrpl.xrpl4j.model.ledger.AccountRootObject;
+import org.xrpl.xrpl4j.model.ledger.AmmObject;
+import org.xrpl.xrpl4j.model.ledger.CheckObject;
+import org.xrpl.xrpl4j.model.ledger.DepositPreAuthObject;
+import org.xrpl.xrpl4j.model.ledger.EscrowObject;
+import org.xrpl.xrpl4j.model.ledger.Issue;
+import org.xrpl.xrpl4j.model.ledger.LedgerObject;
+import org.xrpl.xrpl4j.model.ledger.NfTokenPageObject;
+import org.xrpl.xrpl4j.model.ledger.OfferObject;
+import org.xrpl.xrpl4j.model.ledger.PayChannelObject;
+import org.xrpl.xrpl4j.model.ledger.RippleStateObject;
+import org.xrpl.xrpl4j.model.ledger.TicketObject;
+import org.xrpl.xrpl4j.model.transactions.Address;
+
+class LedgerEntryRequestParamsTest extends AbstractJsonTest {
+
+ @Test
+ void testTypedIndexParams() throws JSONException, JsonProcessingException {
+ LedgerEntryRequestParams params = LedgerEntryRequestParams.index(HASH_256, AmmObject.class,
+ LedgerSpecifier.VALIDATED);
+ assertThat(params.index()).isNotEmpty().get().isEqualTo(HASH_256);
+ assertThat(params.ledgerObjectClass()).isEqualTo(AmmObject.class);
+
+ assertThat(params.accountRoot()).isEmpty();
+ assertThat(params.amm()).isEmpty();
+ assertThat(params.offer()).isEmpty();
+ assertThat(params.rippleState()).isEmpty();
+ assertThat(params.check()).isEmpty();
+ assertThat(params.escrow()).isEmpty();
+ assertThat(params.paymentChannel()).isEmpty();
+ assertThat(params.depositPreAuth()).isEmpty();
+ assertThat(params.ticket()).isEmpty();
+ assertThat(params.nftPage()).isEmpty();
+
+ String json = String.format("{\n" +
+ " \"index\": \"%s\",\n" +
+ " \"binary\": false,\n" +
+ " \"ledger_index\": \"validated\"\n" +
+ " }", HASH_256);
+
+ String serialized = objectMapper.writeValueAsString(params);
+ JSONAssert.assertEquals(json, serialized, JSONCompareMode.STRICT);
+
+ // Note that when deserializing from JSON, we cannot figure out what ledgerObjectClass should be based on the JSON.
+ // This is likely fine because request params should never really be getting deserialized by this library.
+ XrplRequestParams deserialized = objectMapper.readValue(serialized, params.getClass());
+ assertThat(deserialized).usingRecursiveComparison().ignoringFields("ledgerObjectClass")
+ .isEqualTo(params);
+ }
+
+ @Test
+ void testUntypedIndexParams() throws JSONException, JsonProcessingException {
+ LedgerEntryRequestParams params = LedgerEntryRequestParams.index(HASH_256, LedgerSpecifier.VALIDATED);
+ assertThat(params.index()).isNotEmpty().get().isEqualTo(HASH_256);
+ assertThat(params.ledgerObjectClass()).isEqualTo(LedgerObject.class);
+
+ assertThat(params.accountRoot()).isEmpty();
+ assertThat(params.amm()).isEmpty();
+ assertThat(params.offer()).isEmpty();
+ assertThat(params.rippleState()).isEmpty();
+ assertThat(params.check()).isEmpty();
+ assertThat(params.escrow()).isEmpty();
+ assertThat(params.paymentChannel()).isEmpty();
+ assertThat(params.depositPreAuth()).isEmpty();
+ assertThat(params.ticket()).isEmpty();
+ assertThat(params.nftPage()).isEmpty();
+
+ String json = String.format("{\n" +
+ " \"index\": \"%s\",\n" +
+ " \"binary\": false,\n" +
+ " \"ledger_index\": \"validated\"\n" +
+ " }", HASH_256);
+
+ assertCanSerializeAndDeserialize(params, json);
+ }
+
+ @Test
+ void testAccountRootParams() throws JSONException, JsonProcessingException {
+ LedgerEntryRequestParams params = LedgerEntryRequestParams.accountRoot(
+ ED_ADDRESS, LedgerSpecifier.VALIDATED
+ );
+ assertThat(params.accountRoot()).isNotEmpty().get().isEqualTo(ED_ADDRESS);
+ assertThat(params.ledgerObjectClass()).isEqualTo(AccountRootObject.class);
+
+ assertThat(params.index()).isEmpty();
+ assertThat(params.amm()).isEmpty();
+ assertThat(params.offer()).isEmpty();
+ assertThat(params.rippleState()).isEmpty();
+ assertThat(params.check()).isEmpty();
+ assertThat(params.escrow()).isEmpty();
+ assertThat(params.paymentChannel()).isEmpty();
+ assertThat(params.depositPreAuth()).isEmpty();
+ assertThat(params.ticket()).isEmpty();
+ assertThat(params.nftPage()).isEmpty();
+
+ String json = String.format("{\n" +
+ " \"account_root\": \"%s\",\n" +
+ " \"binary\": false,\n" +
+ " \"ledger_index\": \"validated\"\n" +
+ " }", ED_ADDRESS);
+
+ assertCanSerializeAndDeserialize(params, json);
+ }
+
+ @Test
+ void testAmmParams() throws JSONException, JsonProcessingException {
+ AmmLedgerEntryParams ammParams = AmmLedgerEntryParams.builder()
+ .asset(Issue.XRP)
+ .asset2(
+ Issue.builder()
+ .currency("TST")
+ .issuer(Address.of("rP9jPyP5kyvFRb6ZiRghAGw5u8SGAmU4bd"))
+ .build()
+ )
+ .build();
+
+ LedgerEntryRequestParams params = LedgerEntryRequestParams.amm(ammParams, LedgerSpecifier.VALIDATED);
+ assertThat(params.amm()).isNotEmpty().get().isEqualTo(ammParams);
+ assertThat(params.ledgerObjectClass()).isEqualTo(AmmObject.class);
+
+ assertThat(params.index()).isEmpty();
+ assertThat(params.accountRoot()).isEmpty();
+ assertThat(params.offer()).isEmpty();
+ assertThat(params.rippleState()).isEmpty();
+ assertThat(params.check()).isEmpty();
+ assertThat(params.escrow()).isEmpty();
+ assertThat(params.paymentChannel()).isEmpty();
+ assertThat(params.depositPreAuth()).isEmpty();
+ assertThat(params.ticket()).isEmpty();
+ assertThat(params.nftPage()).isEmpty();
+
+ String json = "{\n" +
+ " \"amm\": {\n" +
+ " \"asset\": {\n" +
+ " \"currency\": \"XRP\"\n" +
+ " },\n" +
+ " \"asset2\": {\n" +
+ " \"currency\" : \"TST\",\n" +
+ " \"issuer\" : \"rP9jPyP5kyvFRb6ZiRghAGw5u8SGAmU4bd\"\n" +
+ " }\n" +
+ " },\n" +
+ " \"binary\": false,\n" +
+ " \"ledger_index\": \"validated\"\n" +
+ " }";
+
+ assertCanSerializeAndDeserialize(params, json);
+ }
+
+ @Test
+ void testOfferParams() throws JSONException, JsonProcessingException {
+ OfferLedgerEntryParams offerParams = OfferLedgerEntryParams.builder()
+ .account(Address.of("rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn"))
+ .seq(UnsignedInteger.valueOf(359))
+ .build();
+
+ LedgerEntryRequestParams params = LedgerEntryRequestParams.offer(
+ offerParams, LedgerSpecifier.VALIDATED
+ );
+ assertThat(params.offer()).isNotEmpty().get().isEqualTo(offerParams);
+ assertThat(params.ledgerObjectClass()).isEqualTo(OfferObject.class);
+
+ assertThat(params.index()).isEmpty();
+ assertThat(params.accountRoot()).isEmpty();
+ assertThat(params.amm()).isEmpty();
+ assertThat(params.rippleState()).isEmpty();
+ assertThat(params.check()).isEmpty();
+ assertThat(params.escrow()).isEmpty();
+ assertThat(params.paymentChannel()).isEmpty();
+ assertThat(params.depositPreAuth()).isEmpty();
+ assertThat(params.ticket()).isEmpty();
+ assertThat(params.nftPage()).isEmpty();
+
+ String json = "{\n" +
+ " \"offer\": {\n" +
+ " \"account\": \"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn\",\n" +
+ " \"seq\": 359\n" +
+ " },\n" +
+ " \"binary\": false,\n" +
+ " \"ledger_index\": \"validated\"\n" +
+ " }";
+
+ assertCanSerializeAndDeserialize(params, json);
+ }
+
+ @Test
+ void testRippleStateParams() throws JSONException, JsonProcessingException {
+ RippleStateLedgerEntryParams rippleStateParams = RippleStateLedgerEntryParams.builder()
+ .accounts(RippleStateAccounts.of(
+ Address.of("rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn"),
+ Address.of("rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW")
+ ))
+ .currency("USD")
+ .build();
+
+ LedgerEntryRequestParams params = LedgerEntryRequestParams.rippleState(
+ rippleStateParams, LedgerSpecifier.VALIDATED
+ );
+ assertThat(params.rippleState()).isNotEmpty().get().isEqualTo(rippleStateParams);
+ assertThat(params.ledgerObjectClass()).isEqualTo(RippleStateObject.class);
+
+ assertThat(params.index()).isEmpty();
+ assertThat(params.accountRoot()).isEmpty();
+ assertThat(params.amm()).isEmpty();
+ assertThat(params.offer()).isEmpty();
+ assertThat(params.check()).isEmpty();
+ assertThat(params.escrow()).isEmpty();
+ assertThat(params.paymentChannel()).isEmpty();
+ assertThat(params.depositPreAuth()).isEmpty();
+ assertThat(params.ticket()).isEmpty();
+ assertThat(params.nftPage()).isEmpty();
+
+ String json = "{\n" +
+ " \"ripple_state\": {\n" +
+ " \"accounts\": [\n" +
+ " \"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn\",\n" +
+ " \"rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW\"\n" +
+ " ],\n" +
+ " \"currency\": \"USD\"\n" +
+ " },\n" +
+ " \"binary\": false,\n" +
+ " \"ledger_index\": \"validated\"\n" +
+ " }";
+
+ assertCanSerializeAndDeserialize(params, json);
+ }
+
+ @Test
+ void testCheckParams() throws JSONException, JsonProcessingException {
+ LedgerEntryRequestParams params = LedgerEntryRequestParams.check(HASH_256, LedgerSpecifier.VALIDATED);
+ assertThat(params.check()).isNotEmpty().get().isEqualTo(HASH_256);
+ assertThat(params.ledgerObjectClass()).isEqualTo(CheckObject.class);
+
+ assertThat(params.index()).isEmpty();
+ assertThat(params.accountRoot()).isEmpty();
+ assertThat(params.amm()).isEmpty();
+ assertThat(params.offer()).isEmpty();
+ assertThat(params.rippleState()).isEmpty();
+ assertThat(params.escrow()).isEmpty();
+ assertThat(params.paymentChannel()).isEmpty();
+ assertThat(params.depositPreAuth()).isEmpty();
+ assertThat(params.ticket()).isEmpty();
+ assertThat(params.nftPage()).isEmpty();
+
+ String json = String.format("{\n" +
+ " \"check\": \"%s\",\n" +
+ " \"binary\": false,\n" +
+ " \"ledger_index\": \"validated\"\n" +
+ " }", HASH_256);
+
+ assertCanSerializeAndDeserialize(params, json);
+ }
+
+ @Test
+ void testEscrowParams() throws JSONException, JsonProcessingException {
+ EscrowLedgerEntryParams escrowParams = EscrowLedgerEntryParams.builder()
+ .owner(Address.of("rL4fPHi2FWGwRGRQSH7gBcxkuo2b9NTjKK"))
+ .seq(UnsignedInteger.valueOf(126))
+ .build();
+ LedgerEntryRequestParams params = LedgerEntryRequestParams.escrow(
+ escrowParams, LedgerSpecifier.VALIDATED
+ );
+ assertThat(params.escrow()).isNotEmpty().get().isEqualTo(escrowParams);
+ assertThat(params.ledgerObjectClass()).isEqualTo(EscrowObject.class);
+
+ assertThat(params.index()).isEmpty();
+ assertThat(params.accountRoot()).isEmpty();
+ assertThat(params.amm()).isEmpty();
+ assertThat(params.offer()).isEmpty();
+ assertThat(params.rippleState()).isEmpty();
+ assertThat(params.check()).isEmpty();
+ assertThat(params.paymentChannel()).isEmpty();
+ assertThat(params.depositPreAuth()).isEmpty();
+ assertThat(params.ticket()).isEmpty();
+ assertThat(params.nftPage()).isEmpty();
+
+ String json = "{\n" +
+ " \"escrow\": {\n" +
+ " \"owner\": \"rL4fPHi2FWGwRGRQSH7gBcxkuo2b9NTjKK\",\n" +
+ " \"seq\": 126\n" +
+ " },\n" +
+ " \"binary\": false,\n" +
+ " \"ledger_index\": \"validated\"\n" +
+ " }";
+
+ assertCanSerializeAndDeserialize(params, json);
+ }
+
+ @Test
+ void testPaymentChannelParams() throws JSONException, JsonProcessingException {
+ LedgerEntryRequestParams params = LedgerEntryRequestParams.paymentChannel(
+ HASH_256, LedgerSpecifier.VALIDATED
+ );
+ assertThat(params.paymentChannel()).isNotEmpty().get().isEqualTo(HASH_256);
+ assertThat(params.ledgerObjectClass()).isEqualTo(PayChannelObject.class);
+
+ assertThat(params.index()).isEmpty();
+ assertThat(params.accountRoot()).isEmpty();
+ assertThat(params.amm()).isEmpty();
+ assertThat(params.offer()).isEmpty();
+ assertThat(params.rippleState()).isEmpty();
+ assertThat(params.check()).isEmpty();
+ assertThat(params.escrow()).isEmpty();
+ assertThat(params.depositPreAuth()).isEmpty();
+ assertThat(params.ticket()).isEmpty();
+ assertThat(params.nftPage()).isEmpty();
+
+ String json = String.format("{\n" +
+ " \"payment_channel\": \"%s\",\n" +
+ " \"binary\": false,\n" +
+ " \"ledger_index\": \"validated\"\n" +
+ " }", HASH_256);
+
+ assertCanSerializeAndDeserialize(params, json);
+ }
+
+ @Test
+ void testDepositPreAuthParams() throws JSONException, JsonProcessingException {
+ DepositPreAuthLedgerEntryParams depositPreAuthParams = DepositPreAuthLedgerEntryParams.builder()
+ .owner(Address.of("rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn"))
+ .authorized(Address.of("ra5nK24KXen9AHvsdFTKHSANinZseWnPcX"))
+ .build();
+ LedgerEntryRequestParams params = LedgerEntryRequestParams.depositPreAuth(
+ depositPreAuthParams,
+ LedgerSpecifier.VALIDATED
+ );
+ assertThat(params.depositPreAuth()).isNotEmpty().get().isEqualTo(depositPreAuthParams);
+ assertThat(params.ledgerObjectClass()).isEqualTo(DepositPreAuthObject.class);
+
+ assertThat(params.index()).isEmpty();
+ assertThat(params.accountRoot()).isEmpty();
+ assertThat(params.amm()).isEmpty();
+ assertThat(params.offer()).isEmpty();
+ assertThat(params.rippleState()).isEmpty();
+ assertThat(params.check()).isEmpty();
+ assertThat(params.escrow()).isEmpty();
+ assertThat(params.paymentChannel()).isEmpty();
+ assertThat(params.ticket()).isEmpty();
+ assertThat(params.nftPage()).isEmpty();
+
+ String json = "{\n" +
+ " \"deposit_preauth\": {\n" +
+ " \"owner\": \"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn\",\n" +
+ " \"authorized\": \"ra5nK24KXen9AHvsdFTKHSANinZseWnPcX\"\n" +
+ " },\n" +
+ " \"binary\": false,\n" +
+ " \"ledger_index\": \"validated\"\n" +
+ " }";
+
+ assertCanSerializeAndDeserialize(params, json);
+ }
+
+ @Test
+ void testTicketParams() throws JSONException, JsonProcessingException {
+ TicketLedgerEntryParams ticketParams = TicketLedgerEntryParams.builder()
+ .account(Address.of("rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn"))
+ .ticketSeq(UnsignedInteger.valueOf(389))
+ .build();
+ LedgerEntryRequestParams params = LedgerEntryRequestParams.ticket(
+ ticketParams,
+ LedgerSpecifier.VALIDATED
+ );
+ assertThat(params.ticket()).isNotEmpty().get().isEqualTo(ticketParams);
+ assertThat(params.ledgerObjectClass()).isEqualTo(TicketObject.class);
+
+ assertThat(params.index()).isEmpty();
+ assertThat(params.accountRoot()).isEmpty();
+ assertThat(params.amm()).isEmpty();
+ assertThat(params.offer()).isEmpty();
+ assertThat(params.rippleState()).isEmpty();
+ assertThat(params.check()).isEmpty();
+ assertThat(params.escrow()).isEmpty();
+ assertThat(params.paymentChannel()).isEmpty();
+ assertThat(params.depositPreAuth()).isEmpty();
+ assertThat(params.nftPage()).isEmpty();
+
+ String json = "{\n" +
+ " \"ticket\": {\n" +
+ " \"account\": \"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn\",\n" +
+ " \"ticket_seq\": 389\n" +
+ " },\n" +
+ " \"binary\": false,\n" +
+ " \"ledger_index\": \"validated\"\n" +
+ " }";
+
+ assertCanSerializeAndDeserialize(params, json);
+ }
+
+ @Test
+ void testNftPageParams() throws JSONException, JsonProcessingException {
+ LedgerEntryRequestParams params = LedgerEntryRequestParams.nftPage(
+ HASH_256,
+ LedgerSpecifier.VALIDATED
+ );
+ assertThat(params.nftPage()).isNotEmpty().get().isEqualTo(HASH_256);
+ assertThat(params.ledgerObjectClass()).isEqualTo(NfTokenPageObject.class);
+
+ assertThat(params.index()).isEmpty();
+ assertThat(params.accountRoot()).isEmpty();
+ assertThat(params.amm()).isEmpty();
+ assertThat(params.offer()).isEmpty();
+ assertThat(params.rippleState()).isEmpty();
+ assertThat(params.check()).isEmpty();
+ assertThat(params.escrow()).isEmpty();
+ assertThat(params.paymentChannel()).isEmpty();
+ assertThat(params.depositPreAuth()).isEmpty();
+ assertThat(params.ticket()).isEmpty();
+
+ String json = String.format("{\n" +
+ " \"nft_page\": \"%s\",\n" +
+ " \"binary\": false,\n" +
+ " \"ledger_index\": \"validated\"\n" +
+ " }", HASH_256);
+
+ assertCanSerializeAndDeserialize(params, json);
+ }
+}
\ No newline at end of file
diff --git a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/client/ledger/LedgerEntryResultTest.java b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/client/ledger/LedgerEntryResultTest.java
new file mode 100644
index 000000000..8bcfb0a8d
--- /dev/null
+++ b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/client/ledger/LedgerEntryResultTest.java
@@ -0,0 +1,676 @@
+package org.xrpl.xrpl4j.model.client.ledger;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.mockito.Mockito.mock;
+import static org.xrpl.xrpl4j.crypto.TestConstants.HASH_256;
+
+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.AccountRootFlags;
+import org.xrpl.xrpl4j.model.flags.OfferFlags;
+import org.xrpl.xrpl4j.model.flags.RippleStateFlags;
+import org.xrpl.xrpl4j.model.ledger.AccountRootObject;
+import org.xrpl.xrpl4j.model.ledger.AmmObject;
+import org.xrpl.xrpl4j.model.ledger.AuctionSlot;
+import org.xrpl.xrpl4j.model.ledger.CheckObject;
+import org.xrpl.xrpl4j.model.ledger.DepositPreAuthObject;
+import org.xrpl.xrpl4j.model.ledger.EscrowObject;
+import org.xrpl.xrpl4j.model.ledger.Issue;
+import org.xrpl.xrpl4j.model.ledger.LedgerObject;
+import org.xrpl.xrpl4j.model.ledger.NfToken;
+import org.xrpl.xrpl4j.model.ledger.NfTokenPageObject;
+import org.xrpl.xrpl4j.model.ledger.NfTokenWrapper;
+import org.xrpl.xrpl4j.model.ledger.OfferObject;
+import org.xrpl.xrpl4j.model.ledger.PayChannelObject;
+import org.xrpl.xrpl4j.model.ledger.RippleStateObject;
+import org.xrpl.xrpl4j.model.ledger.TicketObject;
+import org.xrpl.xrpl4j.model.ledger.VoteEntry;
+import org.xrpl.xrpl4j.model.ledger.VoteEntryWrapper;
+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.NfTokenId;
+import org.xrpl.xrpl4j.model.transactions.NfTokenUri;
+import org.xrpl.xrpl4j.model.transactions.TradingFee;
+import org.xrpl.xrpl4j.model.transactions.VoteWeight;
+import org.xrpl.xrpl4j.model.transactions.XrpCurrencyAmount;
+
+class LedgerEntryResultTest extends AbstractJsonTest {
+
+ @Test
+ void testAccountRootResult() throws JSONException, JsonProcessingException {
+ LedgerEntryResult result = LedgerEntryResult.builder()
+ .ledgerIndex(LedgerIndex.of(UnsignedInteger.valueOf(83125250)))
+ .ledgerHash(Hash256.of("783625588CF01BD3D0E9C2719B92098A6A87649AEFF5AE970CD68B911436C1D7"))
+ .validated(true)
+ .index(Hash256.of("13F1A95D7AAB7108D5CE7EEAF504B2894B8C674E6D68499076441C4837282BF8"))
+ .node(
+ AccountRootObject.builder()
+ .account(Address.of("rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn"))
+ .accountTransactionId(Hash256.of("932CC7E9BAC1F7B9FA5381679F293EEC0A646E5E7F2F6D14C85FEE2102F0E66C"))
+ .balance(XrpCurrencyAmount.ofDrops(1066107694))
+ .domain("6D64756F31332E636F6D")
+ .emailHash("98B4375E1D753E5B91627516F6D70977")
+ .flags(AccountRootFlags.of(9568256))
+ .messageKey("0000000000000000000000070000000300")
+ .ownerCount(UnsignedInteger.valueOf(17))
+ .previousTransactionId(Hash256.of("7E5F3FB60E1177F8AF8A9EAC7982F27FA5494FDEA871B23B4B149939A5A7A7BB"))
+ .previousTransactionLedgerSequence(UnsignedInteger.valueOf(82357607))
+ .regularKey(Address.of("rD9iJmieYHn8jTtPjwwkW2Wm9sVDvPXLoJ"))
+ .sequence(UnsignedInteger.valueOf(393))
+ .ticketCount(UnsignedInteger.valueOf(5))
+ .transferRate(UnsignedInteger.valueOf(4294967295L))
+ .index(Hash256.of("13F1A95D7AAB7108D5CE7EEAF504B2894B8C674E6D68499076441C4837282BF8"))
+ .build()
+ )
+ .status("success")
+ .build();
+
+ String json = "{\n" +
+ " \"ledger_hash\": \"783625588CF01BD3D0E9C2719B92098A6A87649AEFF5AE970CD68B911436C1D7\",\n" +
+ " \"ledger_index\": 83125250,\n" +
+ " \"validated\": true,\n" +
+ " \"index\": \"13F1A95D7AAB7108D5CE7EEAF504B2894B8C674E6D68499076441C4837282BF8\",\n" +
+ " \"node\": {\n" +
+ " \"Account\": \"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn\",\n" +
+ " \"AccountTxnID\": \"932CC7E9BAC1F7B9FA5381679F293EEC0A646E5E7F2F6D14C85FEE2102F0E66C\",\n" +
+ " \"Balance\": \"1066107694\",\n" +
+ " \"Domain\": \"6D64756F31332E636F6D\",\n" +
+ " \"EmailHash\": \"98B4375E1D753E5B91627516F6D70977\",\n" +
+ " \"Flags\": 9568256,\n" +
+ " \"LedgerEntryType\": \"AccountRoot\",\n" +
+ " \"MessageKey\": \"0000000000000000000000070000000300\",\n" +
+ " \"OwnerCount\": 17,\n" +
+ " \"PreviousTxnID\": \"7E5F3FB60E1177F8AF8A9EAC7982F27FA5494FDEA871B23B4B149939A5A7A7BB\",\n" +
+ " \"PreviousTxnLgrSeq\": 82357607,\n" +
+ " \"RegularKey\": \"rD9iJmieYHn8jTtPjwwkW2Wm9sVDvPXLoJ\",\n" +
+ " \"Sequence\": 393,\n" +
+ " \"TicketCount\": 5,\n" +
+ " \"TransferRate\": 4294967295,\n" +
+ " \"index\": \"13F1A95D7AAB7108D5CE7EEAF504B2894B8C674E6D68499076441C4837282BF8\"\n" +
+ " },\n" +
+ " \"status\": \"success\"\n" +
+ " }";
+
+ assertCanSerializeAndDeserialize(result, json);
+ }
+
+ @Test
+ void testAmmResult() throws JSONException, JsonProcessingException {
+ LedgerEntryResult result = LedgerEntryResult.builder()
+ .ledgerIndex(LedgerIndex.of(UnsignedInteger.valueOf(607272)))
+ .ledgerHash(Hash256.of("EEB650A0FD3CF0A5CE68B3DBD67C902FEC85E6AFAE1D0A7A7AF4BAD2F38557C7"))
+ .validated(true)
+ .index(Hash256.of("6BCD7E451DDA015FB307DAD9208A98A2DC3AC4D1448E624B42C89246DCF08692"))
+ .node(
+ AmmObject.builder()
+ .account(Address.of("rNqXnvSYbjZeJQ6jWcf6T5mnNMRPzHXaZW"))
+ .asset(Issue.XRP)
+ .asset2(
+ Issue.builder()
+ .currency("7872706C346A436F696E00000000000000000000")
+ .issuer(Address.of("rDeo7rDoYw6AUKGneWwfkHPsMJagxcGWy1"))
+ .build()
+ )
+ .auctionSlot(
+ AuctionSlot.builder()
+ .account(Address.of("rDeo7rDoYw6AUKGneWwfkHPsMJagxcGWy1"))
+ .discountedFee(TradingFee.of(UnsignedInteger.valueOf(77)))
+ .expiration(UnsignedInteger.valueOf(750359162))
+ .price(
+ IssuedCurrencyAmount.builder()
+ .currency("03DCF8F3910BFE6AB56136A90BD41E0902E23C4F")
+ .value("0")
+ .issuer(Address.of("rNqXnvSYbjZeJQ6jWcf6T5mnNMRPzHXaZW"))
+ .build()
+ )
+ .build()
+ )
+ .lpTokenBalance(
+ IssuedCurrencyAmount.builder()
+ .currency("03DCF8F3910BFE6AB56136A90BD41E0902E23C4F")
+ .issuer(Address.of("rNqXnvSYbjZeJQ6jWcf6T5mnNMRPzHXaZW"))
+ .value("70606.68056410846")
+ .build()
+ )
+ .ownerNode("0")
+ .tradingFee(TradingFee.of(UnsignedInteger.valueOf(778)))
+ .addVoteSlots(
+ VoteEntryWrapper.of(VoteEntry.builder()
+ .account(Address.of("rDeo7rDoYw6AUKGneWwfkHPsMJagxcGWy1"))
+ .tradingFee(TradingFee.of(UnsignedInteger.valueOf(1000)))
+ .voteWeight(VoteWeight.of(UnsignedInteger.valueOf(70815)))
+ .build()),
+ VoteEntryWrapper.of(VoteEntry.builder()
+ .account(Address.of("rHPoJo9R3QdQjK6XdWL5hY2eTc4wUeNYzW"))
+ .tradingFee(TradingFee.of(UnsignedInteger.valueOf(240)))
+ .voteWeight(VoteWeight.of(UnsignedInteger.valueOf(29185)))
+ .build())
+ )
+ .index(Hash256.of("6BCD7E451DDA015FB307DAD9208A98A2DC3AC4D1448E624B42C89246DCF08692"))
+ .build()
+ )
+ .status("success")
+ .build();
+
+ String json = "{\n" +
+ " \"index\": \"6BCD7E451DDA015FB307DAD9208A98A2DC3AC4D1448E624B42C89246DCF08692\",\n" +
+ " \"ledger_hash\": \"EEB650A0FD3CF0A5CE68B3DBD67C902FEC85E6AFAE1D0A7A7AF4BAD2F38557C7\",\n" +
+ " \"ledger_index\": 607272,\n" +
+ " \"node\": {\n" +
+ " \"Account\": \"rNqXnvSYbjZeJQ6jWcf6T5mnNMRPzHXaZW\",\n" +
+ " \"Asset\": {\n" +
+ " \"currency\": \"XRP\"\n" +
+ " },\n" +
+ " \"Asset2\": {\n" +
+ " \"currency\": \"7872706C346A436F696E00000000000000000000\",\n" +
+ " \"issuer\": \"rDeo7rDoYw6AUKGneWwfkHPsMJagxcGWy1\"\n" +
+ " },\n" +
+ " \"AuctionSlot\": {\n" +
+ " \"Account\": \"rDeo7rDoYw6AUKGneWwfkHPsMJagxcGWy1\",\n" +
+ " \"DiscountedFee\": 77,\n" +
+ " \"Expiration\": 750359162,\n" +
+ " \"Price\": {\n" +
+ " \"currency\": \"03DCF8F3910BFE6AB56136A90BD41E0902E23C4F\",\n" +
+ " \"issuer\": \"rNqXnvSYbjZeJQ6jWcf6T5mnNMRPzHXaZW\",\n" +
+ " \"value\": \"0\"\n" +
+ " }\n" +
+ " },\n" +
+ " \"Flags\": 0,\n" +
+ " \"LPTokenBalance\": {\n" +
+ " \"currency\": \"03DCF8F3910BFE6AB56136A90BD41E0902E23C4F\",\n" +
+ " \"issuer\": \"rNqXnvSYbjZeJQ6jWcf6T5mnNMRPzHXaZW\",\n" +
+ " \"value\": \"70606.68056410846\"\n" +
+ " },\n" +
+ " \"LedgerEntryType\": \"AMM\",\n" +
+ " \"OwnerNode\": \"0\",\n" +
+ " \"TradingFee\": 778,\n" +
+ " \"VoteSlots\": [\n" +
+ " {\n" +
+ " \"VoteEntry\": {\n" +
+ " \"Account\": \"rDeo7rDoYw6AUKGneWwfkHPsMJagxcGWy1\",\n" +
+ " \"TradingFee\": 1000,\n" +
+ " \"VoteWeight\": 70815\n" +
+ " }\n" +
+ " },\n" +
+ " {\n" +
+ " \"VoteEntry\": {\n" +
+ " \"Account\": \"rHPoJo9R3QdQjK6XdWL5hY2eTc4wUeNYzW\",\n" +
+ " \"TradingFee\": 240,\n" +
+ " \"VoteWeight\": 29185\n" +
+ " }\n" +
+ " }\n" +
+ " ],\n" +
+ " \"index\": \"6BCD7E451DDA015FB307DAD9208A98A2DC3AC4D1448E624B42C89246DCF08692\"\n" +
+ " },\n" +
+ " \"status\": \"success\",\n" +
+ " \"validated\": true\n" +
+ " }";
+
+ assertCanSerializeAndDeserialize(result, json);
+ }
+
+ @Test
+ void testOfferResult() throws JSONException, JsonProcessingException {
+ LedgerEntryResult result = LedgerEntryResult.builder()
+ .ledgerIndex(LedgerIndex.of(UnsignedInteger.valueOf(41931093)))
+ .ledgerHash(Hash256.of("54FE89D2FF925D623D386A03B402FDB22B2D2D058A62AEE441CA52CC9AA92BB1"))
+ .validated(true)
+ .index(Hash256.of("066B61CF7248A5A08672541077E3C58EAFD1FA52DDF6B4FD93595E16542C0A14"))
+ .node(
+ OfferObject.builder()
+ .account(Address.of("rNdCZMZqHCo5VkrvsmNVt8ZtdpahT7rDKx"))
+ .bookDirectory(Hash256.of("D30EF7A9BFCCEE47AF722871D91E1E21522DF5141CA29AB05B071AFD498D0000"))
+ .bookNode("0")
+ .flags(OfferFlags.of(131072))
+ .ownerNode("0")
+ .previousTransactionId(Hash256.of("9D613D7E1E34DA5BE421E06002441F1E183C0B7B3323E1898FC585144A7C13B1"))
+ .previousTransactionLedgerSequence(UnsignedInteger.valueOf(41931065))
+ .sequence(UnsignedInteger.valueOf(41931063))
+ .takerGets(
+ IssuedCurrencyAmount.builder()
+ .currency("USD")
+ .issuer(Address.of("rNdCZMZqHCo5VkrvsmNVt8ZtdpahT7rDKx"))
+ .value("100")
+ .build()
+ )
+ .takerPays(XrpCurrencyAmount.ofDrops(200000000))
+ .index(Hash256.of("066B61CF7248A5A08672541077E3C58EAFD1FA52DDF6B4FD93595E16542C0A14"))
+ .build()
+ )
+ .status("success")
+ .build();
+
+ String json = "{\n" +
+ " \"index\": \"066B61CF7248A5A08672541077E3C58EAFD1FA52DDF6B4FD93595E16542C0A14\",\n" +
+ " \"ledger_hash\": \"54FE89D2FF925D623D386A03B402FDB22B2D2D058A62AEE441CA52CC9AA92BB1\",\n" +
+ " \"ledger_index\": 41931093,\n" +
+ " \"node\": {\n" +
+ " \"Account\": \"rNdCZMZqHCo5VkrvsmNVt8ZtdpahT7rDKx\",\n" +
+ " \"BookDirectory\": \"D30EF7A9BFCCEE47AF722871D91E1E21522DF5141CA29AB05B071AFD498D0000\",\n" +
+ " \"BookNode\": \"0\",\n" +
+ " \"Flags\": 131072,\n" +
+ " \"LedgerEntryType\": \"Offer\",\n" +
+ " \"OwnerNode\": \"0\",\n" +
+ " \"PreviousTxnID\": \"9D613D7E1E34DA5BE421E06002441F1E183C0B7B3323E1898FC585144A7C13B1\",\n" +
+ " \"PreviousTxnLgrSeq\": 41931065,\n" +
+ " \"Sequence\": 41931063,\n" +
+ " \"TakerGets\": {\n" +
+ " \"currency\": \"USD\",\n" +
+ " \"issuer\": \"rNdCZMZqHCo5VkrvsmNVt8ZtdpahT7rDKx\",\n" +
+ " \"value\": \"100\"\n" +
+ " },\n" +
+ " \"TakerPays\": \"200000000\",\n" +
+ " \"index\": \"066B61CF7248A5A08672541077E3C58EAFD1FA52DDF6B4FD93595E16542C0A14\"\n" +
+ " },\n" +
+ " \"status\": \"success\",\n" +
+ " \"validated\": true\n" +
+ " }";
+
+ assertCanSerializeAndDeserialize(result, json);
+ }
+
+ @Test
+ void testRippleStateResult() throws JSONException, JsonProcessingException {
+ LedgerEntryResult result = LedgerEntryResult.builder()
+ .ledgerIndex(LedgerIndex.of(UnsignedInteger.valueOf(83126482)))
+ .ledgerHash(Hash256.of("995F5C7565065ED88C251225C15A02C95D6AADD4AC75E199A9234FA8322B5F84"))
+ .validated(true)
+ .index(Hash256.of("6A409D31A016227B74D6A14C307239B2BBBE0CFBFCF7C271BFAF20CAA7A1E6DA"))
+ .node(
+ RippleStateObject.builder()
+ .balance(
+ IssuedCurrencyAmount.builder()
+ .currency("CNY")
+ .issuer(Address.of("rrrrrrrrrrrrrrrrrrrrBZbvji"))
+ .value("0")
+ .build()
+ )
+ .flags(RippleStateFlags.of(2228224))
+ .highLimit(
+ IssuedCurrencyAmount.builder()
+ .currency("CNY")
+ .issuer(Address.of("rHzKtpcB1KC1YuU4PBhk9m2abqrf2kZsfV"))
+ .value("1000000000")
+ .build()
+ )
+ .highNode("0")
+ .lowLimit(
+ IssuedCurrencyAmount.builder()
+ .currency("CNY")
+ .issuer(Address.of("rJ1adrpGS3xsnQMb9Cw54tWJVFPuSdZHK"))
+ .value("0")
+ .build()
+ )
+ .lowNode("2")
+ .previousTransactionId(Hash256.of("9A5E68C795D68665A648A5A05E5BC94AA3681400353236F75139BD102D9406FD"))
+ .previousTransactionLedgerSequence(UnsignedInteger.valueOf(69746363))
+ .index(Hash256.of("6A409D31A016227B74D6A14C307239B2BBBE0CFBFCF7C271BFAF20CAA7A1E6DA"))
+ .build()
+ )
+ .status("success")
+ .build();
+
+ String json = "{\n" +
+ " \"ledger_hash\": \"995F5C7565065ED88C251225C15A02C95D6AADD4AC75E199A9234FA8322B5F84\",\n" +
+ " \"ledger_index\": 83126482,\n" +
+ " \"validated\": true,\n" +
+ " \"index\": \"6A409D31A016227B74D6A14C307239B2BBBE0CFBFCF7C271BFAF20CAA7A1E6DA\",\n" +
+ " \"node\": {\n" +
+ " \"Balance\": {\n" +
+ " \"currency\": \"CNY\",\n" +
+ " \"issuer\": \"rrrrrrrrrrrrrrrrrrrrBZbvji\",\n" +
+ " \"value\": \"0\"\n" +
+ " },\n" +
+ " \"Flags\": 2228224,\n" +
+ " \"HighLimit\": {\n" +
+ " \"currency\": \"CNY\",\n" +
+ " \"issuer\": \"rHzKtpcB1KC1YuU4PBhk9m2abqrf2kZsfV\",\n" +
+ " \"value\": \"1000000000\"\n" +
+ " },\n" +
+ " \"HighNode\": \"0\",\n" +
+ " \"LedgerEntryType\": \"RippleState\",\n" +
+ " \"LowLimit\": {\n" +
+ " \"currency\": \"CNY\",\n" +
+ " \"issuer\": \"rJ1adrpGS3xsnQMb9Cw54tWJVFPuSdZHK\",\n" +
+ " \"value\": \"0\"\n" +
+ " },\n" +
+ " \"LowNode\": \"2\",\n" +
+ " \"PreviousTxnID\": \"9A5E68C795D68665A648A5A05E5BC94AA3681400353236F75139BD102D9406FD\",\n" +
+ " \"PreviousTxnLgrSeq\": 69746363,\n" +
+ " \"index\": \"6A409D31A016227B74D6A14C307239B2BBBE0CFBFCF7C271BFAF20CAA7A1E6DA\"\n" +
+ " },\n" +
+ " \"status\": \"success\"\n" +
+ " }";
+
+ assertCanSerializeAndDeserialize(result, json);
+ }
+
+ @Test
+ void testCheckResult() throws JSONException, JsonProcessingException {
+ LedgerEntryResult result = LedgerEntryResult.builder()
+ .ledgerIndex(LedgerIndex.of(UnsignedInteger.valueOf(83126482)))
+ .ledgerHash(Hash256.of("995F5C7565065ED88C251225C15A02C95D6AADD4AC75E199A9234FA8322B5F84"))
+ .validated(true)
+ .index(Hash256.of("56B5D2CC81461E339424869D0F5A2F4F24095B74FCD6F79960EF2D5EA10FBE00"))
+ .node(
+ CheckObject.builder()
+ .account(Address.of("rJk8P3yazgCSSvWXavKKCY5Y3tk4UGCiFF"))
+ .destination(Address.of("rHr2n1zVm5nzadgtJY5G2mUYnmWcrxfTbQ"))
+ .destinationNode("0")
+ .invoiceId(Hash256.of("5D059E085A91283DA8F2C1B8DB973994A3250ABDEDB934799A9C3EE243D3DFBD"))
+ .ownerNode("0")
+ .previousTxnId(Hash256.of("7F2DB52CA2D2C600748D7B1DF060964C74BF0219B8EF055DAB151F8A23CA1B09"))
+ .previousTransactionLedgerSequence(UnsignedInteger.valueOf(41931458))
+ .sendMax(XrpCurrencyAmount.ofDrops(12345))
+ .sequence(UnsignedInteger.valueOf(41931456))
+ .index(Hash256.of("56B5D2CC81461E339424869D0F5A2F4F24095B74FCD6F79960EF2D5EA10FBE00"))
+ .build()
+ )
+ .status("success")
+ .build();
+
+ String json = "{\n" +
+ " \"ledger_hash\": \"995F5C7565065ED88C251225C15A02C95D6AADD4AC75E199A9234FA8322B5F84\",\n" +
+ " \"ledger_index\": 83126482,\n" +
+ " \"validated\": true,\n" +
+ " \"index\": \"56B5D2CC81461E339424869D0F5A2F4F24095B74FCD6F79960EF2D5EA10FBE00\",\n" +
+ " \"node\": {\n" +
+ " \"Account\": \"rJk8P3yazgCSSvWXavKKCY5Y3tk4UGCiFF\",\n" +
+ " \"Destination\": \"rHr2n1zVm5nzadgtJY5G2mUYnmWcrxfTbQ\",\n" +
+ " \"DestinationNode\": \"0\",\n" +
+ " \"Flags\": 0,\n" +
+ " \"InvoiceID\": \"5D059E085A91283DA8F2C1B8DB973994A3250ABDEDB934799A9C3EE243D3DFBD\",\n" +
+ " \"LedgerEntryType\": \"Check\",\n" +
+ " \"OwnerNode\": \"0\",\n" +
+ " \"PreviousTxnID\": \"7F2DB52CA2D2C600748D7B1DF060964C74BF0219B8EF055DAB151F8A23CA1B09\",\n" +
+ " \"PreviousTxnLgrSeq\": 41931458,\n" +
+ " \"SendMax\": \"12345\",\n" +
+ " \"Sequence\": 41931456,\n" +
+ " \"index\": \"56B5D2CC81461E339424869D0F5A2F4F24095B74FCD6F79960EF2D5EA10FBE00\"\n" +
+ " },\n" +
+ " \"status\": \"success\"\n" +
+ " }";
+
+ assertCanSerializeAndDeserialize(result, json);
+ }
+
+ @Test
+ void testEscrowResult() throws JSONException, JsonProcessingException {
+ LedgerEntryResult result = LedgerEntryResult.builder()
+ .ledgerIndex(LedgerIndex.of(UnsignedInteger.valueOf(83126482)))
+ .ledgerHash(Hash256.of("995F5C7565065ED88C251225C15A02C95D6AADD4AC75E199A9234FA8322B5F84"))
+ .validated(true)
+ .index(Hash256.of("ABC67054C15F79FEE9183B44D2E16CA06A1804E023E6A2EDB288F4976B1BFEC5"))
+ .node(
+ EscrowObject.builder()
+ .account(Address.of("rEWt92vANNAghT9CC83DtnDDWZcJEL5gk1"))
+ .amount(XrpCurrencyAmount.ofDrops(123456))
+ .cancelAfter(UnsignedLong.valueOf(750277784))
+ .destination(Address.of("rMuTP1PFEFMVhDYKNBLgmre5YrCzVoCYjm"))
+ .destinationNode("0")
+ .finishAfter(UnsignedLong.valueOf(750277689))
+ .ownerNode("0")
+ .previousTransactionId(Hash256.of("466C5F96809D62385073F6BA43F5A3C217C96C4A493B7F64F6CE5B5B64278AAA"))
+ .previousTransactionLedgerSequence(UnsignedInteger.valueOf(41931760))
+ .index(Hash256.of("ABC67054C15F79FEE9183B44D2E16CA06A1804E023E6A2EDB288F4976B1BFEC5"))
+ .build()
+ )
+ .status("success")
+ .build();
+
+ String json = "{\n" +
+ " \"ledger_hash\": \"995F5C7565065ED88C251225C15A02C95D6AADD4AC75E199A9234FA8322B5F84\",\n" +
+ " \"ledger_index\": 83126482,\n" +
+ " \"validated\": true,\n" +
+ " \"index\": \"ABC67054C15F79FEE9183B44D2E16CA06A1804E023E6A2EDB288F4976B1BFEC5\",\n" +
+ " \"node\": {\n" +
+ " \"Account\": \"rEWt92vANNAghT9CC83DtnDDWZcJEL5gk1\",\n" +
+ " \"Amount\": \"123456\",\n" +
+ " \"CancelAfter\": 750277784,\n" +
+ " \"Destination\": \"rMuTP1PFEFMVhDYKNBLgmre5YrCzVoCYjm\",\n" +
+ " \"DestinationNode\": \"0\",\n" +
+ " \"FinishAfter\": 750277689,\n" +
+ " \"Flags\": 0,\n" +
+ " \"LedgerEntryType\": \"Escrow\",\n" +
+ " \"OwnerNode\": \"0\",\n" +
+ " \"PreviousTxnID\": \"466C5F96809D62385073F6BA43F5A3C217C96C4A493B7F64F6CE5B5B64278AAA\",\n" +
+ " \"PreviousTxnLgrSeq\": 41931760,\n" +
+ " \"index\": \"ABC67054C15F79FEE9183B44D2E16CA06A1804E023E6A2EDB288F4976B1BFEC5\"\n" +
+ " },\n" +
+ " \"status\": \"success\"\n" +
+ " }";
+
+ assertCanSerializeAndDeserialize(result, json);
+ }
+
+ @Test
+ void testPaymentChannelResult() throws JSONException, JsonProcessingException {
+ LedgerEntryResult result = LedgerEntryResult.builder()
+ .ledgerIndex(LedgerIndex.of(UnsignedInteger.valueOf(83126482)))
+ .ledgerHash(Hash256.of("995F5C7565065ED88C251225C15A02C95D6AADD4AC75E199A9234FA8322B5F84"))
+ .validated(true)
+ .index(Hash256.of("7474D1ED2DE25B055AD3C8473DDD69553ACD7325BF2B15C83D54E743C576C615"))
+ .node(
+ PayChannelObject.builder()
+ .account(Address.of("rtqQepGRnrvaHCDyLHcc8xY7uCTnV1aRT"))
+ .amount(XrpCurrencyAmount.ofDrops(10000))
+ .balance(XrpCurrencyAmount.ofDrops(0))
+ .cancelAfter(UnsignedLong.valueOf(533171558))
+ .destination(Address.of("r4sxKQshFFUvN8xDiP6KsnUKTyFi1un8UQ"))
+ .ownerNode("0")
+ .previousTransactionId(Hash256.of("987A299731B80FF14519FF28F18C14B0C55487133E086EAC895099428D57737C"))
+ .previousTransactionLedgerSequence(UnsignedInteger.valueOf(41931835))
+ .publicKey("EDAF1B0148D4FBB6BC0FCDA97C917C0BD831A654EBFD9B7D84FCB13ADE1BCB5C44")
+ .settleDelay(UnsignedLong.ONE)
+ .index(Hash256.of("7474D1ED2DE25B055AD3C8473DDD69553ACD7325BF2B15C83D54E743C576C615"))
+ .build()
+ )
+ .status("success")
+ .build();
+
+ String json = "{\n" +
+ " \"ledger_hash\": \"995F5C7565065ED88C251225C15A02C95D6AADD4AC75E199A9234FA8322B5F84\",\n" +
+ " \"ledger_index\": 83126482,\n" +
+ " \"validated\": true,\n" +
+ " \"index\": \"7474D1ED2DE25B055AD3C8473DDD69553ACD7325BF2B15C83D54E743C576C615\",\n" +
+ " \"node\": {\n" +
+ " \"Account\": \"rtqQepGRnrvaHCDyLHcc8xY7uCTnV1aRT\",\n" +
+ " \"Amount\": \"10000\",\n" +
+ " \"Balance\": \"0\",\n" +
+ " \"CancelAfter\": 533171558,\n" +
+ " \"Destination\": \"r4sxKQshFFUvN8xDiP6KsnUKTyFi1un8UQ\",\n" +
+ " \"Flags\": 0,\n" +
+ " \"LedgerEntryType\": \"PayChannel\",\n" +
+ " \"OwnerNode\": \"0\",\n" +
+ " \"PreviousTxnID\": \"987A299731B80FF14519FF28F18C14B0C55487133E086EAC895099428D57737C\",\n" +
+ " \"PreviousTxnLgrSeq\": 41931835,\n" +
+ " \"PublicKey\": \"EDAF1B0148D4FBB6BC0FCDA97C917C0BD831A654EBFD9B7D84FCB13ADE1BCB5C44\",\n" +
+ " \"SettleDelay\": 1,\n" +
+ " \"index\": \"7474D1ED2DE25B055AD3C8473DDD69553ACD7325BF2B15C83D54E743C576C615\"\n" +
+ " },\n" +
+ " \"status\": \"success\"\n" +
+ " }";
+
+ assertCanSerializeAndDeserialize(result, json);
+ }
+
+ @Test
+ void testDepositPreAuthResult() throws JSONException, JsonProcessingException {
+ LedgerEntryResult result = LedgerEntryResult.builder()
+ .ledgerIndex(LedgerIndex.of(UnsignedInteger.valueOf(83126482)))
+ .ledgerHash(Hash256.of("995F5C7565065ED88C251225C15A02C95D6AADD4AC75E199A9234FA8322B5F84"))
+ .validated(true)
+ .index(Hash256.of("4CFA41F0CEB3BBECB0799BCD4E70057A80B98E762AD655D005BE90992E32CDF7"))
+ .node(
+ DepositPreAuthObject.builder()
+ .account(Address.of("rnmLMp1znQHpSM7xKzL1rg9unXiu1o8ptU"))
+ .authorize(Address.of("r4yaMT4QVKFQsyw5sLrJMETe3Wx1L5P9Pe"))
+ .ownerNode("0")
+ .previousTransactionId(Hash256.of("8D2D634EC7E5B4C6BCB5D4DD72575D42A60A11AD91E5A991692E525E6BF463BA"))
+ .previousTransactionLedgerSequence(UnsignedInteger.valueOf(41931900))
+ .index(Hash256.of("4CFA41F0CEB3BBECB0799BCD4E70057A80B98E762AD655D005BE90992E32CDF7"))
+ .build()
+ )
+ .status("success")
+ .build();
+
+ String json = "{\n" +
+ " \"ledger_hash\": \"995F5C7565065ED88C251225C15A02C95D6AADD4AC75E199A9234FA8322B5F84\",\n" +
+ " \"ledger_index\": 83126482,\n" +
+ " \"validated\": true,\n" +
+ " \"index\": \"4CFA41F0CEB3BBECB0799BCD4E70057A80B98E762AD655D005BE90992E32CDF7\",\n" +
+ " \"node\": {\n" +
+ " \"Account\": \"rnmLMp1znQHpSM7xKzL1rg9unXiu1o8ptU\",\n" +
+ " \"Authorize\": \"r4yaMT4QVKFQsyw5sLrJMETe3Wx1L5P9Pe\",\n" +
+ " \"Flags\": 0,\n" +
+ " \"LedgerEntryType\": \"DepositPreauth\",\n" +
+ " \"OwnerNode\": \"0\",\n" +
+ " \"PreviousTxnID\": \"8D2D634EC7E5B4C6BCB5D4DD72575D42A60A11AD91E5A991692E525E6BF463BA\",\n" +
+ " \"PreviousTxnLgrSeq\": 41931900,\n" +
+ " \"index\": \"4CFA41F0CEB3BBECB0799BCD4E70057A80B98E762AD655D005BE90992E32CDF7\"\n" +
+ " },\n" +
+ " \"status\": \"success\"\n" +
+ " }";
+
+ assertCanSerializeAndDeserialize(result, json);
+ }
+
+ @Test
+ void testTicketResult() throws JSONException, JsonProcessingException {
+ LedgerEntryResult result = LedgerEntryResult.builder()
+ .ledgerIndex(LedgerIndex.of(UnsignedInteger.valueOf(83126482)))
+ .ledgerHash(Hash256.of("995F5C7565065ED88C251225C15A02C95D6AADD4AC75E199A9234FA8322B5F84"))
+ .validated(true)
+ .index(Hash256.of("8A0FB133F2D9875961990CE1F6CBB08120C7BD9B330B5D2C9718DE2A4ABCFC47"))
+ .node(
+ TicketObject.builder()
+ .account(Address.of("rKfyHN2fbAJuHtSc1gStGDxxbq4kf9VPFQ"))
+ .ownerNode("0")
+ .previousTransactionId(Hash256.of("AB5B87765DABF11B9FE5B40506E355532BBE3CF0ADC8C15AF1CD82F4F68CD13D"))
+ .previousTransactionLedgerSequence(UnsignedInteger.valueOf(41932010))
+ .ticketSequence(UnsignedInteger.valueOf(41932009))
+ .index(Hash256.of("8A0FB133F2D9875961990CE1F6CBB08120C7BD9B330B5D2C9718DE2A4ABCFC47"))
+ .build()
+ )
+ .status("success")
+ .build();
+
+ String json = "{\n" +
+ " \"ledger_hash\": \"995F5C7565065ED88C251225C15A02C95D6AADD4AC75E199A9234FA8322B5F84\",\n" +
+ " \"ledger_index\": 83126482,\n" +
+ " \"validated\": true,\n" +
+ " \"index\": \"8A0FB133F2D9875961990CE1F6CBB08120C7BD9B330B5D2C9718DE2A4ABCFC47\",\n" +
+ " \"node\": {\n" +
+ " \"Account\": \"rKfyHN2fbAJuHtSc1gStGDxxbq4kf9VPFQ\",\n" +
+ " \"Flags\": 0,\n" +
+ " \"LedgerEntryType\": \"Ticket\",\n" +
+ " \"OwnerNode\": \"0\",\n" +
+ " \"PreviousTxnID\": \"AB5B87765DABF11B9FE5B40506E355532BBE3CF0ADC8C15AF1CD82F4F68CD13D\",\n" +
+ " \"PreviousTxnLgrSeq\": 41932010,\n" +
+ " \"TicketSequence\": 41932009,\n" +
+ " \"index\": \"8A0FB133F2D9875961990CE1F6CBB08120C7BD9B330B5D2C9718DE2A4ABCFC47\"\n" +
+ " },\n" +
+ " \"status\": \"success\"\n" +
+ " }";
+
+ assertCanSerializeAndDeserialize(result, json);
+ }
+
+ @Test
+ void testNftPageResult() throws JSONException, JsonProcessingException {
+ LedgerEntryResult result = LedgerEntryResult.builder()
+ .ledgerIndex(LedgerIndex.of(UnsignedInteger.valueOf(83126482)))
+ .ledgerHash(Hash256.of("995F5C7565065ED88C251225C15A02C95D6AADD4AC75E199A9234FA8322B5F84"))
+ .validated(true)
+ .index(Hash256.of("4070656F661A60726DBB384E09F6E36B88071072FFFFFFFFFFFFFFFFFFFFFFFF"))
+ .node(
+ NfTokenPageObject.builder()
+ .addNfTokens(
+ NfTokenWrapper.of(
+ NfToken.builder()
+ .nfTokenId(NfTokenId.of("000000004070656F661A60726DBB384E09F6E36B880710720000099A00000000"))
+ .uri(NfTokenUri.of(
+ "697066733A2F2F62616679626569676479727A74357366703775646D376875373675683779323" +
+ "66E6634646675796C71616266336F636C67747179353566627A6469")
+ )
+ .build()
+ )
+ )
+
+ .previousTransactionId(Hash256.of("C94CF5BC9DF78A75997E93C71CDD8A2776E2DF279DD6F394BF4976045D960C4B"))
+ .previousTransactionLedgerSequence(LedgerIndex.of(UnsignedInteger.valueOf(41932089)))
+ .index(Hash256.of("4070656F661A60726DBB384E09F6E36B88071072FFFFFFFFFFFFFFFFFFFFFFFF"))
+ .build()
+ )
+ .status("success")
+ .build();
+
+ String json = "{\n" +
+ " \"ledger_hash\": \"995F5C7565065ED88C251225C15A02C95D6AADD4AC75E199A9234FA8322B5F84\",\n" +
+ " \"ledger_index\": 83126482,\n" +
+ " \"validated\": true,\n" +
+ " \"index\": \"4070656F661A60726DBB384E09F6E36B88071072FFFFFFFFFFFFFFFFFFFFFFFF\",\n" +
+ " \"node\": {\n" +
+ " \"LedgerEntryType\": \"NFTokenPage\",\n" +
+ " \"NFTokens\": [\n" +
+ " {\n" +
+ " \"NFToken\": {\n" +
+ " \"NFTokenID\": \"000000004070656F661A60726DBB384E09F6E36B880710720000099A00000000\",\n" +
+ " \"URI\": \"697066733A2F2F62616679626569676479727A74357366703775646D37687537367568377" +
+ "932366E6634646675796C71616266336F636C67747179353566627A6469\"\n" +
+ " }\n" +
+ " }\n" +
+ " ],\n" +
+ " \"PreviousTxnID\": \"C94CF5BC9DF78A75997E93C71CDD8A2776E2DF279DD6F394BF4976045D960C4B\",\n" +
+ " \"PreviousTxnLgrSeq\": 41932089,\n" +
+ " \"index\": \"4070656F661A60726DBB384E09F6E36B88071072FFFFFFFFFFFFFFFFFFFFFFFF\"\n" +
+ " },\n" +
+ " \"status\": \"success\"\n" +
+ " }";
+
+ assertCanSerializeAndDeserialize(result, json);
+ }
+
+ @Test
+ void testWithHashAndLedgerIndex() {
+ LedgerEntryResult result = LedgerEntryResult.builder()
+ .node(mock(LedgerObject.class))
+ .ledgerHash(HASH_256)
+ .ledgerIndex(LedgerIndex.of(UnsignedInteger.ONE))
+ .index(HASH_256)
+ .build();
+
+ assertThat(result.ledgerHash()).isNotEmpty().get().isEqualTo(result.ledgerHashSafe());
+ assertThat(result.ledgerIndex()).isNotEmpty().get().isEqualTo(result.ledgerIndexSafe());
+ assertThat(result.ledgerCurrentIndex()).isEmpty();
+ assertThatThrownBy(result::ledgerCurrentIndexSafe)
+ .isInstanceOf(IllegalStateException.class)
+ .hasMessage("Result did not contain a ledgerCurrentIndex.");
+ }
+
+ @Test
+ void testWithLedgerCurrentIndex() {
+ LedgerEntryResult result = LedgerEntryResult.builder()
+ .node(mock(LedgerObject.class))
+ .ledgerCurrentIndex(LedgerIndex.of(UnsignedInteger.ONE))
+ .index(HASH_256)
+ .build();
+
+ assertThat(result.ledgerCurrentIndex()).isNotEmpty().get().isEqualTo(result.ledgerCurrentIndexSafe());
+ assertThat(result.ledgerHash()).isEmpty();
+ assertThat(result.ledgerIndex()).isEmpty();
+ assertThatThrownBy(result::ledgerHashSafe)
+ .isInstanceOf(IllegalStateException.class)
+ .hasMessage("Result did not contain a ledgerHash.");
+ assertThatThrownBy(result::ledgerIndexSafe)
+ .isInstanceOf(IllegalStateException.class)
+ .hasMessage("Result did not contain a ledgerIndex.");
+ }
+}
\ No newline at end of file
diff --git a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/flags/AccountRootFlagsTests.java b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/flags/AccountRootFlagsTests.java
index c8b4ed840..e5f7e7668 100644
--- a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/flags/AccountRootFlagsTests.java
+++ b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/flags/AccountRootFlagsTests.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.
@@ -34,7 +34,7 @@
public class AccountRootFlagsTests extends AbstractFlagsTest {
public static Stream data() {
- return getBooleanCombinations(13);
+ return getBooleanCombinations(14);
}
@ParameterizedTest
@@ -53,7 +53,8 @@ public void testDeriveIndividualFlagsFromFlags(
boolean lsfDisallowIncomingNFTokenOffer,
boolean lsfDisallowIncomingCheck,
boolean lsfDisallowIncomingPayChan,
- boolean lsfDisallowIncomingTrustline
+ boolean lsfDisallowIncomingTrustline,
+ boolean lsfAllowTrustlineClawback
) {
long expectedFlags = (lsfDefaultRipple ? AccountRootFlags.DEFAULT_RIPPLE.getValue() : 0L) |
(lsfDepositAuth ? AccountRootFlags.DEPOSIT_AUTH.getValue() : 0L) |
@@ -67,7 +68,8 @@ public void testDeriveIndividualFlagsFromFlags(
(lsfDisallowIncomingNFTokenOffer ? AccountRootFlags.DISALLOW_INCOMING_NFT_OFFER.getValue() : 0L) |
(lsfDisallowIncomingCheck ? AccountRootFlags.DISALLOW_INCOMING_CHECK.getValue() : 0L) |
(lsfDisallowIncomingPayChan ? AccountRootFlags.DISALLOW_INCOMING_PAY_CHAN.getValue() : 0L) |
- (lsfDisallowIncomingTrustline ? AccountRootFlags.DISALLOW_INCOMING_TRUSTLINE.getValue() : 0L);
+ (lsfDisallowIncomingTrustline ? AccountRootFlags.DISALLOW_INCOMING_TRUSTLINE.getValue() : 0L) |
+ (lsfAllowTrustlineClawback ? AccountRootFlags.ALLOW_TRUSTLINE_CLAWBACK.getValue() : 0L);
Flags flagsFromFlags = AccountRootFlags.of(
(lsfDefaultRipple ? AccountRootFlags.DEFAULT_RIPPLE : AccountRootFlags.UNSET),
(lsfDepositAuth ? AccountRootFlags.DEPOSIT_AUTH : AccountRootFlags.UNSET),
@@ -81,7 +83,8 @@ public void testDeriveIndividualFlagsFromFlags(
(lsfDisallowIncomingNFTokenOffer ? AccountRootFlags.DISALLOW_INCOMING_NFT_OFFER : AccountRootFlags.UNSET),
(lsfDisallowIncomingCheck ? AccountRootFlags.DISALLOW_INCOMING_CHECK : AccountRootFlags.UNSET),
(lsfDisallowIncomingPayChan ? AccountRootFlags.DISALLOW_INCOMING_PAY_CHAN : AccountRootFlags.UNSET),
- (lsfDisallowIncomingTrustline ? AccountRootFlags.DISALLOW_INCOMING_TRUSTLINE : AccountRootFlags.UNSET)
+ (lsfDisallowIncomingTrustline ? AccountRootFlags.DISALLOW_INCOMING_TRUSTLINE : AccountRootFlags.UNSET),
+ (lsfAllowTrustlineClawback ? AccountRootFlags.ALLOW_TRUSTLINE_CLAWBACK : AccountRootFlags.UNSET)
);
assertThat(flagsFromFlags.getValue()).isEqualTo(expectedFlags);
@@ -102,6 +105,7 @@ public void testDeriveIndividualFlagsFromFlags(
assertThat(flagsFromLong.lsfDisallowIncomingCheck()).isEqualTo(lsfDisallowIncomingCheck);
assertThat(flagsFromLong.lsfDisallowIncomingPayChan()).isEqualTo(lsfDisallowIncomingPayChan);
assertThat(flagsFromLong.lsfDisallowIncomingTrustline()).isEqualTo(lsfDisallowIncomingTrustline);
+ assertThat(flagsFromLong.lsfAllowTrustLineClawback()).isEqualTo(lsfAllowTrustlineClawback);
}
@ParameterizedTest
@@ -120,7 +124,8 @@ void testJson(
boolean lsfDisallowIncomingNFTokenOffer,
boolean lsfDisallowIncomingCheck,
boolean lsfDisallowIncomingPayChan,
- boolean lsfDisallowIncomingTrustline
+ boolean lsfDisallowIncomingTrustline,
+ boolean lsfAllowTrustlineClawback
) throws JSONException, JsonProcessingException {
Flags flags = AccountRootFlags.of(
(lsfDefaultRipple ? AccountRootFlags.DEFAULT_RIPPLE : AccountRootFlags.UNSET),
@@ -135,7 +140,8 @@ void testJson(
(lsfDisallowIncomingNFTokenOffer ? AccountRootFlags.DISALLOW_INCOMING_NFT_OFFER : AccountRootFlags.UNSET),
(lsfDisallowIncomingCheck ? AccountRootFlags.DISALLOW_INCOMING_CHECK : AccountRootFlags.UNSET),
(lsfDisallowIncomingPayChan ? AccountRootFlags.DISALLOW_INCOMING_PAY_CHAN : AccountRootFlags.UNSET),
- (lsfDisallowIncomingTrustline ? AccountRootFlags.DISALLOW_INCOMING_TRUSTLINE : AccountRootFlags.UNSET)
+ (lsfDisallowIncomingTrustline ? AccountRootFlags.DISALLOW_INCOMING_TRUSTLINE : AccountRootFlags.UNSET),
+ (lsfAllowTrustlineClawback ? AccountRootFlags.ALLOW_TRUSTLINE_CLAWBACK : AccountRootFlags.UNSET)
);
FlagsWrapper flagsWrapper = FlagsWrapper.of(flags);
diff --git a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/flags/AmmDepositFlagsTest.java b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/flags/AmmDepositFlagsTest.java
new file mode 100644
index 000000000..f011c82a4
--- /dev/null
+++ b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/flags/AmmDepositFlagsTest.java
@@ -0,0 +1,59 @@
+package org.xrpl.xrpl4j.model.flags;
+
+import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
+
+import org.junit.jupiter.api.Test;
+
+public class AmmDepositFlagsTest {
+
+ @Test
+ void testFlagValues() {
+ AmmDepositFlags lpToken = AmmDepositFlags.LP_TOKEN;
+ assertThat(lpToken.tfLpToken()).isTrue();
+ assertThat(lpToken.tfSingleAsset()).isFalse();
+ assertThat(lpToken.tfTwoAsset()).isFalse();
+ assertThat(lpToken.tfOneAssetLpToken()).isFalse();
+ assertThat(lpToken.tfLimitLpToken()).isFalse();
+ assertThat(lpToken.tfTwoAssetIfEmpty()).isFalse();
+
+ AmmDepositFlags singleAsset = AmmDepositFlags.SINGLE_ASSET;
+ assertThat(singleAsset.tfLpToken()).isFalse();
+ assertThat(singleAsset.tfSingleAsset()).isTrue();
+ assertThat(singleAsset.tfTwoAsset()).isFalse();
+ assertThat(singleAsset.tfOneAssetLpToken()).isFalse();
+ assertThat(singleAsset.tfLimitLpToken()).isFalse();
+ assertThat(singleAsset.tfTwoAssetIfEmpty()).isFalse();
+
+ AmmDepositFlags twoAsset = AmmDepositFlags.TWO_ASSET;
+ assertThat(twoAsset.tfLpToken()).isFalse();
+ assertThat(twoAsset.tfSingleAsset()).isFalse();
+ assertThat(twoAsset.tfTwoAsset()).isTrue();
+ assertThat(twoAsset.tfOneAssetLpToken()).isFalse();
+ assertThat(twoAsset.tfLimitLpToken()).isFalse();
+ assertThat(twoAsset.tfTwoAssetIfEmpty()).isFalse();
+
+ AmmDepositFlags oneAssetLpToken = AmmDepositFlags.ONE_ASSET_LP_TOKEN;
+ assertThat(oneAssetLpToken.tfLpToken()).isFalse();
+ assertThat(oneAssetLpToken.tfSingleAsset()).isFalse();
+ assertThat(oneAssetLpToken.tfTwoAsset()).isFalse();
+ assertThat(oneAssetLpToken.tfOneAssetLpToken()).isTrue();
+ assertThat(oneAssetLpToken.tfLimitLpToken()).isFalse();
+ assertThat(oneAssetLpToken.tfTwoAssetIfEmpty()).isFalse();
+
+ AmmDepositFlags limitLpToken = AmmDepositFlags.LIMIT_LP_TOKEN;
+ assertThat(limitLpToken.tfLpToken()).isFalse();
+ assertThat(limitLpToken.tfSingleAsset()).isFalse();
+ assertThat(limitLpToken.tfTwoAsset()).isFalse();
+ assertThat(limitLpToken.tfOneAssetLpToken()).isFalse();
+ assertThat(limitLpToken.tfLimitLpToken()).isTrue();
+ assertThat(limitLpToken.tfTwoAssetIfEmpty()).isFalse();
+
+ AmmDepositFlags twoAssetIfEmpty = AmmDepositFlags.TWO_ASSET_IF_EMPTY;
+ assertThat(twoAssetIfEmpty.tfLpToken()).isFalse();
+ assertThat(twoAssetIfEmpty.tfSingleAsset()).isFalse();
+ assertThat(twoAssetIfEmpty.tfTwoAsset()).isFalse();
+ assertThat(twoAssetIfEmpty.tfOneAssetLpToken()).isFalse();
+ assertThat(twoAssetIfEmpty.tfLimitLpToken()).isFalse();
+ assertThat(twoAssetIfEmpty.tfTwoAssetIfEmpty()).isTrue();
+ }
+}
diff --git a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/flags/AmmWithdrawFlagsTest.java b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/flags/AmmWithdrawFlagsTest.java
new file mode 100644
index 000000000..ba89e32ba
--- /dev/null
+++ b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/flags/AmmWithdrawFlagsTest.java
@@ -0,0 +1,75 @@
+package org.xrpl.xrpl4j.model.flags;
+
+import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
+
+import org.junit.jupiter.api.Test;
+
+public class AmmWithdrawFlagsTest {
+
+ @Test
+ void testFlagValues() {
+ AmmWithdrawFlags lpToken = AmmWithdrawFlags.LP_TOKEN;
+ assertThat(lpToken.tfLpToken()).isTrue();
+ assertThat(lpToken.tfWithdrawAll()).isFalse();
+ assertThat(lpToken.tfOneAssetWithdrawAll()).isFalse();
+ assertThat(lpToken.tfSingleAsset()).isFalse();
+ assertThat(lpToken.tfTwoAsset()).isFalse();
+ assertThat(lpToken.tfOneAssetLpToken()).isFalse();
+ assertThat(lpToken.tfLimitLpToken()).isFalse();
+
+ AmmWithdrawFlags withdrawAll = AmmWithdrawFlags.WITHDRAW_ALL;
+ assertThat(withdrawAll.tfLpToken()).isFalse();
+ assertThat(withdrawAll.tfWithdrawAll()).isTrue();
+ assertThat(withdrawAll.tfOneAssetWithdrawAll()).isFalse();
+ assertThat(withdrawAll.tfSingleAsset()).isFalse();
+ assertThat(withdrawAll.tfTwoAsset()).isFalse();
+ assertThat(withdrawAll.tfOneAssetLpToken()).isFalse();
+ assertThat(withdrawAll.tfLimitLpToken()).isFalse();
+
+ AmmWithdrawFlags oneAssetWithdrawAll = AmmWithdrawFlags.ONE_ASSET_WITHDRAW_ALL;
+ assertThat(oneAssetWithdrawAll.tfLpToken()).isFalse();
+ assertThat(oneAssetWithdrawAll.tfWithdrawAll()).isFalse();
+ assertThat(oneAssetWithdrawAll.tfOneAssetWithdrawAll()).isTrue();
+ assertThat(oneAssetWithdrawAll.tfSingleAsset()).isFalse();
+ assertThat(oneAssetWithdrawAll.tfTwoAsset()).isFalse();
+ assertThat(oneAssetWithdrawAll.tfOneAssetLpToken()).isFalse();
+ assertThat(oneAssetWithdrawAll.tfLimitLpToken()).isFalse();
+
+ AmmWithdrawFlags singleAsset = AmmWithdrawFlags.SINGLE_ASSET;
+ assertThat(singleAsset.tfLpToken()).isFalse();
+ assertThat(singleAsset.tfWithdrawAll()).isFalse();
+ assertThat(singleAsset.tfOneAssetWithdrawAll()).isFalse();
+ assertThat(singleAsset.tfSingleAsset()).isTrue();
+ assertThat(singleAsset.tfTwoAsset()).isFalse();
+ assertThat(singleAsset.tfOneAssetLpToken()).isFalse();
+ assertThat(singleAsset.tfLimitLpToken()).isFalse();
+
+ AmmWithdrawFlags twoAsset = AmmWithdrawFlags.TWO_ASSET;
+ assertThat(twoAsset.tfLpToken()).isFalse();
+ assertThat(twoAsset.tfWithdrawAll()).isFalse();
+ assertThat(twoAsset.tfOneAssetWithdrawAll()).isFalse();
+ assertThat(twoAsset.tfSingleAsset()).isFalse();
+ assertThat(twoAsset.tfTwoAsset()).isTrue();
+ assertThat(twoAsset.tfOneAssetLpToken()).isFalse();
+ assertThat(twoAsset.tfLimitLpToken()).isFalse();
+
+ AmmWithdrawFlags oneAssetLpToken = AmmWithdrawFlags.ONE_ASSET_LP_TOKEN;
+ assertThat(oneAssetLpToken.tfLpToken()).isFalse();
+ assertThat(oneAssetLpToken.tfWithdrawAll()).isFalse();
+ assertThat(oneAssetLpToken.tfOneAssetWithdrawAll()).isFalse();
+ assertThat(oneAssetLpToken.tfSingleAsset()).isFalse();
+ assertThat(oneAssetLpToken.tfTwoAsset()).isFalse();
+ assertThat(oneAssetLpToken.tfOneAssetLpToken()).isTrue();
+ assertThat(oneAssetLpToken.tfLimitLpToken()).isFalse();
+
+ AmmWithdrawFlags limitLpToken = AmmWithdrawFlags.LIMIT_LP_TOKEN;
+ assertThat(limitLpToken.tfLpToken()).isFalse();
+ assertThat(limitLpToken.tfWithdrawAll()).isFalse();
+ assertThat(limitLpToken.tfOneAssetWithdrawAll()).isFalse();
+ assertThat(limitLpToken.tfSingleAsset()).isFalse();
+ assertThat(limitLpToken.tfTwoAsset()).isFalse();
+ assertThat(limitLpToken.tfOneAssetLpToken()).isFalse();
+ assertThat(limitLpToken.tfLimitLpToken()).isTrue();
+
+ }
+}
diff --git a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/ledger/AmmObjectTest.java b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/ledger/AmmObjectTest.java
new file mode 100644
index 000000000..ec28b7e23
--- /dev/null
+++ b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/ledger/AmmObjectTest.java
@@ -0,0 +1,125 @@
+package org.xrpl.xrpl4j.model.ledger;
+
+import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.xrpl.xrpl4j.crypto.TestConstants.HASH_256;
+
+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;
+import org.xrpl.xrpl4j.model.transactions.Address;
+import org.xrpl.xrpl4j.model.transactions.IssuedCurrencyAmount;
+import org.xrpl.xrpl4j.model.transactions.TradingFee;
+import org.xrpl.xrpl4j.model.transactions.VoteWeight;
+
+class AmmObjectTest extends AbstractJsonTest {
+
+ @Test
+ void voteSlotsUnwrapped() {
+ VoteEntry voteEntry1 = VoteEntry.builder()
+ .account(Address.of("rE54zDvgnghAoPopCgvtiqWNq3dU5y836S"))
+ .voteWeight(VoteWeight.of(UnsignedInteger.ONE))
+ .tradingFee(TradingFee.of(UnsignedInteger.ONE))
+ .build();
+ VoteEntry voteEntry2 = VoteEntry.builder()
+ .account(Address.of("rP9jPyP5kyvFRb6ZiRghAGw5u8SGAmU4bd"))
+ .voteWeight(VoteWeight.of(UnsignedInteger.valueOf(2)))
+ .tradingFee(TradingFee.of(UnsignedInteger.valueOf(2)))
+ .build();
+ AmmObject ammObject = AmmObject.builder()
+ .asset(mock(Issue.class))
+ .asset2(mock(Issue.class))
+ .account(Address.of("rP9jPyP5kyvFRb6ZiRghAGw5u8SGAmU4bd"))
+ .lpTokenBalance(mock(IssuedCurrencyAmount.class))
+ .tradingFee(TradingFee.of(UnsignedInteger.ONE))
+ .addVoteSlots(
+ VoteEntryWrapper.of(voteEntry1),
+ VoteEntryWrapper.of(voteEntry2)
+ )
+ .index(HASH_256)
+ .ownerNode("0")
+ .build();
+
+ assertThat(ammObject.voteSlotsUnwrapped()).asList()
+ .containsExactlyInAnyOrder(voteEntry1, voteEntry2);
+ }
+
+ @Test
+ void testJson() throws JSONException, JsonProcessingException {
+ AmmObject ammObject = AmmObject.builder()
+ .asset(Issue.XRP)
+ .asset2(
+ Issue.builder()
+ .currency("TST")
+ .issuer(Address.of("rP9jPyP5kyvFRb6ZiRghAGw5u8SGAmU4bd"))
+ .build()
+ )
+ .account(Address.of("rE54zDvgnghAoPopCgvtiqWNq3dU5y836S"))
+ .lpTokenBalance(
+ IssuedCurrencyAmount.builder()
+ .value("71150.53584131501")
+ .currency("039C99CD9AB0B70B32ECDA51EAAE471625608EA2")
+ .issuer(Address.of("rE54zDvgnghAoPopCgvtiqWNq3dU5y836S"))
+ .build()
+ )
+ .tradingFee(TradingFee.of(UnsignedInteger.valueOf(600)))
+ .addVoteSlots(
+ VoteEntryWrapper.of(
+ VoteEntry.builder()
+ .account(Address.of("rJVUeRqDFNs2xqA7ncVE6ZoAhPUoaJJSQm"))
+ .voteWeight(VoteWeight.of(UnsignedInteger.valueOf(100000)))
+ .tradingFee(TradingFee.of(UnsignedInteger.valueOf(600)))
+ .build()
+ )
+ )
+ .auctionSlot(
+ AuctionSlot.builder()
+ .account(Address.of("rJVUeRqDFNs2xqA7ncVE6ZoAhPUoaJJSQm"))
+ .addAuthAccounts(
+ AuthAccountWrapper.of(
+ AuthAccount.of(
+ Address.of("rMKXGCbJ5d8LbrqthdG46q3f969MVK2Qeg")
+ )
+ ),
+ AuthAccountWrapper.of(
+ AuthAccount.of(
+ Address.of("rBepJuTLFJt3WmtLXYAxSjtBWAeQxVbncv")
+ )
+ )
+ )
+ .discountedFee(TradingFee.of(UnsignedInteger.ZERO))
+ .price(
+ IssuedCurrencyAmount.builder()
+ .value("0.8696263565463045")
+ .currency("039C99CD9AB0B70B32ECDA51EAAE471625608EA2")
+ .issuer(Address.of("rE54zDvgnghAoPopCgvtiqWNq3dU5y836S"))
+ .build()
+ )
+ .expiration(UnsignedInteger.valueOf(721870180))
+ .build()
+ )
+ .index(HASH_256)
+ .ownerNode("0")
+ .build();
+
+ String json = String.format("{\n" +
+ " \"Account\" : \"rE54zDvgnghAoPopCgvtiqWNq3dU5y836S\",\n" +
+ " \"LedgerEntryType\" : \"AMM\",\n" +
+ " \"Asset\" : " + objectMapper.writeValueAsString(ammObject.asset()) + "," +
+ " \"Asset2\" : " + objectMapper.writeValueAsString(ammObject.asset2()) + "," +
+ " \"AuctionSlot\" : " + objectMapper.writeValueAsString(ammObject.auctionSlot()) + "," +
+ " \"Flags\" : 0,\n" +
+ " \"LPTokenBalance\" : " + objectMapper.writeValueAsString(ammObject.lpTokenBalance()) + "," +
+ " \"TradingFee\" : 600,\n" +
+ " \"index\" : %s,\n" +
+ " \"OwnerNode\" : \"0\",\n" +
+ " \"VoteSlots\" : [\n" +
+ objectMapper.writeValueAsString(ammObject.voteSlots().get(0)) +
+ " ]\n" +
+ "}", HASH_256);
+
+ assertCanSerializeAndDeserialize(ammObject, json);
+ }
+}
\ No newline at end of file
diff --git a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/ledger/AuctionSlotTest.java b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/ledger/AuctionSlotTest.java
new file mode 100644
index 000000000..bb6d9bcb6
--- /dev/null
+++ b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/ledger/AuctionSlotTest.java
@@ -0,0 +1,78 @@
+package org.xrpl.xrpl4j.model.ledger;
+
+import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
+import static org.mockito.Mockito.mock;
+
+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;
+import org.xrpl.xrpl4j.model.transactions.Address;
+import org.xrpl.xrpl4j.model.transactions.IssuedCurrencyAmount;
+import org.xrpl.xrpl4j.model.transactions.TradingFee;
+
+class AuctionSlotTest extends AbstractJsonTest {
+
+ @Test
+ void authAccountsAddresses() {
+ Address address1 = Address.of("rG1QQv2nh2gr7RCZ1P8YYcBUKCCN633jCn");
+ Address address2 = Address.of("rB1QQv2nh2gr7RCZ1P8YYcBUKCCN633jCn");
+ AuctionSlot slot = AuctionSlot.builder()
+ .account(Address.of("rJVUeRqDFNs2xqA7ncVE6ZoAhPUoaJJSQm"))
+ .discountedFee(TradingFee.of(UnsignedInteger.ONE))
+ .price(mock(IssuedCurrencyAmount.class))
+ .expiration(UnsignedInteger.ONE)
+ .addAuthAccounts(
+ AuthAccountWrapper.of(AuthAccount.of(address1)),
+ AuthAccountWrapper.of(AuthAccount.of(address2))
+ )
+ .build();
+ assertThat(slot.authAccountsAddresses()).asList().containsExactlyInAnyOrder(address1, address2);
+ }
+
+ @Test
+ void testJson() throws JSONException, JsonProcessingException {
+ AuctionSlot slot = AuctionSlot.builder()
+ .account(Address.of("rJVUeRqDFNs2xqA7ncVE6ZoAhPUoaJJSQm"))
+ .discountedFee(TradingFee.of(UnsignedInteger.ZERO))
+ .price(
+ IssuedCurrencyAmount.builder()
+ .issuer(Address.of("rE54zDvgnghAoPopCgvtiqWNq3dU5y836S"))
+ .currency("039C99CD9AB0B70B32ECDA51EAAE471625608EA2")
+ .value("0.8696263565463045")
+ .build()
+ )
+ .expiration(UnsignedInteger.valueOf(721870180))
+ .addAuthAccounts(
+ AuthAccountWrapper.of(AuthAccount.of(Address.of("rMKXGCbJ5d8LbrqthdG46q3f969MVK2Qeg"))),
+ AuthAccountWrapper.of(AuthAccount.of(Address.of("rBepJuTLFJt3WmtLXYAxSjtBWAeQxVbncv")))
+ )
+ .build();
+
+ String json = "{\n" +
+ " \"Account\" : \"rJVUeRqDFNs2xqA7ncVE6ZoAhPUoaJJSQm\",\n" +
+ " \"AuthAccounts\" : [\n" +
+ " {\n" +
+ " \"AuthAccount\" : {\n" +
+ " \"Account\" : \"rMKXGCbJ5d8LbrqthdG46q3f969MVK2Qeg\"\n" +
+ " }\n" +
+ " },\n" +
+ " {\n" +
+ " \"AuthAccount\" : {\n" +
+ " \"Account\" : \"rBepJuTLFJt3WmtLXYAxSjtBWAeQxVbncv\"\n" +
+ " }\n" +
+ " }\n" +
+ " ],\n" +
+ " \"DiscountedFee\" : 0,\n" +
+ " \"Expiration\" : 721870180,\n" +
+ " \"Price\" : {\n" +
+ " \"currency\" : \"039C99CD9AB0B70B32ECDA51EAAE471625608EA2\",\n" +
+ " \"issuer\" : \"rE54zDvgnghAoPopCgvtiqWNq3dU5y836S\",\n" +
+ " \"value\" : \"0.8696263565463045\"\n" +
+ " }\n" +
+ " }";
+
+ assertCanSerializeAndDeserialize(slot, json, AuctionSlot.class);
+ }
+}
\ No newline at end of file
diff --git a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/ledger/TicketObjectJsonTests.java b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/ledger/TicketObjectJsonTests.java
index 3d2ba457e..3fb25e4a2 100644
--- a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/ledger/TicketObjectJsonTests.java
+++ b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/ledger/TicketObjectJsonTests.java
@@ -20,6 +20,8 @@
* =========================LICENSE_END==================================
*/
+import static org.xrpl.xrpl4j.crypto.TestConstants.HASH_256;
+
import com.fasterxml.jackson.core.JsonProcessingException;
import com.google.common.primitives.UnsignedInteger;
import org.json.JSONException;
@@ -38,17 +40,19 @@ void testJson() throws JSONException, JsonProcessingException {
.previousTransactionId(Hash256.of("F19AD4577212D3BEACA0F75FE1BA1644F2E854D46E8D62E9C95D18E9708CBFB1"))
.previousTransactionLedgerSequence(UnsignedInteger.valueOf(4))
.ticketSequence(UnsignedInteger.valueOf(3))
+ .index(HASH_256)
.build();
- String json = "{\n" +
+ String json = String.format("{\n" +
" \"Account\" : \"rEhxGqkqPPSxQ3P25J66ft5TwpzV14k2de\",\n" +
" \"Flags\" : 0,\n" +
" \"LedgerEntryType\" : \"Ticket\",\n" +
" \"OwnerNode\" : \"0000000000000000\",\n" +
" \"PreviousTxnID\" : \"F19AD4577212D3BEACA0F75FE1BA1644F2E854D46E8D62E9C95D18E9708CBFB1\",\n" +
" \"PreviousTxnLgrSeq\" : 4,\n" +
+ " \"index\" : %s,\n" +
" \"TicketSequence\" : 3\n" +
- "}";
+ "}", HASH_256);
assertCanSerializeAndDeserialize(ticketObject, json);
}
diff --git a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/ledger/VoteEntryWrapperTest.java b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/ledger/VoteEntryWrapperTest.java
new file mode 100644
index 000000000..89ddcb4f6
--- /dev/null
+++ b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/ledger/VoteEntryWrapperTest.java
@@ -0,0 +1,33 @@
+package org.xrpl.xrpl4j.model.ledger;
+
+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;
+import org.xrpl.xrpl4j.model.transactions.Address;
+import org.xrpl.xrpl4j.model.transactions.TradingFee;
+import org.xrpl.xrpl4j.model.transactions.VoteWeight;
+
+class VoteEntryWrapperTest extends AbstractJsonTest {
+
+ @Test
+ void testJson() throws JSONException, JsonProcessingException {
+ VoteEntryWrapper voteWrapper = VoteEntryWrapper.of(
+ VoteEntry.builder()
+ .account(Address.of("rJVUeRqDFNs2xqA7ncVE6ZoAhPUoaJJSQm"))
+ .tradingFee(TradingFee.of(UnsignedInteger.valueOf(600)))
+ .voteWeight(VoteWeight.of(UnsignedInteger.valueOf(100000)))
+ .build()
+ );
+ String json = "{\n" +
+ " \"VoteEntry\" : {\n" +
+ " \"Account\" : \"rJVUeRqDFNs2xqA7ncVE6ZoAhPUoaJJSQm\",\n" +
+ " \"TradingFee\" : 600,\n" +
+ " \"VoteWeight\" : 100000\n" +
+ " }\n" +
+ " }";
+
+ assertCanSerializeAndDeserialize(voteWrapper, json, VoteEntryWrapper.class);
+ }
+}
\ No newline at end of file
diff --git a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/AccountSetTests.java b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/AccountSetTests.java
index cdf4cb094..5eb5a4479 100644
--- a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/AccountSetTests.java
+++ b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/AccountSetTests.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.
@@ -21,15 +21,27 @@
*/
import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.junit.jupiter.api.Assertions.assertThrows;
import com.google.common.primitives.UnsignedInteger;
import com.google.common.primitives.UnsignedLong;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
import org.xrpl.xrpl4j.model.flags.AccountSetTransactionFlags;
+import org.xrpl.xrpl4j.model.transactions.AccountSet.AccountSetFlag;
+
+import java.util.Arrays;
+import java.util.stream.Stream;
public class AccountSetTests {
+ public static Stream accountSetFlags() {
+ return Arrays.stream(AccountSetFlag.values()).map(Arguments::of);
+ }
+
@Test
public void simpleAccountSet() {
AccountSet accountSet = AccountSet.builder()
@@ -49,12 +61,209 @@ public void simpleAccountSet() {
assertThat(accountSet.sequence()).isEqualTo(UnsignedInteger.valueOf(5));
assertThat(accountSet.domain()).isNotEmpty().get().isEqualTo("6578616D706C652E636F6D");
assertThat(accountSet.setFlag()).isNotEmpty().get().isEqualTo(AccountSet.AccountSetFlag.ACCOUNT_TXN_ID);
+ assertThat(accountSet.setFlagRawValue()).isNotEmpty().get()
+ .isEqualTo(UnsignedInteger.valueOf(AccountSetFlag.ACCOUNT_TXN_ID.getValue()));
assertThat(accountSet.messageKey()).isNotEmpty().get()
.isEqualTo("03AB40A0490F9B7ED8DF29D246BF2D6269820A0EE7742ACDD457BEA7C7D0931EDB");
assertThat(accountSet.transferRate()).isNotEmpty().get().isEqualTo(UnsignedInteger.valueOf(1000000001));
assertThat(accountSet.flags().isEmpty()).isTrue();
}
+ @Test
+ void testWithEmptyClearFlagAndEmptyRawValue() {
+ AccountSet accountSet = AccountSet.builder()
+ .account(Address.of("rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn"))
+ .fee(XrpCurrencyAmount.ofDrops(12))
+ .sequence(UnsignedInteger.valueOf(5))
+ .build();
+
+ assertThat(accountSet.clearFlag()).isEmpty();
+ assertThat(accountSet.clearFlagRawValue()).isEmpty();
+ }
+
+ @ParameterizedTest
+ @MethodSource("accountSetFlags")
+ void testWithPresentClearFlagAndPresentRawValue(AccountSetFlag accountSetFlag) {
+ AccountSet accountSet = AccountSet.builder()
+ .account(Address.of("rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn"))
+ .fee(XrpCurrencyAmount.ofDrops(12))
+ .sequence(UnsignedInteger.valueOf(5))
+ .clearFlag(accountSetFlag)
+ .clearFlagRawValue(UnsignedInteger.valueOf(accountSetFlag.getValue()))
+ .build();
+
+ assertThat(accountSet.clearFlag()).isNotEmpty().get().isEqualTo(accountSetFlag);
+ assertThat(accountSet.clearFlagRawValue()).isNotEmpty().get()
+ .isEqualTo(UnsignedInteger.valueOf(accountSetFlag.getValue()));
+ }
+
+ @ParameterizedTest
+ @MethodSource("accountSetFlags")
+ void testWithPresentClearFlagAndPresentRawValueThrowsForMismatchedValues(AccountSetFlag accountSetFlag) {
+ assertThatThrownBy(
+ () -> AccountSet.builder()
+ .account(Address.of("rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn"))
+ .fee(XrpCurrencyAmount.ofDrops(12))
+ .sequence(UnsignedInteger.valueOf(5))
+ .clearFlag(accountSetFlag)
+ .clearFlagRawValue(UnsignedInteger.valueOf(accountSetFlag.getValue()).plus(UnsignedInteger.ONE))
+ .build()
+ ).isInstanceOf(IllegalStateException.class)
+ .hasMessage(String.format("clearFlag and clearFlagRawValue should be equivalent, but clearFlag's underlying " +
+ "value was %s and clearFlagRawValue was %s",
+ accountSetFlag.getValue(),
+ accountSetFlag.getValue() + 1
+ ));
+ }
+
+ @ParameterizedTest
+ @MethodSource("accountSetFlags")
+ void testWithPresentClearFlagAndEmptyRawValue(AccountSetFlag accountSetFlag) {
+ AccountSet accountSet = AccountSet.builder()
+ .account(Address.of("rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn"))
+ .fee(XrpCurrencyAmount.ofDrops(12))
+ .sequence(UnsignedInteger.valueOf(5))
+ .clearFlag(accountSetFlag)
+ .build();
+
+ assertThat(accountSet.clearFlag()).isNotEmpty().get().isEqualTo(accountSetFlag);
+ assertThat(accountSet.clearFlagRawValue()).isNotEmpty().get()
+ .isEqualTo(UnsignedInteger.valueOf(accountSetFlag.getValue()));
+ }
+
+ @ParameterizedTest
+ @MethodSource("accountSetFlags")
+ void testWithEmptyClearFlagAndPresentRawValue(AccountSetFlag accountSetFlag) {
+ AccountSet accountSet = AccountSet.builder()
+ .account(Address.of("rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn"))
+ .fee(XrpCurrencyAmount.ofDrops(12))
+ .sequence(UnsignedInteger.valueOf(5))
+ .clearFlagRawValue(UnsignedInteger.valueOf(accountSetFlag.getValue()))
+ .build();
+
+ assertThat(accountSet.clearFlag()).isNotEmpty().get().isEqualTo(accountSetFlag);
+ assertThat(accountSet.clearFlagRawValue()).isNotEmpty().get()
+ .isEqualTo(UnsignedInteger.valueOf(accountSetFlag.getValue()));
+ }
+
+ @Test
+ void testWithEmptyClearFlagAndPresentInvalidRawValue() {
+ AccountSet accountSet = AccountSet.builder()
+ .account(Address.of("rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn"))
+ .fee(XrpCurrencyAmount.ofDrops(12))
+ .sequence(UnsignedInteger.valueOf(5))
+ .clearFlagRawValue(UnsignedInteger.valueOf(AccountSetFlag.MAX_VALUE + 1))
+ .build();
+
+ assertThat(accountSet.clearFlag()).isEmpty();
+ assertThat(accountSet.clearFlagRawValue()).isNotEmpty().get()
+ .isEqualTo(UnsignedInteger.valueOf(AccountSetFlag.MAX_VALUE + 1));
+ }
+
+ //////////////////////////////////////
+
+ @Test
+ void testWithEmptySetFlagAndEmptyRawValue() {
+ AccountSet accountSet = AccountSet.builder()
+ .account(Address.of("rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn"))
+ .fee(XrpCurrencyAmount.ofDrops(12))
+ .sequence(UnsignedInteger.valueOf(5))
+ .build();
+
+ assertThat(accountSet.setFlag()).isEmpty();
+ assertThat(accountSet.setFlagRawValue()).isEmpty();
+ }
+
+ @ParameterizedTest
+ @MethodSource("accountSetFlags")
+ void testWithPresentSetFlagAndPresentRawValue(AccountSetFlag accountSetFlag) {
+ AccountSet accountSet = AccountSet.builder()
+ .account(Address.of("rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn"))
+ .fee(XrpCurrencyAmount.ofDrops(12))
+ .sequence(UnsignedInteger.valueOf(5))
+ .setFlag(accountSetFlag)
+ .setFlagRawValue(UnsignedInteger.valueOf(accountSetFlag.getValue()))
+ .build();
+
+ assertThat(accountSet.setFlag()).isNotEmpty().get().isEqualTo(accountSetFlag);
+ assertThat(accountSet.setFlagRawValue()).isNotEmpty().get()
+ .isEqualTo(UnsignedInteger.valueOf(accountSetFlag.getValue()));
+ }
+
+ @ParameterizedTest
+ @MethodSource("accountSetFlags")
+ void testWithPresentSetFlagAndPresentRawValueThrowsForMismatchedValues(AccountSetFlag accountSetFlag) {
+ assertThatThrownBy(
+ () -> AccountSet.builder()
+ .account(Address.of("rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn"))
+ .fee(XrpCurrencyAmount.ofDrops(12))
+ .sequence(UnsignedInteger.valueOf(5))
+ .setFlag(accountSetFlag)
+ .setFlagRawValue(UnsignedInteger.valueOf(accountSetFlag.getValue()).plus(UnsignedInteger.ONE))
+ .build()
+ ).isInstanceOf(IllegalStateException.class)
+ .hasMessage(String.format("setFlag and setFlagRawValue should be equivalent, but setFlag's underlying " +
+ "value was %s and setFlagRawValue was %s",
+ accountSetFlag.getValue(),
+ accountSetFlag.getValue() + 1
+ ));
+ }
+
+ @ParameterizedTest
+ @MethodSource("accountSetFlags")
+ void testWithPresentSetFlagAndEmptyRawValue(AccountSetFlag accountSetFlag) {
+ AccountSet accountSet = AccountSet.builder()
+ .account(Address.of("rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn"))
+ .fee(XrpCurrencyAmount.ofDrops(12))
+ .sequence(UnsignedInteger.valueOf(5))
+ .setFlag(accountSetFlag)
+ .build();
+
+ assertThat(accountSet.setFlag()).isNotEmpty().get().isEqualTo(accountSetFlag);
+ assertThat(accountSet.setFlagRawValue()).isNotEmpty().get()
+ .isEqualTo(UnsignedInteger.valueOf(accountSetFlag.getValue()));
+ }
+
+ @ParameterizedTest
+ @MethodSource("accountSetFlags")
+ void testWithEmptySetFlagAndPresentRawValue(AccountSetFlag accountSetFlag) {
+ AccountSet accountSet = AccountSet.builder()
+ .account(Address.of("rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn"))
+ .fee(XrpCurrencyAmount.ofDrops(12))
+ .sequence(UnsignedInteger.valueOf(5))
+ .setFlagRawValue(UnsignedInteger.valueOf(accountSetFlag.getValue()))
+ .build();
+
+ assertThat(accountSet.setFlag()).isNotEmpty().get().isEqualTo(accountSetFlag);
+ assertThat(accountSet.setFlagRawValue()).isNotEmpty().get()
+ .isEqualTo(UnsignedInteger.valueOf(accountSetFlag.getValue()));
+ }
+
+ @Test
+ void testWithEmptySetFlagAndPresentInvalidRawValue() {
+ AccountSet accountSet = AccountSet.builder()
+ .account(Address.of("rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn"))
+ .fee(XrpCurrencyAmount.ofDrops(12))
+ .sequence(UnsignedInteger.valueOf(5))
+ .setFlagRawValue(UnsignedInteger.valueOf(AccountSetFlag.MAX_VALUE + 1))
+ .build();
+
+ assertThat(accountSet.setFlag()).isEmpty();
+ assertThat(accountSet.setFlagRawValue()).isNotEmpty().get()
+ .isEqualTo(UnsignedInteger.valueOf(AccountSetFlag.MAX_VALUE + 1));
+
+ accountSet = AccountSet.builder()
+ .account(Address.of("rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn"))
+ .fee(XrpCurrencyAmount.ofDrops(12))
+ .sequence(UnsignedInteger.valueOf(5))
+ .setFlagRawValue(UnsignedInteger.MAX_VALUE.minus(UnsignedInteger.ONE))
+ .build();
+
+ assertThat(accountSet.setFlag()).isEmpty();
+ assertThat(accountSet.setFlagRawValue()).isNotEmpty().get()
+ .isEqualTo(UnsignedInteger.MAX_VALUE.minus(UnsignedInteger.ONE));
+ }
+
@Test
void accountSetWithSetFlagAndTransactionFlags() {
AccountSetTransactionFlags flags = AccountSetTransactionFlags.builder()
diff --git a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/AmmBidTest.java b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/AmmBidTest.java
new file mode 100644
index 000000000..91f51139b
--- /dev/null
+++ b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/AmmBidTest.java
@@ -0,0 +1,252 @@
+package org.xrpl.xrpl4j.model.transactions;
+
+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.crypto.keys.PublicKey;
+import org.xrpl.xrpl4j.model.AbstractJsonTest;
+import org.xrpl.xrpl4j.model.flags.TransactionFlags;
+import org.xrpl.xrpl4j.model.ledger.AuthAccount;
+import org.xrpl.xrpl4j.model.ledger.AuthAccountWrapper;
+import org.xrpl.xrpl4j.model.ledger.Issue;
+
+class AmmBidTest extends AbstractJsonTest {
+
+ @Test
+ void testJsonWithoutMinAndMax() throws JSONException, JsonProcessingException {
+ AmmBid bid = AmmBid.builder()
+ .account(Address.of("rJVUeRqDFNs2xqA7ncVE6ZoAhPUoaJJSQm"))
+ .signingPublicKey(
+ PublicKey.fromBase16EncodedPublicKey("02356E89059A75438887F9FEE2056A2890DB82A68353BE9C0C0C8F89C0018B37FC")
+ )
+ .asset(Issue.XRP)
+ .asset2(
+ Issue.builder()
+ .issuer(Address.of("rP9jPyP5kyvFRb6ZiRghAGw5u8SGAmU4bd"))
+ .currency("TST")
+ .build()
+ )
+ .addAuthAccounts(
+ AuthAccountWrapper.of(AuthAccount.of(Address.of("rMKXGCbJ5d8LbrqthdG46q3f969MVK2Qeg"))),
+ AuthAccountWrapper.of(AuthAccount.of(Address.of("rBepJuTLFJt3WmtLXYAxSjtBWAeQxVbncv")))
+ )
+ .fee(XrpCurrencyAmount.ofDrops(10))
+ .sequence(UnsignedInteger.valueOf(9))
+ .build();
+
+ String json = "{\n" +
+ " \"Account\" : \"rJVUeRqDFNs2xqA7ncVE6ZoAhPUoaJJSQm\",\n" +
+ " \"Asset\" : {\n" +
+ " \"currency\" : \"XRP\"\n" +
+ " },\n" +
+ " \"Asset2\" : {\n" +
+ " \"currency\" : \"TST\",\n" +
+ " \"issuer\" : \"rP9jPyP5kyvFRb6ZiRghAGw5u8SGAmU4bd\"\n" +
+ " },\n" +
+ " \"AuthAccounts\" : [\n" +
+ " {\n" +
+ " \"AuthAccount\" : {\n" +
+ " \"Account\" : \"rMKXGCbJ5d8LbrqthdG46q3f969MVK2Qeg\"\n" +
+ " }\n" +
+ " },\n" +
+ " {\n" +
+ " \"AuthAccount\" : {\n" +
+ " \"Account\" : \"rBepJuTLFJt3WmtLXYAxSjtBWAeQxVbncv\"\n" +
+ " }\n" +
+ " }\n" +
+ " ],\n" +
+ " \"Fee\" : \"10\",\n" +
+ " \"Sequence\" : 9,\n" +
+ " \"SigningPubKey\" : \"02356E89059A75438887F9FEE2056A2890DB82A68353BE9C0C0C8F89C0018B37FC\",\n" +
+ " \"TransactionType\" : \"AMMBid\"\n" +
+ "}";
+
+ assertCanSerializeAndDeserialize(bid, json);
+ }
+
+ @Test
+ void testJsonWithUnsetFlags() throws JSONException, JsonProcessingException {
+ AmmBid bid = AmmBid.builder()
+ .account(Address.of("rJVUeRqDFNs2xqA7ncVE6ZoAhPUoaJJSQm"))
+ .signingPublicKey(
+ PublicKey.fromBase16EncodedPublicKey("02356E89059A75438887F9FEE2056A2890DB82A68353BE9C0C0C8F89C0018B37FC")
+ )
+ .asset(Issue.XRP)
+ .asset2(
+ Issue.builder()
+ .issuer(Address.of("rP9jPyP5kyvFRb6ZiRghAGw5u8SGAmU4bd"))
+ .currency("TST")
+ .build()
+ )
+ .addAuthAccounts(
+ AuthAccountWrapper.of(AuthAccount.of(Address.of("rMKXGCbJ5d8LbrqthdG46q3f969MVK2Qeg"))),
+ AuthAccountWrapper.of(AuthAccount.of(Address.of("rBepJuTLFJt3WmtLXYAxSjtBWAeQxVbncv")))
+ )
+ .fee(XrpCurrencyAmount.ofDrops(10))
+ .sequence(UnsignedInteger.valueOf(9))
+ .flags(TransactionFlags.UNSET)
+ .build();
+
+ String json = "{\n" +
+ " \"Account\" : \"rJVUeRqDFNs2xqA7ncVE6ZoAhPUoaJJSQm\",\n" +
+ " \"Asset\" : {\n" +
+ " \"currency\" : \"XRP\"\n" +
+ " },\n" +
+ " \"Asset2\" : {\n" +
+ " \"currency\" : \"TST\",\n" +
+ " \"issuer\" : \"rP9jPyP5kyvFRb6ZiRghAGw5u8SGAmU4bd\"\n" +
+ " },\n" +
+ " \"AuthAccounts\" : [\n" +
+ " {\n" +
+ " \"AuthAccount\" : {\n" +
+ " \"Account\" : \"rMKXGCbJ5d8LbrqthdG46q3f969MVK2Qeg\"\n" +
+ " }\n" +
+ " },\n" +
+ " {\n" +
+ " \"AuthAccount\" : {\n" +
+ " \"Account\" : \"rBepJuTLFJt3WmtLXYAxSjtBWAeQxVbncv\"\n" +
+ " }\n" +
+ " }\n" +
+ " ],\n" +
+ " \"Fee\" : \"10\",\n" +
+ " \"Flags\" : 0,\n" +
+ " \"Sequence\" : 9,\n" +
+ " \"SigningPubKey\" : \"02356E89059A75438887F9FEE2056A2890DB82A68353BE9C0C0C8F89C0018B37FC\",\n" +
+ " \"TransactionType\" : \"AMMBid\"\n" +
+ "}";
+
+ assertCanSerializeAndDeserialize(bid, json);
+ }
+
+ @Test
+ void testJsonWithNonZeroFlags() throws JSONException, JsonProcessingException {
+ AmmBid bid = AmmBid.builder()
+ .account(Address.of("rJVUeRqDFNs2xqA7ncVE6ZoAhPUoaJJSQm"))
+ .signingPublicKey(
+ PublicKey.fromBase16EncodedPublicKey("02356E89059A75438887F9FEE2056A2890DB82A68353BE9C0C0C8F89C0018B37FC")
+ )
+ .asset(Issue.XRP)
+ .asset2(
+ Issue.builder()
+ .issuer(Address.of("rP9jPyP5kyvFRb6ZiRghAGw5u8SGAmU4bd"))
+ .currency("TST")
+ .build()
+ )
+ .addAuthAccounts(
+ AuthAccountWrapper.of(AuthAccount.of(Address.of("rMKXGCbJ5d8LbrqthdG46q3f969MVK2Qeg"))),
+ AuthAccountWrapper.of(AuthAccount.of(Address.of("rBepJuTLFJt3WmtLXYAxSjtBWAeQxVbncv")))
+ )
+ .fee(XrpCurrencyAmount.ofDrops(10))
+ .sequence(UnsignedInteger.valueOf(9))
+ .flags(TransactionFlags.FULLY_CANONICAL_SIG)
+ .build();
+
+ String json = "{\n" +
+ " \"Account\" : \"rJVUeRqDFNs2xqA7ncVE6ZoAhPUoaJJSQm\",\n" +
+ " \"Asset\" : {\n" +
+ " \"currency\" : \"XRP\"\n" +
+ " },\n" +
+ " \"Asset2\" : {\n" +
+ " \"currency\" : \"TST\",\n" +
+ " \"issuer\" : \"rP9jPyP5kyvFRb6ZiRghAGw5u8SGAmU4bd\"\n" +
+ " },\n" +
+ " \"AuthAccounts\" : [\n" +
+ " {\n" +
+ " \"AuthAccount\" : {\n" +
+ " \"Account\" : \"rMKXGCbJ5d8LbrqthdG46q3f969MVK2Qeg\"\n" +
+ " }\n" +
+ " },\n" +
+ " {\n" +
+ " \"AuthAccount\" : {\n" +
+ " \"Account\" : \"rBepJuTLFJt3WmtLXYAxSjtBWAeQxVbncv\"\n" +
+ " }\n" +
+ " }\n" +
+ " ],\n" +
+ " \"Fee\" : \"10\",\n" +
+ " \"Flags\" : 0,\n" +
+ " \"Sequence\" : 9,\n" +
+ " \"Flags\" : 2147483648,\n" +
+ " \"SigningPubKey\" : \"02356E89059A75438887F9FEE2056A2890DB82A68353BE9C0C0C8F89C0018B37FC\",\n" +
+ " \"TransactionType\" : \"AMMBid\"\n" +
+ "}";
+
+ assertCanSerializeAndDeserialize(bid, json);
+ }
+
+ @Test
+ void testJsonWithMinAndMax() throws JSONException, JsonProcessingException {
+ AmmBid bid = AmmBid.builder()
+ .account(Address.of("rJVUeRqDFNs2xqA7ncVE6ZoAhPUoaJJSQm"))
+ .signingPublicKey(
+ PublicKey.fromBase16EncodedPublicKey("02356E89059A75438887F9FEE2056A2890DB82A68353BE9C0C0C8F89C0018B37FC")
+ )
+ .asset(Issue.XRP)
+ .asset2(
+ Issue.builder()
+ .issuer(Address.of("rP9jPyP5kyvFRb6ZiRghAGw5u8SGAmU4bd"))
+ .currency("TST")
+ .build()
+ )
+ .addAuthAccounts(
+ AuthAccountWrapper.of(AuthAccount.of(Address.of("rMKXGCbJ5d8LbrqthdG46q3f969MVK2Qeg"))),
+ AuthAccountWrapper.of(AuthAccount.of(Address.of("rBepJuTLFJt3WmtLXYAxSjtBWAeQxVbncv")))
+ )
+ .bidMax(
+ IssuedCurrencyAmount.builder()
+ .currency("039C99CD9AB0B70B32ECDA51EAAE471625608EA2")
+ .issuer(Address.of("rE54zDvgnghAoPopCgvtiqWNq3dU5y836S"))
+ .value("100")
+ .build()
+ )
+ .bidMin(
+ IssuedCurrencyAmount.builder()
+ .currency("039C99CD9AB0B70B32ECDA51EAAE471625608EA2")
+ .issuer(Address.of("rE54zDvgnghAoPopCgvtiqWNq3dU5y836S"))
+ .value("100")
+ .build()
+ )
+ .fee(XrpCurrencyAmount.ofDrops(10))
+ .sequence(UnsignedInteger.valueOf(9))
+ .build();
+
+ String json = "{\n" +
+ " \"Account\" : \"rJVUeRqDFNs2xqA7ncVE6ZoAhPUoaJJSQm\",\n" +
+ " \"Asset\" : {\n" +
+ " \"currency\" : \"XRP\"\n" +
+ " },\n" +
+ " \"Asset2\" : {\n" +
+ " \"currency\" : \"TST\",\n" +
+ " \"issuer\" : \"rP9jPyP5kyvFRb6ZiRghAGw5u8SGAmU4bd\"\n" +
+ " },\n" +
+ " \"AuthAccounts\" : [\n" +
+ " {\n" +
+ " \"AuthAccount\" : {\n" +
+ " \"Account\" : \"rMKXGCbJ5d8LbrqthdG46q3f969MVK2Qeg\"\n" +
+ " }\n" +
+ " },\n" +
+ " {\n" +
+ " \"AuthAccount\" : {\n" +
+ " \"Account\" : \"rBepJuTLFJt3WmtLXYAxSjtBWAeQxVbncv\"\n" +
+ " }\n" +
+ " }\n" +
+ " ],\n" +
+ " \"BidMax\" : {\n" +
+ " \"currency\" : \"039C99CD9AB0B70B32ECDA51EAAE471625608EA2\",\n" +
+ " \"issuer\" : \"rE54zDvgnghAoPopCgvtiqWNq3dU5y836S\",\n" +
+ " \"value\" : \"100\"\n" +
+ " },\n" +
+ " \"BidMin\" : {\n" +
+ " \"currency\" : \"039C99CD9AB0B70B32ECDA51EAAE471625608EA2\",\n" +
+ " \"issuer\" : \"rE54zDvgnghAoPopCgvtiqWNq3dU5y836S\",\n" +
+ " \"value\" : \"100\"\n" +
+ " },\n" +
+ " \"Fee\" : \"10\",\n" +
+ " \"Sequence\" : 9,\n" +
+ " \"SigningPubKey\" : \"02356E89059A75438887F9FEE2056A2890DB82A68353BE9C0C0C8F89C0018B37FC\",\n" +
+ " \"TransactionType\" : \"AMMBid\"\n" +
+ "}";
+
+ assertCanSerializeAndDeserialize(bid, json);
+ }
+}
\ No newline at end of file
diff --git a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/AmmCreateTest.java b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/AmmCreateTest.java
new file mode 100644
index 000000000..275582f17
--- /dev/null
+++ b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/AmmCreateTest.java
@@ -0,0 +1,130 @@
+package org.xrpl.xrpl4j.model.transactions;
+
+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.crypto.keys.PublicKey;
+import org.xrpl.xrpl4j.model.AbstractJsonTest;
+import org.xrpl.xrpl4j.model.flags.TransactionFlags;
+
+class AmmCreateTest extends AbstractJsonTest {
+
+ @Test
+ void testJson() throws JSONException, JsonProcessingException {
+ AmmCreate ammCreate = AmmCreate.builder()
+ .account(Address.of("rJVUeRqDFNs2xqA7ncVE6ZoAhPUoaJJSQm"))
+ .amount(
+ IssuedCurrencyAmount.builder()
+ .currency("TST")
+ .issuer(Address.of("rP9jPyP5kyvFRb6ZiRghAGw5u8SGAmU4bd"))
+ .value("25")
+ .build()
+ )
+ .amount2(XrpCurrencyAmount.ofDrops(250000000))
+ .fee(XrpCurrencyAmount.ofDrops(10))
+ .sequence(UnsignedInteger.valueOf(6))
+ .tradingFee(TradingFee.of(UnsignedInteger.valueOf(500)))
+ .signingPublicKey(
+ PublicKey.fromBase16EncodedPublicKey("02356E89059A75438887F9FEE2056A2890DB82A68353BE9C0C0C8F89C0018B37FC")
+ )
+ .build();
+
+ String json = "{\n" +
+ " \"Account\" : \"rJVUeRqDFNs2xqA7ncVE6ZoAhPUoaJJSQm\",\n" +
+ " \"Amount\" : {\n" +
+ " \"currency\" : \"TST\",\n" +
+ " \"issuer\" : \"rP9jPyP5kyvFRb6ZiRghAGw5u8SGAmU4bd\",\n" +
+ " \"value\" : \"25\"\n" +
+ " },\n" +
+ " \"Amount2\" : \"250000000\",\n" +
+ " \"Fee\" : \"10\",\n" +
+ " \"Sequence\" : 6,\n" +
+ " \"TradingFee\" : 500,\n" +
+ " \"SigningPubKey\" : \"02356E89059A75438887F9FEE2056A2890DB82A68353BE9C0C0C8F89C0018B37FC\",\n" +
+ " \"TransactionType\" : \"AMMCreate\"\n" +
+ "}";
+
+ assertCanSerializeAndDeserialize(ammCreate, json);
+ }
+
+ @Test
+ void testJsonWithUnsetFlags() throws JSONException, JsonProcessingException {
+ AmmCreate ammCreate = AmmCreate.builder()
+ .account(Address.of("rJVUeRqDFNs2xqA7ncVE6ZoAhPUoaJJSQm"))
+ .amount(
+ IssuedCurrencyAmount.builder()
+ .currency("TST")
+ .issuer(Address.of("rP9jPyP5kyvFRb6ZiRghAGw5u8SGAmU4bd"))
+ .value("25")
+ .build()
+ )
+ .amount2(XrpCurrencyAmount.ofDrops(250000000))
+ .fee(XrpCurrencyAmount.ofDrops(10))
+ .sequence(UnsignedInteger.valueOf(6))
+ .tradingFee(TradingFee.of(UnsignedInteger.valueOf(500)))
+ .signingPublicKey(
+ PublicKey.fromBase16EncodedPublicKey("02356E89059A75438887F9FEE2056A2890DB82A68353BE9C0C0C8F89C0018B37FC")
+ )
+ .flags(TransactionFlags.UNSET)
+ .build();
+
+ String json = "{\n" +
+ " \"Account\" : \"rJVUeRqDFNs2xqA7ncVE6ZoAhPUoaJJSQm\",\n" +
+ " \"Amount\" : {\n" +
+ " \"currency\" : \"TST\",\n" +
+ " \"issuer\" : \"rP9jPyP5kyvFRb6ZiRghAGw5u8SGAmU4bd\",\n" +
+ " \"value\" : \"25\"\n" +
+ " },\n" +
+ " \"Amount2\" : \"250000000\",\n" +
+ " \"Fee\" : \"10\",\n" +
+ " \"Sequence\" : 6,\n" +
+ " \"TradingFee\" : 500,\n" +
+ " \"Flags\" : 0,\n" +
+ " \"SigningPubKey\" : \"02356E89059A75438887F9FEE2056A2890DB82A68353BE9C0C0C8F89C0018B37FC\",\n" +
+ " \"TransactionType\" : \"AMMCreate\"\n" +
+ "}";
+
+ assertCanSerializeAndDeserialize(ammCreate, json);
+ }
+
+ @Test
+ void testJsonWithNonZeroFlags() throws JSONException, JsonProcessingException {
+ AmmCreate ammCreate = AmmCreate.builder()
+ .account(Address.of("rJVUeRqDFNs2xqA7ncVE6ZoAhPUoaJJSQm"))
+ .amount(
+ IssuedCurrencyAmount.builder()
+ .currency("TST")
+ .issuer(Address.of("rP9jPyP5kyvFRb6ZiRghAGw5u8SGAmU4bd"))
+ .value("25")
+ .build()
+ )
+ .amount2(XrpCurrencyAmount.ofDrops(250000000))
+ .fee(XrpCurrencyAmount.ofDrops(10))
+ .sequence(UnsignedInteger.valueOf(6))
+ .tradingFee(TradingFee.of(UnsignedInteger.valueOf(500)))
+ .signingPublicKey(
+ PublicKey.fromBase16EncodedPublicKey("02356E89059A75438887F9FEE2056A2890DB82A68353BE9C0C0C8F89C0018B37FC")
+ )
+ .flags(TransactionFlags.FULLY_CANONICAL_SIG)
+ .build();
+
+ String json = "{\n" +
+ " \"Account\" : \"rJVUeRqDFNs2xqA7ncVE6ZoAhPUoaJJSQm\",\n" +
+ " \"Amount\" : {\n" +
+ " \"currency\" : \"TST\",\n" +
+ " \"issuer\" : \"rP9jPyP5kyvFRb6ZiRghAGw5u8SGAmU4bd\",\n" +
+ " \"value\" : \"25\"\n" +
+ " },\n" +
+ " \"Amount2\" : \"250000000\",\n" +
+ " \"Fee\" : \"10\",\n" +
+ " \"Sequence\" : 6,\n" +
+ " \"TradingFee\" : 500,\n" +
+ " \"Flags\" : 2147483648,\n" +
+ " \"SigningPubKey\" : \"02356E89059A75438887F9FEE2056A2890DB82A68353BE9C0C0C8F89C0018B37FC\",\n" +
+ " \"TransactionType\" : \"AMMCreate\"\n" +
+ "}";
+
+ assertCanSerializeAndDeserialize(ammCreate, json);
+ }
+}
\ No newline at end of file
diff --git a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/AmmDeleteTest.java b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/AmmDeleteTest.java
new file mode 100644
index 000000000..2889e572e
--- /dev/null
+++ b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/AmmDeleteTest.java
@@ -0,0 +1,49 @@
+package org.xrpl.xrpl4j.model.transactions;
+
+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.crypto.keys.PublicKey;
+import org.xrpl.xrpl4j.model.AbstractJsonTest;
+import org.xrpl.xrpl4j.model.flags.TransactionFlags;
+import org.xrpl.xrpl4j.model.ledger.Issue;
+
+class AmmDeleteTest extends AbstractJsonTest {
+
+ @Test
+ void testJson() throws JSONException, JsonProcessingException {
+ AmmDelete ammDelete = AmmDelete.builder()
+ .asset(Issue.XRP)
+ .asset2(
+ Issue.builder()
+ .issuer(Address.of("rP9jPyP5kyvFRb6ZiRghAGw5u8SGAmU4bd"))
+ .currency("TST")
+ .build()
+ )
+ .account(Address.of("rJVUeRqDFNs2xqA7ncVE6ZoAhPUoaJJSQm"))
+ .fee(XrpCurrencyAmount.ofDrops(10))
+ .sequence(UnsignedInteger.valueOf(9))
+ .signingPublicKey(PublicKey.fromBase16EncodedPublicKey(
+ "EDD299D60BCE7980F6082945B5597FFFD35223F1950673BFA4D4AED6FDE5097156"
+ ))
+ .build();
+
+ String json = "{\n" +
+ " \"Account\" : \"rJVUeRqDFNs2xqA7ncVE6ZoAhPUoaJJSQm\",\n" +
+ " \"Asset\" : {\n" +
+ " \"currency\" : \"XRP\"\n" +
+ " },\n" +
+ " \"Asset2\" : {\n" +
+ " \"currency\" : \"TST\",\n" +
+ " \"issuer\" : \"rP9jPyP5kyvFRb6ZiRghAGw5u8SGAmU4bd\"\n" +
+ " },\n" +
+ " \"Fee\" : \"10\",\n" +
+ " \"Sequence\" : 9,\n" +
+ " \"SigningPubKey\" : \"EDD299D60BCE7980F6082945B5597FFFD35223F1950673BFA4D4AED6FDE5097156\",\n" +
+ " \"TransactionType\" : \"AMMDelete\"\n" +
+ "}";
+
+ assertCanSerializeAndDeserialize(ammDelete, json);
+ }
+}
\ No newline at end of file
diff --git a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/AmmDepositTest.java b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/AmmDepositTest.java
new file mode 100644
index 000000000..9f4c5c1f9
--- /dev/null
+++ b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/AmmDepositTest.java
@@ -0,0 +1,347 @@
+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 org.json.JSONException;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.xrpl.xrpl4j.crypto.keys.PublicKey;
+import org.xrpl.xrpl4j.model.AbstractJsonTest;
+import org.xrpl.xrpl4j.model.flags.AmmDepositFlags;
+import org.xrpl.xrpl4j.model.ledger.Issue;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Stream;
+
+class AmmDepositTest extends AbstractJsonTest {
+
+ @Test
+ void constructLpTokenDepositAndTestJson() throws JSONException, JsonProcessingException {
+ AmmDeposit deposit = AmmDeposit.builder()
+ .account(Address.of("rJVUeRqDFNs2xqA7ncVE6ZoAhPUoaJJSQm"))
+ .fee(XrpCurrencyAmount.ofDrops(10))
+ .signingPublicKey(
+ PublicKey.fromBase16EncodedPublicKey("02356E89059A75438887F9FEE2056A2890DB82A68353BE9C0C0C8F89C0018B37FC")
+ )
+ .flags(AmmDepositFlags.LP_TOKEN)
+ .asset(Issue.XRP)
+ .asset2(
+ Issue.builder()
+ .issuer(Address.of("rP9jPyP5kyvFRb6ZiRghAGw5u8SGAmU4bd"))
+ .currency("TST")
+ .build()
+ )
+ .lpTokenOut(
+ IssuedCurrencyAmount.builder()
+ .currency("039C99CD9AB0B70B32ECDA51EAAE471625608EA2")
+ .issuer(Address.of("rE54zDvgnghAoPopCgvtiqWNq3dU5y836S"))
+ .value("100")
+ .build()
+ ).build();
+
+ assertThat(deposit.flags()).isEqualTo(AmmDepositFlags.LP_TOKEN);
+
+ String json = "{\n" +
+ " \"Account\" : \"" + deposit.account() + "\",\n" +
+ " \"LPTokenOut\" : {\n" +
+ " \"currency\" : \"039C99CD9AB0B70B32ECDA51EAAE471625608EA2\",\n" +
+ " \"issuer\" : \"rE54zDvgnghAoPopCgvtiqWNq3dU5y836S\",\n" +
+ " \"value\" : \"100\"\n" +
+ " },\n" +
+ " \"Asset2\" : {\n" +
+ " \"currency\" : \"TST\",\n" +
+ " \"issuer\" : \"rP9jPyP5kyvFRb6ZiRghAGw5u8SGAmU4bd\"\n" +
+ " },\n" +
+ " \"Asset\" : {\n" +
+ " \"currency\" : \"XRP\"\n" +
+ " },\n" +
+ " \"Fee\" : \"10\",\n" +
+ " \"Flags\" : " + AmmDepositFlags.LP_TOKEN + ",\n" +
+ " \"Sequence\" : 0,\n" +
+ " \"SigningPubKey\" : \"02356E89059A75438887F9FEE2056A2890DB82A68353BE9C0C0C8F89C0018B37FC\",\n" +
+ " \"TransactionType\" : \"AMMDeposit\"\n" +
+ "}";
+
+ assertCanSerializeAndDeserialize(deposit, json);
+ }
+
+ @Test
+ void constructTwoAssetDepositAndTestJson() throws JSONException, JsonProcessingException {
+ AmmDeposit deposit = AmmDeposit.builder()
+ .account(Address.of("rJVUeRqDFNs2xqA7ncVE6ZoAhPUoaJJSQm"))
+ .fee(XrpCurrencyAmount.ofDrops(10))
+ .signingPublicKey(
+ PublicKey.fromBase16EncodedPublicKey("02356E89059A75438887F9FEE2056A2890DB82A68353BE9C0C0C8F89C0018B37FC")
+ )
+ .flags(AmmDepositFlags.TWO_ASSET)
+ .asset(Issue.XRP)
+ .asset2(
+ Issue.builder()
+ .issuer(Address.of("rP9jPyP5kyvFRb6ZiRghAGw5u8SGAmU4bd"))
+ .currency("TST")
+ .build()
+ )
+ .amount(
+ IssuedCurrencyAmount.builder()
+ .currency("039C99CD9AB0B70B32ECDA51EAAE471625608EA2")
+ .issuer(Address.of("rE54zDvgnghAoPopCgvtiqWNq3dU5y836S"))
+ .value("100")
+ .build()
+ )
+ .amount2(XrpCurrencyAmount.ofDrops(10))
+ .build();
+
+ assertThat(deposit.flags()).isEqualTo(AmmDepositFlags.TWO_ASSET);
+
+ String json = "{\n" +
+ " \"Account\" : \"" + deposit.account() + "\",\n" +
+ " \"Amount\" : {\n" +
+ " \"currency\" : \"039C99CD9AB0B70B32ECDA51EAAE471625608EA2\",\n" +
+ " \"issuer\" : \"rE54zDvgnghAoPopCgvtiqWNq3dU5y836S\",\n" +
+ " \"value\" : \"100\"\n" +
+ " },\n" +
+ " \"Amount2\" : \"10\"," +
+ " \"Asset2\" : {\n" +
+ " \"currency\" : \"TST\",\n" +
+ " \"issuer\" : \"rP9jPyP5kyvFRb6ZiRghAGw5u8SGAmU4bd\"\n" +
+ " },\n" +
+ " \"Asset\" : {\n" +
+ " \"currency\" : \"XRP\"\n" +
+ " },\n" +
+ " \"Fee\" : \"10\",\n" +
+ " \"Flags\" : " + AmmDepositFlags.TWO_ASSET + ",\n" +
+ " \"Sequence\" : 0,\n" +
+ " \"SigningPubKey\" : \"02356E89059A75438887F9FEE2056A2890DB82A68353BE9C0C0C8F89C0018B37FC\",\n" +
+ " \"TransactionType\" : \"AMMDeposit\"\n" +
+ "}";
+
+ assertCanSerializeAndDeserialize(deposit, json);
+ }
+
+ @Test
+ void constructSingleAssetDepositAndTestJson() throws JSONException, JsonProcessingException {
+ AmmDeposit deposit = AmmDeposit.builder()
+ .account(Address.of("rJVUeRqDFNs2xqA7ncVE6ZoAhPUoaJJSQm"))
+ .fee(XrpCurrencyAmount.ofDrops(10))
+ .flags(AmmDepositFlags.SINGLE_ASSET)
+ .asset(Issue.XRP)
+ .asset2(
+ Issue.builder()
+ .issuer(Address.of("rP9jPyP5kyvFRb6ZiRghAGw5u8SGAmU4bd"))
+ .currency("TST")
+ .build()
+ )
+ .amount(
+ IssuedCurrencyAmount.builder()
+ .currency("039C99CD9AB0B70B32ECDA51EAAE471625608EA2")
+ .issuer(Address.of("rE54zDvgnghAoPopCgvtiqWNq3dU5y836S"))
+ .value("100")
+ .build()
+ )
+ .signingPublicKey(
+ PublicKey.fromBase16EncodedPublicKey("02356E89059A75438887F9FEE2056A2890DB82A68353BE9C0C0C8F89C0018B37FC")
+ )
+ .build();
+
+ assertThat(deposit.flags()).isEqualTo(AmmDepositFlags.SINGLE_ASSET);
+
+ String json = "{\n" +
+ " \"Account\" : \"" + deposit.account() + "\",\n" +
+ " \"Amount\" : {\n" +
+ " \"currency\" : \"039C99CD9AB0B70B32ECDA51EAAE471625608EA2\",\n" +
+ " \"issuer\" : \"rE54zDvgnghAoPopCgvtiqWNq3dU5y836S\",\n" +
+ " \"value\" : \"100\"\n" +
+ " },\n" +
+ " \"Asset2\" : {\n" +
+ " \"currency\" : \"TST\",\n" +
+ " \"issuer\" : \"rP9jPyP5kyvFRb6ZiRghAGw5u8SGAmU4bd\"\n" +
+ " },\n" +
+ " \"Asset\" : {\n" +
+ " \"currency\" : \"XRP\"\n" +
+ " },\n" +
+ " \"Fee\" : \"10\",\n" +
+ " \"Flags\" : " + AmmDepositFlags.SINGLE_ASSET + ",\n" +
+ " \"Sequence\" : 0,\n" +
+ " \"SigningPubKey\" : \"02356E89059A75438887F9FEE2056A2890DB82A68353BE9C0C0C8F89C0018B37FC\",\n" +
+ " \"TransactionType\" : \"AMMDeposit\"\n" +
+ "}";
+
+ assertCanSerializeAndDeserialize(deposit, json);
+ }
+
+ @Test
+ void constructOneAssetLpTokenDepositAndTestJson() throws JSONException, JsonProcessingException {
+ AmmDeposit deposit = AmmDeposit.builder()
+ .account(Address.of("rJVUeRqDFNs2xqA7ncVE6ZoAhPUoaJJSQm"))
+ .fee(XrpCurrencyAmount.ofDrops(10))
+ .signingPublicKey(
+ PublicKey.fromBase16EncodedPublicKey("02356E89059A75438887F9FEE2056A2890DB82A68353BE9C0C0C8F89C0018B37FC")
+ )
+ .flags(AmmDepositFlags.ONE_ASSET_LP_TOKEN)
+ .asset(Issue.XRP)
+ .asset2(
+ Issue.builder()
+ .issuer(Address.of("rP9jPyP5kyvFRb6ZiRghAGw5u8SGAmU4bd"))
+ .currency("TST")
+ .build()
+ )
+ .amount(
+ IssuedCurrencyAmount.builder()
+ .currency("039C99CD9AB0B70B32ECDA51EAAE471625608EA2")
+ .issuer(Address.of("rE54zDvgnghAoPopCgvtiqWNq3dU5y836S"))
+ .value("100")
+ .build()
+ )
+ .lpTokenOut(
+ IssuedCurrencyAmount.builder()
+ .currency("039C99CD9AB0B70B32ECDA51EAAE471625608EA2")
+ .issuer(Address.of("rE54zDvgnghAoPopCgvtiqWNq3dU5y836S"))
+ .value("100")
+ .build()
+ )
+ .build();
+
+ assertThat(deposit.flags()).isEqualTo(AmmDepositFlags.ONE_ASSET_LP_TOKEN);
+
+ String json = "{\n" +
+ " \"Account\" : \"" + deposit.account() + "\",\n" +
+ " \"Amount\" : {\n" +
+ " \"currency\" : \"039C99CD9AB0B70B32ECDA51EAAE471625608EA2\",\n" +
+ " \"issuer\" : \"rE54zDvgnghAoPopCgvtiqWNq3dU5y836S\",\n" +
+ " \"value\" : \"100\"\n" +
+ " },\n" +
+ " \"LPTokenOut\" : {\n" +
+ " \"currency\" : \"039C99CD9AB0B70B32ECDA51EAAE471625608EA2\",\n" +
+ " \"issuer\" : \"rE54zDvgnghAoPopCgvtiqWNq3dU5y836S\",\n" +
+ " \"value\" : \"100\"\n" +
+ " },\n" +
+ " \"Asset2\" : {\n" +
+ " \"currency\" : \"TST\",\n" +
+ " \"issuer\" : \"rP9jPyP5kyvFRb6ZiRghAGw5u8SGAmU4bd\"\n" +
+ " },\n" +
+ " \"Asset\" : {\n" +
+ " \"currency\" : \"XRP\"\n" +
+ " },\n" +
+ " \"Fee\" : \"10\",\n" +
+ " \"Flags\" : " + AmmDepositFlags.ONE_ASSET_LP_TOKEN + ",\n" +
+ " \"Sequence\" : 0,\n" +
+ " \"SigningPubKey\" : \"02356E89059A75438887F9FEE2056A2890DB82A68353BE9C0C0C8F89C0018B37FC\",\n" +
+ " \"TransactionType\" : \"AMMDeposit\"\n" +
+ "}";
+
+ assertCanSerializeAndDeserialize(deposit, json);
+ }
+
+ @Test
+ void constructLimitLpTokenDepositAndTestJson() throws JSONException, JsonProcessingException {
+ AmmDeposit deposit = AmmDeposit.builder()
+ .account(Address.of("rJVUeRqDFNs2xqA7ncVE6ZoAhPUoaJJSQm"))
+ .fee(XrpCurrencyAmount.ofDrops(10))
+ .signingPublicKey(
+ PublicKey.fromBase16EncodedPublicKey("02356E89059A75438887F9FEE2056A2890DB82A68353BE9C0C0C8F89C0018B37FC")
+ )
+ .flags(AmmDepositFlags.LIMIT_LP_TOKEN)
+ .asset(Issue.XRP)
+ .asset2(
+ Issue.builder()
+ .issuer(Address.of("rP9jPyP5kyvFRb6ZiRghAGw5u8SGAmU4bd"))
+ .currency("TST")
+ .build()
+ )
+ .amount(
+ IssuedCurrencyAmount.builder()
+ .currency("039C99CD9AB0B70B32ECDA51EAAE471625608EA2")
+ .issuer(Address.of("rE54zDvgnghAoPopCgvtiqWNq3dU5y836S"))
+ .value("100")
+ .build()
+ )
+ .effectivePrice(XrpCurrencyAmount.ofDrops(10))
+ .build();
+
+ assertThat(deposit.flags()).isEqualTo(AmmDepositFlags.LIMIT_LP_TOKEN);
+
+ String json = "{\n" +
+ " \"Account\" : \"" + deposit.account() + "\",\n" +
+ " \"Amount\" : {\n" +
+ " \"currency\" : \"039C99CD9AB0B70B32ECDA51EAAE471625608EA2\",\n" +
+ " \"issuer\" : \"rE54zDvgnghAoPopCgvtiqWNq3dU5y836S\",\n" +
+ " \"value\" : \"100\"\n" +
+ " },\n" +
+ " \"EPrice\" : \"10\",\n" +
+ " \"Asset2\" : {\n" +
+ " \"currency\" : \"TST\",\n" +
+ " \"issuer\" : \"rP9jPyP5kyvFRb6ZiRghAGw5u8SGAmU4bd\"\n" +
+ " },\n" +
+ " \"Asset\" : {\n" +
+ " \"currency\" : \"XRP\"\n" +
+ " },\n" +
+ " \"Fee\" : \"10\",\n" +
+ " \"Flags\" : " + AmmDepositFlags.LIMIT_LP_TOKEN + ",\n" +
+ " \"Sequence\" : 0,\n" +
+ " \"SigningPubKey\" : \"02356E89059A75438887F9FEE2056A2890DB82A68353BE9C0C0C8F89C0018B37FC\",\n" +
+ " \"TransactionType\" : \"AMMDeposit\"\n" +
+ "}";
+
+ assertCanSerializeAndDeserialize(deposit, json);
+ }
+
+ @Test
+ void constructTwoAssetIfEmptyDepositTestJson() throws JSONException, JsonProcessingException {
+ AmmDeposit deposit = AmmDeposit.builder()
+ .account(Address.of("rJVUeRqDFNs2xqA7ncVE6ZoAhPUoaJJSQm"))
+ .fee(XrpCurrencyAmount.ofDrops(10))
+ .signingPublicKey(
+ PublicKey.fromBase16EncodedPublicKey("02356E89059A75438887F9FEE2056A2890DB82A68353BE9C0C0C8F89C0018B37FC")
+ )
+ .flags(AmmDepositFlags.TWO_ASSET_IF_EMPTY)
+ .asset(Issue.XRP)
+ .asset2(
+ Issue.builder()
+ .issuer(Address.of("rP9jPyP5kyvFRb6ZiRghAGw5u8SGAmU4bd"))
+ .currency("TST")
+ .build()
+ )
+ .amount(
+ IssuedCurrencyAmount.builder()
+ .currency("039C99CD9AB0B70B32ECDA51EAAE471625608EA2")
+ .issuer(Address.of("rE54zDvgnghAoPopCgvtiqWNq3dU5y836S"))
+ .value("100")
+ .build()
+ )
+ .amount2(XrpCurrencyAmount.ofDrops(10))
+ .effectivePrice(XrpCurrencyAmount.ofDrops(10))
+ .build();
+
+ assertThat(deposit.flags()).isEqualTo(AmmDepositFlags.TWO_ASSET_IF_EMPTY);
+
+ String json = "{\n" +
+ " \"Account\" : \"" + deposit.account() + "\",\n" +
+ " \"Amount\" : {\n" +
+ " \"currency\" : \"039C99CD9AB0B70B32ECDA51EAAE471625608EA2\",\n" +
+ " \"issuer\" : \"rE54zDvgnghAoPopCgvtiqWNq3dU5y836S\",\n" +
+ " \"value\" : \"100\"\n" +
+ " },\n" +
+ " \"Amount2\" : \"10\",\n" +
+ " \"EPrice\" : \"10\",\n" +
+ " \"Asset2\" : {\n" +
+ " \"currency\" : \"TST\",\n" +
+ " \"issuer\" : \"rP9jPyP5kyvFRb6ZiRghAGw5u8SGAmU4bd\"\n" +
+ " },\n" +
+ " \"Asset\" : {\n" +
+ " \"currency\" : \"XRP\"\n" +
+ " },\n" +
+ " \"Fee\" : \"10\",\n" +
+ " \"Flags\" : " + AmmDepositFlags.TWO_ASSET_IF_EMPTY + ",\n" +
+ " \"Sequence\" : 0,\n" +
+ " \"SigningPubKey\" : \"02356E89059A75438887F9FEE2056A2890DB82A68353BE9C0C0C8F89C0018B37FC\",\n" +
+ " \"TransactionType\" : \"AMMDeposit\"\n" +
+ "}";
+
+ assertCanSerializeAndDeserialize(deposit, json);
+ }
+}
\ No newline at end of file
diff --git a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/AmmVoteTest.java b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/AmmVoteTest.java
new file mode 100644
index 000000000..e0fee4500
--- /dev/null
+++ b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/AmmVoteTest.java
@@ -0,0 +1,131 @@
+package org.xrpl.xrpl4j.model.transactions;
+
+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.crypto.keys.PublicKey;
+import org.xrpl.xrpl4j.model.AbstractJsonTest;
+import org.xrpl.xrpl4j.model.flags.TransactionFlags;
+import org.xrpl.xrpl4j.model.ledger.Issue;
+
+class AmmVoteTest extends AbstractJsonTest {
+
+ @Test
+ void testJson() throws JSONException, JsonProcessingException {
+ AmmVote vote = AmmVote.builder()
+ .account(Address.of("rJVUeRqDFNs2xqA7ncVE6ZoAhPUoaJJSQm"))
+ .asset(Issue.XRP)
+ .asset2(
+ Issue.builder()
+ .currency("TST")
+ .issuer(Address.of("rP9jPyP5kyvFRb6ZiRghAGw5u8SGAmU4bd"))
+ .build()
+ )
+ .fee(XrpCurrencyAmount.ofDrops(10))
+ .sequence(UnsignedInteger.valueOf(8))
+ .tradingFee(TradingFee.of(UnsignedInteger.valueOf(600)))
+ .signingPublicKey(
+ PublicKey.fromBase16EncodedPublicKey("02356E89059A75438887F9FEE2056A2890DB82A68353BE9C0C0C8F89C0018B37FC")
+ )
+ .build();
+
+ String json = "{\n" +
+ " \"Account\" : \"rJVUeRqDFNs2xqA7ncVE6ZoAhPUoaJJSQm\",\n" +
+ " \"Asset\" : {\n" +
+ " \"currency\" : \"XRP\"\n" +
+ " },\n" +
+ " \"Asset2\" : {\n" +
+ " \"currency\" : \"TST\",\n" +
+ " \"issuer\" : \"rP9jPyP5kyvFRb6ZiRghAGw5u8SGAmU4bd\"\n" +
+ " },\n" +
+ " \"Fee\" : \"10\",\n" +
+ " \"Sequence\" : 8,\n" +
+ " \"TradingFee\" : 600,\n" +
+ " \"SigningPubKey\" : \"02356E89059A75438887F9FEE2056A2890DB82A68353BE9C0C0C8F89C0018B37FC\",\n" +
+ " \"TransactionType\" : \"AMMVote\"\n" +
+ "}";
+
+ assertCanSerializeAndDeserialize(vote, json);
+ }
+
+ @Test
+ void testJsonWithUnsetFlags() throws JSONException, JsonProcessingException {
+ AmmVote vote = AmmVote.builder()
+ .account(Address.of("rJVUeRqDFNs2xqA7ncVE6ZoAhPUoaJJSQm"))
+ .asset(Issue.XRP)
+ .asset2(
+ Issue.builder()
+ .currency("TST")
+ .issuer(Address.of("rP9jPyP5kyvFRb6ZiRghAGw5u8SGAmU4bd"))
+ .build()
+ )
+ .fee(XrpCurrencyAmount.ofDrops(10))
+ .sequence(UnsignedInteger.valueOf(8))
+ .tradingFee(TradingFee.of(UnsignedInteger.valueOf(600)))
+ .signingPublicKey(
+ PublicKey.fromBase16EncodedPublicKey("02356E89059A75438887F9FEE2056A2890DB82A68353BE9C0C0C8F89C0018B37FC")
+ )
+ .flags(TransactionFlags.UNSET)
+ .build();
+
+ String json = "{\n" +
+ " \"Account\" : \"rJVUeRqDFNs2xqA7ncVE6ZoAhPUoaJJSQm\",\n" +
+ " \"Asset\" : {\n" +
+ " \"currency\" : \"XRP\"\n" +
+ " },\n" +
+ " \"Asset2\" : {\n" +
+ " \"currency\" : \"TST\",\n" +
+ " \"issuer\" : \"rP9jPyP5kyvFRb6ZiRghAGw5u8SGAmU4bd\"\n" +
+ " },\n" +
+ " \"Fee\" : \"10\",\n" +
+ " \"Flags\" : 0,\n" +
+ " \"Sequence\" : 8,\n" +
+ " \"TradingFee\" : 600,\n" +
+ " \"SigningPubKey\" : \"02356E89059A75438887F9FEE2056A2890DB82A68353BE9C0C0C8F89C0018B37FC\",\n" +
+ " \"TransactionType\" : \"AMMVote\"\n" +
+ "}";
+
+ assertCanSerializeAndDeserialize(vote, json);
+ }
+
+ @Test
+ void testJsonWithNonZeroFlags() throws JSONException, JsonProcessingException {
+ AmmVote vote = AmmVote.builder()
+ .account(Address.of("rJVUeRqDFNs2xqA7ncVE6ZoAhPUoaJJSQm"))
+ .asset(Issue.XRP)
+ .asset2(
+ Issue.builder()
+ .currency("TST")
+ .issuer(Address.of("rP9jPyP5kyvFRb6ZiRghAGw5u8SGAmU4bd"))
+ .build()
+ )
+ .fee(XrpCurrencyAmount.ofDrops(10))
+ .sequence(UnsignedInteger.valueOf(8))
+ .tradingFee(TradingFee.of(UnsignedInteger.valueOf(600)))
+ .signingPublicKey(
+ PublicKey.fromBase16EncodedPublicKey("02356E89059A75438887F9FEE2056A2890DB82A68353BE9C0C0C8F89C0018B37FC")
+ )
+ .flags(TransactionFlags.FULLY_CANONICAL_SIG)
+ .build();
+
+ String json = "{\n" +
+ " \"Account\" : \"rJVUeRqDFNs2xqA7ncVE6ZoAhPUoaJJSQm\",\n" +
+ " \"Asset\" : {\n" +
+ " \"currency\" : \"XRP\"\n" +
+ " },\n" +
+ " \"Asset2\" : {\n" +
+ " \"currency\" : \"TST\",\n" +
+ " \"issuer\" : \"rP9jPyP5kyvFRb6ZiRghAGw5u8SGAmU4bd\"\n" +
+ " },\n" +
+ " \"Fee\" : \"10\",\n" +
+ " \"Flags\" : 2147483648,\n" +
+ " \"Sequence\" : 8,\n" +
+ " \"TradingFee\" : 600,\n" +
+ " \"SigningPubKey\" : \"02356E89059A75438887F9FEE2056A2890DB82A68353BE9C0C0C8F89C0018B37FC\",\n" +
+ " \"TransactionType\" : \"AMMVote\"\n" +
+ "}";
+
+ assertCanSerializeAndDeserialize(vote, json);
+ }
+}
\ No newline at end of file
diff --git a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/AmmWithdrawTest.java b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/AmmWithdrawTest.java
new file mode 100644
index 000000000..493af7fe3
--- /dev/null
+++ b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/AmmWithdrawTest.java
@@ -0,0 +1,223 @@
+package org.xrpl.xrpl4j.model.transactions;
+
+import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import org.json.JSONException;
+import org.junit.jupiter.api.Test;
+import org.xrpl.xrpl4j.crypto.keys.PublicKey;
+import org.xrpl.xrpl4j.model.AbstractJsonTest;
+import org.xrpl.xrpl4j.model.flags.AmmWithdrawFlags;
+import org.xrpl.xrpl4j.model.ledger.Issue;
+
+class AmmWithdrawTest extends AbstractJsonTest {
+
+ @Test
+ void constructLpTokenWithdrawAndTestJson() throws JSONException, JsonProcessingException {
+ AmmWithdraw withdraw = baseBuilder()
+ .flags(AmmWithdrawFlags.LP_TOKEN)
+ .lpTokensIn(lpTokensIn())
+ .build();
+
+ String json = "{\n" +
+ " \"Account\" : \"rJVUeRqDFNs2xqA7ncVE6ZoAhPUoaJJSQm\",\n" +
+ " \"LPTokensIn\" : " + objectMapper.writeValueAsString(withdraw.lpTokensIn()) + "," +
+ " \"Asset\" : " + objectMapper.writeValueAsString(withdraw.asset()) + "," +
+ " \"Asset2\" : " + objectMapper.writeValueAsString(withdraw.asset2()) + "," +
+ " \"Fee\" : \"10\",\n" +
+ " \"Flags\" : " + AmmWithdrawFlags.LP_TOKEN + ",\n" +
+ " \"Sequence\" : 0,\n" +
+ " \"SigningPubKey\" : \"02356E89059A75438887F9FEE2056A2890DB82A68353BE9C0C0C8F89C0018B37FC\",\n" +
+ " \"TransactionType\" : \"AMMWithdraw\"\n" +
+ "}";
+
+ assertCanSerializeAndDeserialize(withdraw, json);
+ }
+
+ @Test
+ void constructWithdrawAllAndTestJson() throws JSONException, JsonProcessingException {
+ AmmWithdraw withdraw = baseBuilder()
+ .flags(AmmWithdrawFlags.WITHDRAW_ALL)
+ .build();
+
+ String json = "{\n" +
+ " \"Account\" : \"rJVUeRqDFNs2xqA7ncVE6ZoAhPUoaJJSQm\",\n" +
+ " \"Asset\" : " + objectMapper.writeValueAsString(withdraw.asset()) + "," +
+ " \"Asset2\" : " + objectMapper.writeValueAsString(withdraw.asset2()) + "," +
+ " \"Fee\" : \"10\",\n" +
+ " \"Flags\" : " + AmmWithdrawFlags.WITHDRAW_ALL + ",\n" +
+ " \"Sequence\" : 0,\n" +
+ " \"SigningPubKey\" : \"02356E89059A75438887F9FEE2056A2890DB82A68353BE9C0C0C8F89C0018B37FC\",\n" +
+ " \"TransactionType\" : \"AMMWithdraw\"\n" +
+ "}";
+
+ assertCanSerializeAndDeserialize(withdraw, json);
+ }
+
+ @Test
+ void constructTwoAssetAndTestJson() throws JSONException, JsonProcessingException {
+ AmmWithdraw withdraw = baseBuilder()
+ .flags(AmmWithdrawFlags.TWO_ASSET)
+ .amount(amount())
+ .amount2(XrpCurrencyAmount.ofDrops(50000000))
+ .build();
+
+ String json = "{\n" +
+ " \"Account\" : \"rJVUeRqDFNs2xqA7ncVE6ZoAhPUoaJJSQm\",\n" +
+ " \"Amount\" : {\n" +
+ " \"currency\" : \"TST\",\n" +
+ " \"issuer\" : \"rP9jPyP5kyvFRb6ZiRghAGw5u8SGAmU4bd\",\n" +
+ " \"value\" : \"5\"\n" +
+ " },\n" +
+ " \"Amount2\" : \"50000000\"," +
+ " \"Asset\" : " + objectMapper.writeValueAsString(withdraw.asset()) + "," +
+ " \"Asset2\" : " + objectMapper.writeValueAsString(withdraw.asset2()) + "," +
+ " \"Fee\" : \"10\",\n" +
+ " \"Flags\" : " + AmmWithdrawFlags.TWO_ASSET + ",\n" +
+ " \"Sequence\" : 0,\n" +
+ " \"SigningPubKey\" : \"02356E89059A75438887F9FEE2056A2890DB82A68353BE9C0C0C8F89C0018B37FC\",\n" +
+ " \"TransactionType\" : \"AMMWithdraw\"\n" +
+ "}";
+
+ assertCanSerializeAndDeserialize(withdraw, json);
+ }
+
+ @Test
+ void constructSingleAssetAndTestJson() throws JSONException, JsonProcessingException {
+ AmmWithdraw withdraw = baseBuilder()
+ .flags(AmmWithdrawFlags.SINGLE_ASSET)
+ .amount(amount())
+ .build();
+
+ String json = "{\n" +
+ " \"Account\" : \"rJVUeRqDFNs2xqA7ncVE6ZoAhPUoaJJSQm\",\n" +
+ " \"Amount\" : {\n" +
+ " \"currency\" : \"TST\",\n" +
+ " \"issuer\" : \"rP9jPyP5kyvFRb6ZiRghAGw5u8SGAmU4bd\",\n" +
+ " \"value\" : \"5\"\n" +
+ " },\n" +
+ " \"Asset\" : " + objectMapper.writeValueAsString(withdraw.asset()) + "," +
+ " \"Asset2\" : " + objectMapper.writeValueAsString(withdraw.asset2()) + "," +
+ " \"Fee\" : \"10\",\n" +
+ " \"Flags\" : " + AmmWithdrawFlags.SINGLE_ASSET + ",\n" +
+ " \"Sequence\" : 0,\n" +
+ " \"SigningPubKey\" : \"02356E89059A75438887F9FEE2056A2890DB82A68353BE9C0C0C8F89C0018B37FC\",\n" +
+ " \"TransactionType\" : \"AMMWithdraw\"\n" +
+ "}";
+
+ assertCanSerializeAndDeserialize(withdraw, json);
+ }
+
+ @Test
+ void constructOneAssetWithdrawAllAndTestJson() throws JSONException, JsonProcessingException {
+ AmmWithdraw withdraw = baseBuilder()
+ .flags(AmmWithdrawFlags.ONE_ASSET_WITHDRAW_ALL)
+ .amount(amount())
+ .build();
+
+ String json = "{\n" +
+ " \"Account\" : \"rJVUeRqDFNs2xqA7ncVE6ZoAhPUoaJJSQm\",\n" +
+ " \"Amount\" : {\n" +
+ " \"currency\" : \"TST\",\n" +
+ " \"issuer\" : \"rP9jPyP5kyvFRb6ZiRghAGw5u8SGAmU4bd\",\n" +
+ " \"value\" : \"5\"\n" +
+ " },\n" +
+ " \"Asset\" : " + objectMapper.writeValueAsString(withdraw.asset()) + "," +
+ " \"Asset2\" : " + objectMapper.writeValueAsString(withdraw.asset2()) + "," +
+ " \"Fee\" : \"10\",\n" +
+ " \"Flags\" : " + AmmWithdrawFlags.ONE_ASSET_WITHDRAW_ALL + ",\n" +
+ " \"Sequence\" : 0,\n" +
+ " \"SigningPubKey\" : \"02356E89059A75438887F9FEE2056A2890DB82A68353BE9C0C0C8F89C0018B37FC\",\n" +
+ " \"TransactionType\" : \"AMMWithdraw\"\n" +
+ "}";
+
+ assertCanSerializeAndDeserialize(withdraw, json);
+ }
+
+ @Test
+ void constructOneAssetLpTokenAndTestJson() throws JSONException, JsonProcessingException {
+ AmmWithdraw withdraw = baseBuilder()
+ .flags(AmmWithdrawFlags.ONE_ASSET_LP_TOKEN)
+ .amount(amount())
+ .lpTokensIn(lpTokensIn())
+ .build();
+
+ String json = "{\n" +
+ " \"Account\" : \"rJVUeRqDFNs2xqA7ncVE6ZoAhPUoaJJSQm\",\n" +
+ " \"Amount\" : {\n" +
+ " \"currency\" : \"TST\",\n" +
+ " \"issuer\" : \"rP9jPyP5kyvFRb6ZiRghAGw5u8SGAmU4bd\",\n" +
+ " \"value\" : \"5\"\n" +
+ " },\n" +
+ " \"LPTokensIn\" : " + objectMapper.writeValueAsString(withdraw.lpTokensIn()) + "," +
+ " \"Asset\" : " + objectMapper.writeValueAsString(withdraw.asset()) + "," +
+ " \"Asset2\" : " + objectMapper.writeValueAsString(withdraw.asset2()) + "," +
+ " \"Fee\" : \"10\",\n" +
+ " \"Flags\" : " + AmmWithdrawFlags.ONE_ASSET_LP_TOKEN + ",\n" +
+ " \"Sequence\" : 0,\n" +
+ " \"SigningPubKey\" : \"02356E89059A75438887F9FEE2056A2890DB82A68353BE9C0C0C8F89C0018B37FC\",\n" +
+ " \"TransactionType\" : \"AMMWithdraw\"\n" +
+ "}";
+
+ assertCanSerializeAndDeserialize(withdraw, json);
+ }
+
+ @Test
+ void constructLimitLpTokenAndTestJson() throws JSONException, JsonProcessingException {
+ AmmWithdraw withdraw = baseBuilder()
+ .flags(AmmWithdrawFlags.LIMIT_LP_TOKEN)
+ .amount(amount())
+ .effectivePrice(amount())
+ .build();
+
+ String json = "{\n" +
+ " \"Account\" : \"rJVUeRqDFNs2xqA7ncVE6ZoAhPUoaJJSQm\",\n" +
+ " \"Amount\" : {\n" +
+ " \"currency\" : \"TST\",\n" +
+ " \"issuer\" : \"rP9jPyP5kyvFRb6ZiRghAGw5u8SGAmU4bd\",\n" +
+ " \"value\" : \"5\"\n" +
+ " },\n" +
+ " \"EPrice\" : " + objectMapper.writeValueAsString(withdraw.effectivePrice()) + "," +
+ " \"Asset\" : " + objectMapper.writeValueAsString(withdraw.asset()) + "," +
+ " \"Asset2\" : " + objectMapper.writeValueAsString(withdraw.asset2()) + "," +
+ " \"Fee\" : \"10\",\n" +
+ " \"Flags\" : " + AmmWithdrawFlags.LIMIT_LP_TOKEN + ",\n" +
+ " \"Sequence\" : 0,\n" +
+ " \"SigningPubKey\" : \"02356E89059A75438887F9FEE2056A2890DB82A68353BE9C0C0C8F89C0018B37FC\",\n" +
+ " \"TransactionType\" : \"AMMWithdraw\"\n" +
+ "}";
+
+ assertCanSerializeAndDeserialize(withdraw, json);
+ }
+
+ private ImmutableIssuedCurrencyAmount amount() {
+ return IssuedCurrencyAmount.builder()
+ .currency("TST")
+ .issuer(Address.of("rP9jPyP5kyvFRb6ZiRghAGw5u8SGAmU4bd"))
+ .value("5")
+ .build();
+ }
+
+ private ImmutableIssuedCurrencyAmount lpTokensIn() {
+ return IssuedCurrencyAmount.builder()
+ .currency("039C99CD9AB0B70B32ECDA51EAAE471625608EA2")
+ .issuer(Address.of("rE54zDvgnghAoPopCgvtiqWNq3dU5y836S"))
+ .value("100")
+ .build();
+ }
+
+ private ImmutableAmmWithdraw.Builder baseBuilder() {
+ return AmmWithdraw.builder()
+ .account(Address.of("rJVUeRqDFNs2xqA7ncVE6ZoAhPUoaJJSQm"))
+ .signingPublicKey(
+ PublicKey.fromBase16EncodedPublicKey("02356E89059A75438887F9FEE2056A2890DB82A68353BE9C0C0C8F89C0018B37FC")
+ )
+ .fee(XrpCurrencyAmount.ofDrops(10))
+ .asset(
+ Issue.builder()
+ .issuer(Address.of("rP9jPyP5kyvFRb6ZiRghAGw5u8SGAmU4bd"))
+ .currency("TST")
+ .build()
+ ).asset2(Issue.XRP);
+ }
+}
\ No newline at end of file
diff --git a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/ClawbackTest.java b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/ClawbackTest.java
new file mode 100644
index 000000000..5eeb0410a
--- /dev/null
+++ b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/ClawbackTest.java
@@ -0,0 +1,118 @@
+package org.xrpl.xrpl4j.model.transactions;
+
+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.crypto.keys.PublicKey;
+import org.xrpl.xrpl4j.model.AbstractJsonTest;
+import org.xrpl.xrpl4j.model.flags.TransactionFlags;
+
+class ClawbackTest extends AbstractJsonTest {
+
+ @Test
+ void testJsonWithoutFlags() throws JSONException, JsonProcessingException {
+ Clawback clawback = Clawback.builder()
+ .account(Address.of("rp6abvbTbjoce8ZDJkT6snvxTZSYMBCC9S"))
+ .fee(XrpCurrencyAmount.ofDrops(10))
+ .sequence(UnsignedInteger.ONE)
+ .signingPublicKey(PublicKey.fromBase16EncodedPublicKey(
+ "02356E89059A75438887F9FEE2056A2890DB82A68353BE9C0C0C8F89C0018B37FC"
+ ))
+ .amount(
+ IssuedCurrencyAmount.builder()
+ .currency("FOO")
+ .issuer(Address.of("rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW"))
+ .value("314.159")
+ .build()
+ )
+ .build();
+
+ String json = "{\n" +
+ " \"TransactionType\": \"Clawback\",\n" +
+ " \"Account\": \"rp6abvbTbjoce8ZDJkT6snvxTZSYMBCC9S\",\n" +
+ " \"Amount\": {\n" +
+ " \"currency\": \"FOO\",\n" +
+ " \"issuer\": \"rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW\",\n" +
+ " \"value\": \"314.159\"\n" +
+ " },\n" +
+ " \"Fee\": \"10\",\n" +
+ " \"Sequence\": 1,\n" +
+ " \"SigningPubKey\": \"02356E89059A75438887F9FEE2056A2890DB82A68353BE9C0C0C8F89C0018B37FC\"\n" +
+ "}";
+
+ assertCanSerializeAndDeserialize(clawback, json);
+ }
+
+ @Test
+ void testJsonWithZeroFlags() throws JSONException, JsonProcessingException {
+ Clawback clawback = Clawback.builder()
+ .account(Address.of("rp6abvbTbjoce8ZDJkT6snvxTZSYMBCC9S"))
+ .fee(XrpCurrencyAmount.ofDrops(10))
+ .sequence(UnsignedInteger.ONE)
+ .signingPublicKey(PublicKey.fromBase16EncodedPublicKey(
+ "02356E89059A75438887F9FEE2056A2890DB82A68353BE9C0C0C8F89C0018B37FC"
+ ))
+ .flags(TransactionFlags.UNSET)
+ .amount(
+ IssuedCurrencyAmount.builder()
+ .currency("FOO")
+ .issuer(Address.of("rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW"))
+ .value("314.159")
+ .build()
+ )
+ .build();
+
+ String json = "{\n" +
+ " \"TransactionType\": \"Clawback\",\n" +
+ " \"Account\": \"rp6abvbTbjoce8ZDJkT6snvxTZSYMBCC9S\",\n" +
+ " \"Amount\": {\n" +
+ " \"currency\": \"FOO\",\n" +
+ " \"issuer\": \"rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW\",\n" +
+ " \"value\": \"314.159\"\n" +
+ " },\n" +
+ " \"Fee\": \"10\",\n" +
+ " \"Flags\": 0,\n" +
+ " \"Sequence\": 1,\n" +
+ " \"SigningPubKey\": \"02356E89059A75438887F9FEE2056A2890DB82A68353BE9C0C0C8F89C0018B37FC\"\n" +
+ "}";
+
+ assertCanSerializeAndDeserialize(clawback, json);
+ }
+
+ @Test
+ void testJsonWithNonZeroFlags() throws JSONException, JsonProcessingException {
+ Clawback clawback = Clawback.builder()
+ .account(Address.of("rp6abvbTbjoce8ZDJkT6snvxTZSYMBCC9S"))
+ .fee(XrpCurrencyAmount.ofDrops(10))
+ .sequence(UnsignedInteger.ONE)
+ .signingPublicKey(PublicKey.fromBase16EncodedPublicKey(
+ "02356E89059A75438887F9FEE2056A2890DB82A68353BE9C0C0C8F89C0018B37FC"
+ ))
+ .flags(TransactionFlags.FULLY_CANONICAL_SIG)
+ .amount(
+ IssuedCurrencyAmount.builder()
+ .currency("FOO")
+ .issuer(Address.of("rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW"))
+ .value("314.159")
+ .build()
+ )
+ .build();
+
+ String json = String.format("{\n" +
+ " \"TransactionType\": \"Clawback\",\n" +
+ " \"Account\": \"rp6abvbTbjoce8ZDJkT6snvxTZSYMBCC9S\",\n" +
+ " \"Amount\": {\n" +
+ " \"currency\": \"FOO\",\n" +
+ " \"issuer\": \"rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW\",\n" +
+ " \"value\": \"314.159\"\n" +
+ " },\n" +
+ " \"Fee\": \"10\",\n" +
+ " \"Flags\": %s,\n" +
+ " \"Sequence\": 1,\n" +
+ " \"SigningPubKey\": \"02356E89059A75438887F9FEE2056A2890DB82A68353BE9C0C0C8F89C0018B37FC\"\n" +
+ "}", TransactionFlags.FULLY_CANONICAL_SIG);
+
+ assertCanSerializeAndDeserialize(clawback, json);
+ }
+}
\ No newline at end of file
diff --git a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/CurrencyAmountTest.java b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/CurrencyAmountTest.java
index f7129de5a..87c494d2b 100644
--- a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/CurrencyAmountTest.java
+++ b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/CurrencyAmountTest.java
@@ -24,8 +24,15 @@
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.fail;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.google.common.io.BaseEncoding;
import com.google.common.primitives.UnsignedLong;
import org.junit.jupiter.api.Test;
+import org.xrpl.xrpl4j.codec.addresses.ByteUtils;
+import org.xrpl.xrpl4j.codec.binary.XrplBinaryCodec;
+import org.xrpl.xrpl4j.model.jackson.ObjectMapperFactory;
+
+import java.nio.charset.StandardCharsets;
/**
* Unit tests for {@link CurrencyAmount}.
@@ -134,6 +141,80 @@ public void mapIssuance() {
);
}
+ /**
+ * Verify that xrpl4j is unaffected by the bug reported in rippled issue #4112. When rippled APIs are provided
+ * 3-character currency codes, those APIs will upper-case the supplied currency values. Only after that normalization
+ * will those APIs then convert to binary. For example, if a request is made to rippled to sign a payload with a
+ * currency code of `UsD`, the API layer will normalize this value to `USD` (i.e., all-caps) before signing. However,
+ * tooling (like xrpl4j) is not forced to do this kind of upper-case normalization. So, it's possible for any tooling
+ * (like xrpl4j) to unintentionally allow issuers to issue mixed-case, 3-character currency codes. However, there's
+ * debate in the GH issue linked below about whether this is a bug in tooling (like xrpl4j), or if this is actually a
+ * bug in the rippled code base (in which case, the normalization functionality should be removed from rippled, and
+ * tooling should do nothing). Contributors to the issue assert the latter -- i.e., it's a bug in rippled and should
+ * be removed from rippled. There is also PR to this effect. Thus, this test ensures that xrpl4j tooling does the
+ * correct thing (i.e., no currency code normalization, either in our Transaction layer or in the binary codec).
+ *
+ * @see "https://github.com/XRPLF/rippled/issues/4112"
+ */
+ @Test
+ public void buildIssuanceWithMixedCaseThreeCharacterCode() {
+ final IssuedCurrencyAmount issuedCurrencyAmount = IssuedCurrencyAmount.builder()
+ .issuer(Address.of("rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59Ba"))
+ .currency("UsD")
+ .value("100")
+ .build();
+
+ assertThat(issuedCurrencyAmount.currency()).isEqualTo("UsD");
+ }
+
+ @Test
+ public void encodeDecodeMixedCaseCurrencyCode() throws JsonProcessingException {
+ currencyTestHelper("Usd");
+ currencyTestHelper("UsD");
+ currencyTestHelper("USD");
+ currencyTestHelper("$GHOST");
+ currencyTestHelper("$ghost");
+ currencyTestHelper("$ghosT");
+ }
+
+ /**
+ * Helper method to test various currencies codes for capitalization.
+ *
+ * @param currencyCode A {@link String} representing a currency code.
+ */
+ private void currencyTestHelper(String currencyCode) throws JsonProcessingException {
+ if (currencyCode.length() > 3) {
+ currencyCode = ByteUtils.padded(
+ BaseEncoding.base16().encode(currencyCode.getBytes(StandardCharsets.US_ASCII)),
+ 40 // <-- Non-standard currency codes must be 40 bytes.
+ );
+ }
+
+ final CurrencyAmount issuedCurrencyAmountMixed = IssuedCurrencyAmount.builder()
+ .issuer(Address.of("rPx8CtHbTkjYbQzrwfDxXfPfLHV9nbjYBz"))
+ .currency(currencyCode)
+ .value("100")
+ .build();
+
+ Payment payment = Payment.builder()
+ .account(Address.of("rPx8CtHbTkjYbQzrwfDxXfPfLHV9nbjYBz"))
+ .destination(Address.of("rPx8CtHbTkjYbQzrwfDxXfPfLHV9nbjYBz"))
+ .amount(issuedCurrencyAmountMixed)
+ .fee(XrpCurrencyAmount.of(UnsignedLong.ONE))
+ .build();
+
+ String transactionJson = ObjectMapperFactory.create().writeValueAsString(payment);
+ String transactionBinary = XrplBinaryCodec.getInstance().encode(transactionJson);
+ String decodedTransactionJson = XrplBinaryCodec.getInstance().decode(transactionBinary);
+ Payment decodedPayment = ObjectMapperFactory.create().readValue(decodedTransactionJson, Payment.class);
+
+ final String finalCurrencyCode = currencyCode;
+ decodedPayment.amount().handle(
+ xrpCurrencyAmount -> fail(),
+ issuedCurrencyAmount -> assertThat(issuedCurrencyAmount.currency()).isEqualTo(finalCurrencyCode)
+ );
+ }
+
@Test
void testConstants() {
assertThat(CurrencyAmount.ONE_XRP_IN_DROPS).isEqualTo(1_000_000L);
diff --git a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/EscrowFinishTest.java b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/EscrowFinishTest.java
index 804f8005f..232bf6b6b 100644
--- a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/EscrowFinishTest.java
+++ b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/EscrowFinishTest.java
@@ -21,13 +21,21 @@
*/
import static org.assertj.core.api.Assertions.assertThat;
-import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.google.common.io.BaseEncoding;
import com.google.common.primitives.UnsignedInteger;
+import com.ripple.cryptoconditions.Condition;
+import com.ripple.cryptoconditions.CryptoConditionReader;
import com.ripple.cryptoconditions.Fulfillment;
+import com.ripple.cryptoconditions.PreimageSha256Condition;
import com.ripple.cryptoconditions.PreimageSha256Fulfillment;
+import com.ripple.cryptoconditions.der.DerEncodingException;
import org.assertj.core.api.Assertions;
+import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
+import org.xrpl.xrpl4j.model.jackson.ObjectMapperFactory;
import org.xrpl.xrpl4j.model.transactions.ImmutableEscrowFinish.Builder;
/**
@@ -35,8 +43,27 @@
*/
public class EscrowFinishTest {
+ public static final String GOOD_CONDITION_STR =
+ "A0258020E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855810100";
+
+ public static final String GOOD_FULFILLMENT_STR =
+ "A0028000";
+
+ private static Condition condition;
+ private static Fulfillment> fulfillment;
+
+ @BeforeAll
+ static void beforeAll() {
+ try {
+ condition = CryptoConditionReader.readCondition(BaseEncoding.base16().decode(GOOD_CONDITION_STR));
+ fulfillment = CryptoConditionReader.readFulfillment(BaseEncoding.base16().decode(GOOD_FULFILLMENT_STR));
+ } catch (DerEncodingException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
@Test
- public void testNormalizeWithNoFulfillmentNoCondition() {
+ public void constructWithNoFulfillmentNoCondition() {
EscrowFinish actual = EscrowFinish.builder()
.fee(XrpCurrencyAmount.ofDrops(1))
.account(Address.of("rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59Ba"))
@@ -46,7 +73,9 @@ public void testNormalizeWithNoFulfillmentNoCondition() {
.build();
assertThat(actual.condition()).isNotPresent();
+ assertThat(actual.conditionRawValue()).isNotPresent();
assertThat(actual.fulfillment()).isNotPresent();
+ assertThat(actual.fulfillmentRawValue()).isNotPresent();
assertThat(actual.fee()).isEqualTo(XrpCurrencyAmount.ofDrops(1));
assertThat(actual.account()).isEqualTo(Address.of("rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59Ba"));
assertThat(actual.sequence()).isEqualTo(UnsignedInteger.ONE);
@@ -54,86 +83,220 @@ public void testNormalizeWithNoFulfillmentNoCondition() {
assertThat(actual.offerSequence()).isEqualTo(UnsignedInteger.ZERO);
}
+ ////////////////////////////////
+ // normalizeCondition tests
+ ////////////////////////////////
+
+ @Test
+ void normalizeWithNoConditionNoRawValue() {
+ EscrowFinish actual = EscrowFinish.builder()
+ .fee(XrpCurrencyAmount.ofDrops(1))
+ .account(Address.of("rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59Ba"))
+ .sequence(UnsignedInteger.ONE)
+ .owner(Address.of("rN7n7otQDd6FczFgLdSqtcsAUxDkw6fzRH"))
+ .offerSequence(UnsignedInteger.ZERO)
+ .build();
+
+ assertThat(actual.condition()).isEmpty();
+ assertThat(actual.conditionRawValue()).isEmpty();
+ }
+
+ @Test
+ void normalizeWithConditionAndRawValueMatching() {
+ EscrowFinish actual = EscrowFinish.builder()
+ .fee(XrpCurrencyAmount.ofDrops(1))
+ .account(Address.of("rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59Ba"))
+ .sequence(UnsignedInteger.ONE)
+ .owner(Address.of("rN7n7otQDd6FczFgLdSqtcsAUxDkw6fzRH"))
+ .offerSequence(UnsignedInteger.ZERO)
+ .condition(condition)
+ .conditionRawValue(GOOD_CONDITION_STR)
+ .build();
+
+ assertThat(actual.condition()).isNotEmpty().get().isEqualTo(condition);
+ assertThat(actual.conditionRawValue()).isNotEmpty().get().isEqualTo(GOOD_CONDITION_STR);
+ }
+
+ @Test
+ void normalizeWithConditionAndRawValueNonMatching() {
+ assertThatThrownBy(() -> EscrowFinish.builder()
+ .fee(XrpCurrencyAmount.ofDrops(1))
+ .account(Address.of("rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59Ba"))
+ .sequence(UnsignedInteger.ONE)
+ .owner(Address.of("rN7n7otQDd6FczFgLdSqtcsAUxDkw6fzRH"))
+ .offerSequence(UnsignedInteger.ZERO)
+ .condition(condition)
+ // This is slightly different than GOOD_CONDITION_STR
+ .conditionRawValue("A0258020E3B0C44298FC1C149ABCD4C8996FB92427AE41E4649B934CA495991B7852B855810100")
+ .build()
+ ).isInstanceOf(IllegalStateException.class)
+ .hasMessage("condition and conditionRawValue should be equivalent if both are present.");
+ }
+
@Test
- public void testNormalizeWithFulfillmentNoCondition() {
- Fulfillment fulfillment = PreimageSha256Fulfillment.from("ssh".getBytes());
-
- assertThrows(
- IllegalStateException.class,
- () -> EscrowFinish.builder()
- .fee(XrpCurrencyAmount.ofDrops(1))
- .account(Address.of("rN7n7otQDd6FczFgLdSqtcsAUxDkw6fzRH"))
- .sequence(UnsignedInteger.ONE)
- .owner(Address.of("rN7n7otQDd6FczFgLdSqtcsAUxDkw6fzRH"))
- .offerSequence(UnsignedInteger.ZERO)
- .fulfillment(fulfillment)
- .build(),
- "If a fulfillment is specified, the corresponding condition must also be specified."
- );
+ void normalizeWithConditionPresentAndNoRawValue() {
+ EscrowFinish actual = EscrowFinish.builder()
+ .fee(XrpCurrencyAmount.ofDrops(1))
+ .account(Address.of("rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59Ba"))
+ .sequence(UnsignedInteger.ONE)
+ .owner(Address.of("rN7n7otQDd6FczFgLdSqtcsAUxDkw6fzRH"))
+ .offerSequence(UnsignedInteger.ZERO)
+ .condition(condition)
+ .build();
+
+ assertThat(actual.conditionRawValue()).isNotEmpty().get().isEqualTo(GOOD_CONDITION_STR);
}
@Test
- public void testNormalizeWithNoFulfillmentAndCondition() {
- Fulfillment fulfillment = PreimageSha256Fulfillment.from("ssh".getBytes());
-
- assertThrows(
- IllegalStateException.class,
- () -> EscrowFinish.builder()
- .fee(XrpCurrencyAmount.ofDrops(1))
- .account(Address.of("rN7n7otQDd6FczFgLdSqtcsAUxDkw6fzRH"))
- .sequence(UnsignedInteger.ONE)
- .owner(Address.of("rN7n7otQDd6FczFgLdSqtcsAUxDkw6fzRH"))
- .offerSequence(UnsignedInteger.ZERO)
- .condition(fulfillment.getDerivedCondition())
- .build(),
- "If a condition is specified, the corresponding fulfillment must also be specified."
- );
+ void normalizeWithNoConditionAndRawValueForValidCondition() {
+ EscrowFinish actual = EscrowFinish.builder()
+ .fee(XrpCurrencyAmount.ofDrops(1))
+ .account(Address.of("rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59Ba"))
+ .sequence(UnsignedInteger.ONE)
+ .owner(Address.of("rN7n7otQDd6FczFgLdSqtcsAUxDkw6fzRH"))
+ .offerSequence(UnsignedInteger.ZERO)
+ .conditionRawValue(GOOD_CONDITION_STR)
+ .build();
+
+ assertThat(actual.condition()).isNotEmpty().get().isEqualTo(condition);
}
@Test
- public void testNormalizeWithFulfillmentAndConditionButFeeLow() {
- // We expect the
+ void normalizeWithNoConditionAndRawValueForMalformedCondition() {
+ EscrowFinish actual = EscrowFinish.builder()
+ .fee(XrpCurrencyAmount.ofDrops(1))
+ .account(Address.of("rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59Ba"))
+ .sequence(UnsignedInteger.ONE)
+ .owner(Address.of("rN7n7otQDd6FczFgLdSqtcsAUxDkw6fzRH"))
+ .offerSequence(UnsignedInteger.ZERO)
+ .conditionRawValue("1234")
+ .build();
- Fulfillment fulfillment = PreimageSha256Fulfillment.from("ssh".getBytes());
+ assertThat(actual.condition()).isEmpty();
+ assertThat(actual.conditionRawValue()).isNotEmpty().get().isEqualTo("1234");
+ }
+ @Test
+ void normalizeWithNoConditionAndRawValueForBadHexLengthCondition() {
EscrowFinish actual = EscrowFinish.builder()
- .fee(XrpCurrencyAmount.ofDrops(330))
- .account(Address.of("rN7n7otQDd6FczFgLdSqtcsAUxDkw6fzRH"))
+ .fee(XrpCurrencyAmount.ofDrops(1))
+ .account(Address.of("rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59Ba"))
+ .sequence(UnsignedInteger.ONE)
+ .owner(Address.of("rN7n7otQDd6FczFgLdSqtcsAUxDkw6fzRH"))
+ .offerSequence(UnsignedInteger.ZERO)
+ .conditionRawValue("123")
+ .build();
+
+ assertThat(actual.condition()).isEmpty();
+ assertThat(actual.conditionRawValue()).isNotEmpty().get().isEqualTo("123");
+ }
+
+ ////////////////////////////////
+ // normalizeFulfillment tests
+ ////////////////////////////////
+
+ @Test
+ void normalizeWithNoFulfillmentNoRawValue() {
+ EscrowFinish actual = EscrowFinish.builder()
+ .fee(XrpCurrencyAmount.ofDrops(1))
+ .account(Address.of("rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59Ba"))
+ .sequence(UnsignedInteger.ONE)
+ .owner(Address.of("rN7n7otQDd6FczFgLdSqtcsAUxDkw6fzRH"))
+ .offerSequence(UnsignedInteger.ZERO)
+ .build();
+
+ assertThat(actual.fulfillment()).isEmpty();
+ assertThat(actual.fulfillmentRawValue()).isEmpty();
+ }
+
+ @Test
+ void normalizeWithFulfillmentAndRawValueMatching() {
+ EscrowFinish actual = EscrowFinish.builder()
+ .fee(XrpCurrencyAmount.ofDrops(1))
+ .account(Address.of("rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59Ba"))
.sequence(UnsignedInteger.ONE)
.owner(Address.of("rN7n7otQDd6FczFgLdSqtcsAUxDkw6fzRH"))
.offerSequence(UnsignedInteger.ZERO)
.fulfillment(fulfillment)
- .condition(fulfillment.getDerivedCondition())
+ .fulfillmentRawValue(GOOD_FULFILLMENT_STR)
.build();
- assertThat(actual.condition()).isPresent();
- assertThat(actual.account()).isEqualTo(Address.of("rN7n7otQDd6FczFgLdSqtcsAUxDkw6fzRH"));
- assertThat(actual.sequence()).isEqualTo(UnsignedInteger.ONE);
- assertThat(actual.owner()).isEqualTo(Address.of("rN7n7otQDd6FczFgLdSqtcsAUxDkw6fzRH"));
- assertThat(actual.offerSequence()).isEqualTo(UnsignedInteger.ZERO);
+ assertThat(actual.fulfillment()).isNotEmpty().get().isEqualTo(fulfillment);
+ assertThat(actual.fulfillmentRawValue()).isNotEmpty().get().isEqualTo(GOOD_FULFILLMENT_STR);
+ }
- assertThat(actual.fulfillment()).isPresent();
- assertThat(actual.fulfillment().get()).isEqualTo(fulfillment);
- assertThat(actual.fee()).isEqualTo(XrpCurrencyAmount.ofDrops(330));
+ @Test
+ void normalizeWithFulfillmentAndRawValueNonMatching() {
+ assertThatThrownBy(() -> EscrowFinish.builder()
+ .fee(XrpCurrencyAmount.ofDrops(1))
+ .account(Address.of("rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59Ba"))
+ .sequence(UnsignedInteger.ONE)
+ .owner(Address.of("rN7n7otQDd6FczFgLdSqtcsAUxDkw6fzRH"))
+ .offerSequence(UnsignedInteger.ZERO)
+ .fulfillment(fulfillment)
+ // This is slightly different than GOOD_FULFILLMENT_STR
+ .fulfillmentRawValue("A0011000")
+ .build()
+ ).isInstanceOf(IllegalStateException.class)
+ .hasMessage("fulfillment and fulfillmentRawValue should be equivalent if both are present.");
}
@Test
- public void testNormalizeWithFeeTooLow() {
- Fulfillment fulfillment = PreimageSha256Fulfillment.from("ssh".getBytes());
-
- assertThrows(
- IllegalStateException.class,
- () -> EscrowFinish.builder()
- .fee(XrpCurrencyAmount.ofDrops(1))
- .account(Address.of("rN7n7otQDd6FczFgLdSqtcsAUxDkw6fzRH"))
- .sequence(UnsignedInteger.ONE)
- .owner(Address.of("rN7n7otQDd6FczFgLdSqtcsAUxDkw6fzRH"))
- .offerSequence(UnsignedInteger.ZERO)
- .fulfillment(fulfillment)
- .condition(fulfillment.getDerivedCondition())
- .build(),
- "If a fulfillment is specified, the fee must be set to 330 or greater."
- );
+ void normalizeWithFulfillmentPresentAndNoRawValue() {
+ EscrowFinish actual = EscrowFinish.builder()
+ .fee(XrpCurrencyAmount.ofDrops(1))
+ .account(Address.of("rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59Ba"))
+ .sequence(UnsignedInteger.ONE)
+ .owner(Address.of("rN7n7otQDd6FczFgLdSqtcsAUxDkw6fzRH"))
+ .offerSequence(UnsignedInteger.ZERO)
+ .fulfillment(fulfillment)
+ .build();
+
+ assertThat(actual.fulfillmentRawValue()).isNotEmpty().get().isEqualTo(GOOD_FULFILLMENT_STR);
+ }
+
+ @Test
+ void normalizeWithNoFulfillmentAndRawValueForValidFulfillment() {
+ EscrowFinish actual = EscrowFinish.builder()
+ .fee(XrpCurrencyAmount.ofDrops(1))
+ .account(Address.of("rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59Ba"))
+ .sequence(UnsignedInteger.ONE)
+ .owner(Address.of("rN7n7otQDd6FczFgLdSqtcsAUxDkw6fzRH"))
+ .offerSequence(UnsignedInteger.ZERO)
+ .fulfillmentRawValue(GOOD_FULFILLMENT_STR)
+ .build();
+
+ assertThat(actual.fulfillment()).isNotEmpty().get().isEqualTo(fulfillment);
+ }
+
+ @Test
+ void normalizeWithNoFulfillmentAndRawValueForMalformedFulfillment() {
+ EscrowFinish actual = EscrowFinish.builder()
+ .fee(XrpCurrencyAmount.ofDrops(1))
+ .account(Address.of("rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59Ba"))
+ .sequence(UnsignedInteger.ONE)
+ .owner(Address.of("rN7n7otQDd6FczFgLdSqtcsAUxDkw6fzRH"))
+ .offerSequence(UnsignedInteger.ZERO)
+ .fulfillmentRawValue("1234")
+ .build();
+
+ assertThat(actual.fulfillment()).isEmpty();
+ assertThat(actual.fulfillmentRawValue()).isNotEmpty().get().isEqualTo("1234");
+ }
+
+ @Test
+ void normalizeWithNoFulfillmentAndRawValueForBadHexLengthFulfillment() {
+ EscrowFinish actual = EscrowFinish.builder()
+ .fee(XrpCurrencyAmount.ofDrops(1))
+ .account(Address.of("rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59Ba"))
+ .sequence(UnsignedInteger.ONE)
+ .owner(Address.of("rN7n7otQDd6FczFgLdSqtcsAUxDkw6fzRH"))
+ .offerSequence(UnsignedInteger.ZERO)
+ .fulfillmentRawValue("123")
+ .build();
+
+ assertThat(actual.fulfillment()).isEmpty();
+ assertThat(actual.fulfillmentRawValue()).isNotEmpty().get().isEqualTo("123");
}
@Test
diff --git a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/TradingFeeTest.java b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/TradingFeeTest.java
new file mode 100644
index 000000000..16aa2f272
--- /dev/null
+++ b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/TradingFeeTest.java
@@ -0,0 +1,105 @@
+package org.xrpl.xrpl4j.model.transactions;
+
+import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
+import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy;
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+
+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;
+
+import java.math.BigDecimal;
+
+public class TradingFeeTest {
+
+ ObjectMapper objectMapper = ObjectMapperFactory.create();
+
+ @Test
+ void fromUnsignedInteger() {
+ TradingFee fee = TradingFee.of(UnsignedInteger.ONE);
+ assertThat(fee.toString()).isEqualTo("1");
+ assertThat(fee.value()).isEqualTo(UnsignedInteger.ONE);
+ }
+
+ @Test
+ void ofPercent() {
+ TradingFee fee = TradingFee.ofPercent(BigDecimal.valueOf(0.99900));
+ assertThat(fee.value()).isEqualTo(UnsignedInteger.valueOf(999));
+
+ fee = TradingFee.ofPercent(BigDecimal.valueOf(1.0000));
+ assertThat(fee.value()).isEqualTo(UnsignedInteger.valueOf(1000));
+
+ fee = TradingFee.ofPercent(BigDecimal.valueOf(0));
+ assertThat(fee.value()).isEqualTo(UnsignedInteger.valueOf(0));
+
+ fee = TradingFee.ofPercent(BigDecimal.valueOf(0.00000000000));
+ assertThat(fee.value()).isEqualTo(UnsignedInteger.valueOf(0));
+ }
+
+ @Test
+ void ofPercentWithTooManyDecimalPlaces() {
+ assertThatThrownBy(
+ () -> TradingFee.ofPercent(BigDecimal.valueOf(0.0001))
+ ).isInstanceOf(IllegalArgumentException.class)
+ .hasMessage("Percent value should have a maximum of 3 decimal places.");
+
+ assertThatThrownBy(
+ () -> TradingFee.ofPercent(BigDecimal.valueOf(0.0001000))
+ ).isInstanceOf(IllegalArgumentException.class)
+ .hasMessage("Percent value should have a maximum of 3 decimal places.");
+ }
+
+ @Test
+ void bigDecimalValue() {
+ BigDecimal percent = BigDecimal.valueOf(0.001000);
+ TradingFee fee = TradingFee.ofPercent(percent);
+ assertThat(fee.bigDecimalValue()).isEqualTo(percent);
+
+ percent = BigDecimal.valueOf(1, 3);
+ fee = TradingFee.ofPercent(percent);
+ assertThat(fee.bigDecimalValue()).isEqualTo(percent);
+ }
+
+ @Test
+ void testJson() throws JsonProcessingException, JSONException {
+ TradingFee tradingFee = TradingFee.of(UnsignedInteger.valueOf(1000));
+ TradingFeeWrapper wrapper = TradingFeeWrapper.of(tradingFee);
+
+ String json = "{\"tradingFee\": 1000}";
+ assertSerializesAndDeserializes(wrapper, json);
+ }
+
+ private void assertSerializesAndDeserializes(
+ TradingFeeWrapper wrapper,
+ String json
+ ) throws JsonProcessingException, JSONException {
+ String serialized = objectMapper.writeValueAsString(wrapper);
+ JSONAssert.assertEquals(json, serialized, JSONCompareMode.STRICT);
+ TradingFeeWrapper deserialized = objectMapper.readValue(
+ serialized, TradingFeeWrapper.class
+ );
+ Assertions.assertThat(deserialized).isEqualTo(wrapper);
+ }
+
+ @Value.Immutable
+ @JsonSerialize(as = ImmutableTradingFeeWrapper.class)
+ @JsonDeserialize(as = ImmutableTradingFeeWrapper.class)
+ interface TradingFeeWrapper {
+
+ static TradingFeeWrapper of(TradingFee tradingFee) {
+ return ImmutableTradingFeeWrapper.builder().tradingFee(tradingFee).build();
+ }
+
+ TradingFee tradingFee();
+
+ }
+}
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 d572a6f42..1719a471a 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
@@ -21,6 +21,7 @@
import org.xrpl.xrpl4j.model.transactions.metadata.ImmutableDeletedNode;
import org.xrpl.xrpl4j.model.transactions.metadata.ImmutableMetaNfTokenOfferObject;
import org.xrpl.xrpl4j.model.transactions.metadata.MetaAccountRootObject;
+import org.xrpl.xrpl4j.model.transactions.metadata.MetaAmmObject;
import org.xrpl.xrpl4j.model.transactions.metadata.MetaCheckObject;
import org.xrpl.xrpl4j.model.transactions.metadata.MetaDepositPreAuthObject;
import org.xrpl.xrpl4j.model.transactions.metadata.MetaEscrowObject;
@@ -804,6 +805,8 @@ private Class extends MetaLedgerObject> determineLedgerObjectType(String ledge
return MetaTicketObject.class;
case "NFTokenPage":
return MetaNfTokenPageObject.class;
+ case "AMM":
+ return MetaAmmObject.class;
default:
return MetaUnknownObject.class;
}
diff --git a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/VoteWeightTest.java b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/VoteWeightTest.java
new file mode 100644
index 000000000..1c7180276
--- /dev/null
+++ b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/VoteWeightTest.java
@@ -0,0 +1,81 @@
+package org.xrpl.xrpl4j.model.transactions;
+
+import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
+
+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;
+
+import java.math.BigDecimal;
+
+public class VoteWeightTest {
+
+ ObjectMapper objectMapper = ObjectMapperFactory.create();
+
+ @Test
+ void bigDecimalValue() {
+ VoteWeight weight = VoteWeight.of(UnsignedInteger.ZERO);
+ assertThat(weight.bigDecimalValue().compareTo(BigDecimal.ZERO)).isEqualTo(0);
+
+ weight = VoteWeight.of(UnsignedInteger.ONE);
+ assertThat(weight.bigDecimalValue().compareTo(BigDecimal.valueOf(0.001))).isEqualTo(0);
+
+ weight = VoteWeight.of(UnsignedInteger.valueOf(999));
+ assertThat(weight.bigDecimalValue().compareTo(BigDecimal.valueOf(0.999))).isEqualTo(0);
+
+ weight = VoteWeight.of(UnsignedInteger.valueOf(99_000));
+ assertThat(weight.bigDecimalValue().compareTo(BigDecimal.valueOf(99.000))).isEqualTo(0);
+
+ weight = VoteWeight.of(UnsignedInteger.valueOf(100_000));
+ assertThat(weight.bigDecimalValue().compareTo(BigDecimal.valueOf(100.000))).isEqualTo(0);
+ }
+
+ @Test
+ void testToString() {
+ VoteWeight weight = VoteWeight.of(UnsignedInteger.ZERO);
+ assertThat(weight.toString()).isEqualTo(UnsignedInteger.ZERO.toString());
+ }
+
+ @Test
+ void testJson() throws JsonProcessingException, JSONException {
+ VoteWeight voteWeight = VoteWeight.of(UnsignedInteger.valueOf(1000));
+ VoteWeightWrapper wrapper = VoteWeightWrapper.of(voteWeight);
+
+ String json = "{\"voteWeight\": 1000}";
+ assertSerializesAndDeserializes(wrapper, json);
+ }
+
+ private void assertSerializesAndDeserializes(
+ VoteWeightWrapper wrapper,
+ String json
+ ) throws JsonProcessingException, JSONException {
+ String serialized = objectMapper.writeValueAsString(wrapper);
+ JSONAssert.assertEquals(json, serialized, JSONCompareMode.STRICT);
+ VoteWeightWrapper deserialized = objectMapper.readValue(
+ serialized, VoteWeightWrapper.class
+ );
+ Assertions.assertThat(deserialized).isEqualTo(wrapper);
+ }
+
+ @Value.Immutable
+ @JsonSerialize(as = ImmutableVoteWeightWrapper.class)
+ @JsonDeserialize(as = ImmutableVoteWeightWrapper.class)
+ interface VoteWeightWrapper {
+
+ static VoteWeightWrapper of(VoteWeight voteWeight) {
+ return ImmutableVoteWeightWrapper.builder().voteWeight(voteWeight).build();
+ }
+
+ VoteWeight voteWeight();
+
+ }
+}
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 764e91430..6f6447209 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
@@ -25,6 +25,7 @@
import org.json.JSONException;
import org.junit.jupiter.api.Test;
import org.xrpl.xrpl4j.crypto.keys.PublicKey;
+import org.xrpl.xrpl4j.crypto.signing.Signature;
import org.xrpl.xrpl4j.model.AbstractJsonTest;
import org.xrpl.xrpl4j.model.flags.AccountSetTransactionFlags;
import org.xrpl.xrpl4j.model.flags.TransactionFlags;
@@ -194,4 +195,34 @@ void testJsonWithZeroClearFlagAndSetFlag() throws JSONException, JsonProcessingE
assertCanSerializeAndDeserialize(accountSet, json);
}
+
+ @Test
+ void testJsonWithUnrecognizedClearAndSetFlag() throws JSONException, JsonProcessingException {
+ AccountSet accountSet = AccountSet.builder()
+ .account(Address.of("rhyg7sn3ZQj9aja9CBuLETFKdkG9Fte7ck"))
+ .fee(XrpCurrencyAmount.ofDrops(15))
+ .sequence(UnsignedInteger.valueOf(40232131))
+ .setFlagRawValue(UnsignedInteger.valueOf(4294967295L))
+ .clearFlagRawValue(UnsignedInteger.valueOf(4294967295L))
+ .signingPublicKey(PublicKey.fromBase16EncodedPublicKey(
+ "ED03FCED79BB3699089BC3F0F57BBD9F9ABA4772C20EC14BFAE908B4299344194A"
+ ))
+ .transactionSignature(Signature.fromBase16("8D0915449FB617234DD54E1BA79136B50C4439696BB4287F" +
+ "CA25717F3FD4875D5A1FEE6269B91D71D9306B48DECAAE1F1C91CAD1AAD0E0D683DC11E68212740E"))
+ .build();
+
+ String json = "{\n" +
+ " \"Account\": \"rhyg7sn3ZQj9aja9CBuLETFKdkG9Fte7ck\",\n" +
+ " \"Fee\": \"15\",\n" +
+ " \"Sequence\": 40232131,\n" +
+ " \"SetFlag\": 4294967295,\n" +
+ " \"ClearFlag\": 4294967295,\n" +
+ " \"SigningPubKey\": \"ED03FCED79BB3699089BC3F0F57BBD9F9ABA4772C20EC14BFAE908B4299344194A\",\n" +
+ " \"TransactionType\": \"AccountSet\",\n" +
+ " \"TxnSignature\": \"8D0915449FB617234DD54E1BA79136B50C4439696BB4287FCA25717F3FD4875D5A1FEE6269B" +
+ "91D71D9306B48DECAAE1F1C91CAD1AAD0E0D683DC11E68212740E\"" +
+ "}";
+
+ assertCanSerializeAndDeserialize(accountSet, json);
+ }
}
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 df7609290..6c7c39f87 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
@@ -20,14 +20,13 @@
* =========================LICENSE_END==================================
*/
-import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.google.common.io.BaseEncoding;
import com.google.common.primitives.UnsignedInteger;
import com.google.common.primitives.UnsignedLong;
import com.ripple.cryptoconditions.CryptoConditionReader;
-import com.ripple.cryptoconditions.PreimageSha256Fulfillment;
import com.ripple.cryptoconditions.der.DerEncodingException;
import org.json.JSONException;
import org.junit.jupiter.api.Test;
@@ -352,19 +351,46 @@ public void testEscrowFinishJsonWithNonZeroFlags()
}
@Test
- public void testEscrowFinishJsonWithFeeTooLow() {
- assertThrows(
- IllegalStateException.class,
- () -> EscrowFinish.builder()
- .account(Address.of("rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn"))
- .fee(XrpCurrencyAmount.ofDrops(3))
- .sequence(UnsignedInteger.ONE)
- .owner(Address.of("rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn"))
- .offerSequence(UnsignedInteger.valueOf(7))
- .condition(PreimageSha256Fulfillment.from(new byte[48]).getDerivedCondition())
- .fulfillment(PreimageSha256Fulfillment.from(new byte[60]))
- .build(),
- "If a fulfillment is specified, the fee must be set to 330 or greater."
- );
+ void testEscrowFinishJsonWithMalformedCondition() throws JsonProcessingException {
+ String json = "{\n" +
+ " \"Account\": \"rKWZ2fDqE5B9XorAcEQZD46H6HEdJQVNdb\",\n" +
+ " \"Condition\": \"A02580209423ED2EF4CACA8CA4AFC08D3F5EC60A545FD7A97E66E16EA0E2E2\",\n" +
+ " \"Fee\": \"563\",\n" +
+ " \"Fulfillment\": \"A02280203377850F1B3A4322F1562DF6F75D584596ABE5B9C76EEA8301F56CB942ACC69B\",\n" +
+ " \"LastLedgerSequence\": 40562057,\n" +
+ " \"OfferSequence\": 40403748,\n" +
+ " \"Owner\": \"r3iocgQwoGNCYyvvt8xuWv2XYXx7Z2gQqd\",\n" +
+ " \"Sequence\": 39899485,\n" +
+ " \"SigningPubKey\": \"ED09285829A011D520A1873A0E2F1014F5D6B66A6DDE6953FC02C8185EAFA6A1B0\",\n" +
+ " \"TransactionType\": \"EscrowFinish\",\n" +
+ " \"TxnSignature\": \"A3E64F6B8D1D7C4FBC9663FCD217F86C3529EC2E2F16442DD48D1B66EEE30EAC2CE960A76080F74BC749" +
+ "8CA7BBFB822BEE9E8F767114D7B54F7403A7CB672501\"\n" +
+ "}";
+
+ EscrowFinish escrowFinish = objectMapper.readValue(json, EscrowFinish.class);
+ assertThat(escrowFinish.condition()).isEmpty();
+ assertThat(escrowFinish.conditionRawValue()).isNotEmpty().get()
+ .isEqualTo("A02580209423ED2EF4CACA8CA4AFC08D3F5EC60A545FD7A97E66E16EA0E2E2");
+ }
+
+ @Test
+ void testEscrowFinishJsonWithMalformedFulfillment() throws JsonProcessingException {
+ String json = String.format("{\n" +
+ " \"Account\": \"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn\",\n" +
+ " \"TransactionType\": \"EscrowFinish\",\n" +
+ " \"Owner\": \"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn\",\n" +
+ " \"OfferSequence\": 7,\n" +
+ " \"Condition\": \"A0258020E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855810100\",\n" +
+ " \"Fulfillment\": \"123\",\n" +
+ " \"Sequence\": 1,\n" +
+ " \"Flags\": %s,\n" +
+ " \"SigningPubKey\" : \"02356E89059A75438887F9FEE2056A2890DB82A68353BE9C0C0C8F89C0018B37FC\",\n" +
+ " \"Fee\": \"330\"\n" +
+ "}", TransactionFlags.FULLY_CANONICAL_SIG.getValue());
+
+ EscrowFinish escrowFinish = objectMapper.readValue(json, EscrowFinish.class);
+ assertThat(escrowFinish.fulfillment()).isEmpty();
+ assertThat(escrowFinish.fulfillmentRawValue()).isNotEmpty().get()
+ .isEqualTo("123");
}
}
diff --git a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/metadata/MetaLedgerEntryTypeTest.java b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/metadata/MetaLedgerEntryTypeTest.java
index fbabdae69..03ce8f461 100644
--- a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/metadata/MetaLedgerEntryTypeTest.java
+++ b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/metadata/MetaLedgerEntryTypeTest.java
@@ -29,6 +29,7 @@ void testConstants() {
assertThat(MetaLedgerEntryType.SIGNER_LIST.value()).isEqualTo("SignerList");
assertThat(MetaLedgerEntryType.TICKET.value()).isEqualTo("Ticket");
assertThat(MetaLedgerEntryType.NFTOKEN_PAGE.value()).isEqualTo("NFTokenPage");
+ assertThat(MetaLedgerEntryType.AMM.value()).isEqualTo("AMM");
}
@Test
diff --git a/xrpl4j-core/src/test/resources/codec-fixtures.json b/xrpl4j-core/src/test/resources/codec-fixtures.json
index 92ef8d945..e05cff35f 100644
--- a/xrpl4j-core/src/test/resources/codec-fixtures.json
+++ b/xrpl4j-core/src/test/resources/codec-fixtures.json
@@ -33,9 +33,7 @@
"LedgerEntryType": "DirectoryNode",
"Flags": 0,
"RootIndex": "059D1E86DE5DCCCF956BF4799675B2425AF9AD44FE4CCA6FEE1C812EEF6423E6",
- "Indexes": [
- "908D554AA0D29F660716A3EE65C61DD886B744DDF60DE70E6B16EADB770635DB"
- ]
+ "Indexes": ["908D554AA0D29F660716A3EE65C61DD886B744DDF60DE70E6B16EADB770635DB"]
}
},
{
@@ -329,9 +327,7 @@
"LedgerEntryType": "DirectoryNode",
"Flags": 0,
"RootIndex": "17CC40C6872E0C0E658C49B75D0812A70D4161DDA53324DF51FA58D3819C814B",
- "Indexes": [
- "571BF14F28C4D97871CDACD344A8CF57E6BA287BF0440B9E0D0683D02751CC7B"
- ]
+ "Indexes": ["571BF14F28C4D97871CDACD344A8CF57E6BA287BF0440B9E0D0683D02751CC7B"]
}
},
{
@@ -380,9 +376,7 @@
"LedgerEntryType": "DirectoryNode",
"Flags": 0,
"RootIndex": "1BCA9161A199AD5E907751CBF3FBA49689D517F0E8EE823AE17B737039B41DE1",
- "Indexes": [
- "26B894EE68470AD5AEEB55D5EBF936E6397CEE6957B93C56A2E7882CA9082873"
- ]
+ "Indexes": ["26B894EE68470AD5AEEB55D5EBF936E6397CEE6957B93C56A2E7882CA9082873"]
}
},
{
@@ -446,9 +440,7 @@
"LedgerEntryType": "DirectoryNode",
"Flags": 0,
"RootIndex": "1F9FF48419CA69FDDCC294CCEEE608F5F8A8BE11E286AD5743ED2D457C5570C4",
- "Indexes": [
- "7D4325BE338A40BBCBCC1F351B3272EB3E76305A878E76603DE206A795871619"
- ]
+ "Indexes": ["7D4325BE338A40BBCBCC1F351B3272EB3E76305A878E76603DE206A795871619"]
}
},
{
@@ -614,9 +606,7 @@
"LedgerEntryType": "DirectoryNode",
"Flags": 0,
"RootIndex": "289CFC476B5876F28C8A3B3C5B7058EC2BDF668C37B846EA7E5E1A73A4AA0816",
- "Indexes": [
- "BC10E40AFB79298004CDE51CB065DBDCABA86EC406E3A1CF02CE5F8A9628A2BD"
- ]
+ "Indexes": ["BC10E40AFB79298004CDE51CB065DBDCABA86EC406E3A1CF02CE5F8A9628A2BD"]
}
},
{
@@ -719,9 +709,7 @@
"LedgerEntryType": "DirectoryNode",
"Flags": 0,
"RootIndex": "2C9F00EFA5CCBD43452EF364B12C8DFCEF2B910336E5EFCE3AA412A556991582",
- "Indexes": [
- "F721E924498EE68BFF906CD856E8332073DD350BAC9E8977AC3F31860BA1E33A"
- ]
+ "Indexes": ["F721E924498EE68BFF906CD856E8332073DD350BAC9E8977AC3F31860BA1E33A"]
}
},
{
@@ -758,9 +746,7 @@
"LedgerEntryType": "DirectoryNode",
"Flags": 0,
"RootIndex": "2FB4904ACFB96228FC002335B1B5A4C5584D9D727BBE82144F0415EB4EA0C727",
- "Indexes": [
- "5F22826818CC83448C9DF34939AB4019D3F80C70DEB8BDBDCF0496A36DC68719"
- ],
+ "Indexes": ["5F22826818CC83448C9DF34939AB4019D3F80C70DEB8BDBDCF0496A36DC68719"],
"TakerPaysIssuer": "2B6C42A95B3F7EE1971E4A10098E8F1B5F66AA08"
}
},
@@ -774,9 +760,7 @@
"LedgerEntryType": "DirectoryNode",
"Flags": 0,
"RootIndex": "2FB4904ACFB96228FC002335B1B5A4C5584D9D727BBE82145003BAF82D03A000",
- "Indexes": [
- "5B7F148A8DDB4EB7386C9E75C4C1ED918DEDE5C52D5BA51B694D7271EF8BDB46"
- ],
+ "Indexes": ["5B7F148A8DDB4EB7386C9E75C4C1ED918DEDE5C52D5BA51B694D7271EF8BDB46"],
"TakerPaysIssuer": "2B6C42A95B3F7EE1971E4A10098E8F1B5F66AA08"
}
},
@@ -967,9 +951,7 @@
"LedgerEntryType": "DirectoryNode",
"Flags": 0,
"RootIndex": "3F2BADB38F12C87D111D3970CD1F05FE698DB86F14DC7C5FAEB05BFB6391B00E",
- "Indexes": [
- "73E075E64CA5E7CE60FFCD5359C1D730EDFFEE7C4D992760A87DF7EA0A34E40F"
- ]
+ "Indexes": ["73E075E64CA5E7CE60FFCD5359C1D730EDFFEE7C4D992760A87DF7EA0A34E40F"]
}
},
{
@@ -994,9 +976,7 @@
"LedgerEntryType": "DirectoryNode",
"Flags": 0,
"RootIndex": "4235CD082112FB621C02D6DA2E4F4ACFAFC91CB0585E034B936C29ABF4A76B01",
- "Indexes": [
- "6C4C3F1C6B9D76A6EF50F377E7C3991825694C604DBE0C1DD09362045EE41997"
- ]
+ "Indexes": ["6C4C3F1C6B9D76A6EF50F377E7C3991825694C604DBE0C1DD09362045EE41997"]
}
},
{
@@ -1095,9 +1075,7 @@
"LedgerEntryType": "DirectoryNode",
"Flags": 0,
"RootIndex": "48E91FD14597FB089654DADE7B70EB08CAF421EA611D703F3E871F7D4B5AAB5D",
- "Indexes": [
- "25DCAC87FBE4C3B66A1AFDE3C3F98E5A16333975C4FD46682F7497F27DFB9766"
- ]
+ "Indexes": ["25DCAC87FBE4C3B66A1AFDE3C3F98E5A16333975C4FD46682F7497F27DFB9766"]
}
},
{
@@ -1159,9 +1137,7 @@
"LedgerEntryType": "DirectoryNode",
"Flags": 0,
"RootIndex": "4EFC0442D07AE681F7FDFAA89C75F06F8E28CFF888593440201B0320E8F2C7BD",
- "Indexes": [
- "1595E5D5197330F58A479200A2FDD434D7A244BD1FFEC5E5EE8CF064AE77D3F5"
- ]
+ "Indexes": ["1595E5D5197330F58A479200A2FDD434D7A244BD1FFEC5E5EE8CF064AE77D3F5"]
}
},
{
@@ -1375,9 +1351,7 @@
"LedgerEntryType": "DirectoryNode",
"Flags": 0,
"RootIndex": "98082E695CAB618590BEEA0647A5F24D2B610A686ECD49310604FC7431FAAB0D",
- "Indexes": [
- "9BF3216E42575CA5A3CB4D0F2021EE81D0F7835BA2EDD78E05CAB44B655962BB"
- ]
+ "Indexes": ["9BF3216E42575CA5A3CB4D0F2021EE81D0F7835BA2EDD78E05CAB44B655962BB"]
}
},
{
@@ -1582,9 +1556,7 @@
"LedgerEntryType": "DirectoryNode",
"Flags": 0,
"RootIndex": "62AE37A44FE44BDCFC2BA5DD14D74BEC0AC346DA2DC1F04756044364C5BB0000",
- "Indexes": [
- "600A398F57CAE44461B4C8C25DE12AC289F87ED125438440B33B97417FE3D82C"
- ],
+ "Indexes": ["600A398F57CAE44461B4C8C25DE12AC289F87ED125438440B33B97417FE3D82C"],
"TakerPaysIssuer": "2B6C42A95B3F7EE1971E4A10098E8F1B5F66AA08"
}
},
@@ -1960,9 +1932,7 @@
"LedgerEntryType": "DirectoryNode",
"Flags": 0,
"RootIndex": "72D60CCD3905A3ABE19049B6EE76E8E0F3A2CBAC852625C757176F1B73EF617F",
- "Indexes": [
- "AB124EEAB087452070EC70D9DEA1A22C9766FFBBEE1025FD46495CC74148CCA8"
- ]
+ "Indexes": ["AB124EEAB087452070EC70D9DEA1A22C9766FFBBEE1025FD46495CC74148CCA8"]
}
},
{
@@ -2091,9 +2061,7 @@
"LedgerEntryType": "DirectoryNode",
"Flags": 0,
"RootIndex": "80AB25842B230D48027800213EB86023A3EAF4430E22C092D333795FFF1E5219",
- "Indexes": [
- "42E28285A82D01DCA856118A064C8AEEE1BF8167C08186DA5BFC678687E86F7C"
- ]
+ "Indexes": ["42E28285A82D01DCA856118A064C8AEEE1BF8167C08186DA5BFC678687E86F7C"]
}
},
{
@@ -2218,9 +2186,7 @@
"LedgerEntryType": "DirectoryNode",
"Flags": 0,
"RootIndex": "8ADF3C5527CCF6D0B5863365EF40254171536C3901F1CBD9E2BC5F918A7D492A",
- "Indexes": [
- "BC10E40AFB79298004CDE51CB065DBDCABA86EC406E3A1CF02CE5F8A9628A2BD"
- ]
+ "Indexes": ["BC10E40AFB79298004CDE51CB065DBDCABA86EC406E3A1CF02CE5F8A9628A2BD"]
}
},
{
@@ -2621,9 +2587,7 @@
"LedgerEntryType": "DirectoryNode",
"Flags": 0,
"RootIndex": "A00CD19C13A5CFA3FECB409D42B38017C07A4AEAE05A7A00347DDA17199BA683",
- "Indexes": [
- "E49318D6DF22411C3F35581B1D28297A36E47F68B45F36A587C156E6E43CE0A6"
- ]
+ "Indexes": ["E49318D6DF22411C3F35581B1D28297A36E47F68B45F36A587C156E6E43CE0A6"]
}
},
{
@@ -2670,9 +2634,7 @@
"LedgerEntryType": "DirectoryNode",
"Flags": 0,
"RootIndex": "A39F044D860C5B5846AA7E0FAAD44DC8897F0A62B2F628AA073B21B3EC146010",
- "Indexes": [
- "CD34D8FF7C656B66E2298DB420C918FE27DFFF2186AC8D1785D8CBF2C6BC3488"
- ]
+ "Indexes": ["CD34D8FF7C656B66E2298DB420C918FE27DFFF2186AC8D1785D8CBF2C6BC3488"]
}
},
{
@@ -2721,9 +2683,7 @@
"LedgerEntryType": "DirectoryNode",
"Flags": 0,
"RootIndex": "A7E461C6DC98F472991FDE51FADDC0082D755F553F5849875D554B52624EF1C3",
- "Indexes": [
- "116C6D5E5C6C59C9C5362B84CB9DD30BD3D4B7CB98CE993D49C068323BF19747"
- ]
+ "Indexes": ["116C6D5E5C6C59C9C5362B84CB9DD30BD3D4B7CB98CE993D49C068323BF19747"]
}
},
{
@@ -2757,9 +2717,7 @@
"LedgerEntryType": "DirectoryNode",
"Flags": 0,
"RootIndex": "AA539C8EECE0A0CFF0DBF3BFACD6B42CD4421715428AD90B034091BD3C721038",
- "Indexes": [
- "72307CB57E53604A0C50E653AB10E386F3835460B5585B70CB7F668C1E04AC8B"
- ]
+ "Indexes": ["72307CB57E53604A0C50E653AB10E386F3835460B5585B70CB7F668C1E04AC8B"]
}
},
{
@@ -3819,9 +3777,7 @@
"LedgerEntryType": "DirectoryNode",
"Flags": 0,
"RootIndex": "D4A00D9B3452C7F93C5F0531FA8FFB4599FEEC405CA803FBEFE0FA22137D863D",
- "Indexes": [
- "C1C5FB39D6C15C581D822DBAF725EF7EDE40BEC9F93C52398CF5CE9F64154D6C"
- ]
+ "Indexes": ["C1C5FB39D6C15C581D822DBAF725EF7EDE40BEC9F93C52398CF5CE9F64154D6C"]
}
},
{
@@ -3831,9 +3787,7 @@
"LedgerEntryType": "DirectoryNode",
"Flags": 0,
"RootIndex": "D4B68B54869E428428078E1045B8BB66C24DD101DB3FCCBB099929B3B63BCB40",
- "Indexes": [
- "9A551971E78FE2FB80D930A77EA0BAC2139A49D6BEB98406427C79F52A347A09"
- ]
+ "Indexes": ["9A551971E78FE2FB80D930A77EA0BAC2139A49D6BEB98406427C79F52A347A09"]
}
},
{
@@ -3908,9 +3862,7 @@
"LedgerEntryType": "DirectoryNode",
"Flags": 0,
"RootIndex": "DD23E2C60C9BC58180AC6EA7C668233EC51A0947E42FD1FAD4F5FBAED9698D95",
- "Indexes": [
- "908D554AA0D29F660716A3EE65C61DD886B744DDF60DE70E6B16EADB770635DB"
- ]
+ "Indexes": ["908D554AA0D29F660716A3EE65C61DD886B744DDF60DE70E6B16EADB770635DB"]
}
},
{
@@ -4061,9 +4013,7 @@
"LedgerEntryType": "DirectoryNode",
"Flags": 0,
"RootIndex": "E2EC9E1BC7B4667B7A5F2F68857F6E6A478A09B5BB4F99E09F694437C4152DED",
- "Indexes": [
- "65492B9F30F1CBEA168509128EB8619BAE02A7A7A4725FF3F8DAA70FA707A26E"
- ]
+ "Indexes": ["65492B9F30F1CBEA168509128EB8619BAE02A7A7A4725FF3F8DAA70FA707A26E"]
}
},
{
@@ -4240,9 +4190,7 @@
"IndexPrevious": "0000000000000002",
"Flags": 0,
"RootIndex": "8E92E688A132410427806A734DF6154B7535E439B72DECA5E4BC7CE17135C5A4",
- "Indexes": [
- "73E075E64CA5E7CE60FFCD5359C1D730EDFFEE7C4D992760A87DF7EA0A34E40F"
- ]
+ "Indexes": ["73E075E64CA5E7CE60FFCD5359C1D730EDFFEE7C4D992760A87DF7EA0A34E40F"]
}
},
{
@@ -4346,9 +4294,7 @@
"LedgerEntryType": "DirectoryNode",
"Flags": 0,
"RootIndex": "F774E0321809251174AC85531606FB46B75EEF9F842F9697531AA535D3D0C000",
- "Indexes": [
- "D1CB738BD08AC36DCB77191DB87C6E40FA478B86503371ED497F30931D7F4F52"
- ],
+ "Indexes": ["D1CB738BD08AC36DCB77191DB87C6E40FA478B86503371ED497F30931D7F4F52"],
"TakerPaysIssuer": "E8ACFC6B5EF4EA0601241525375162F43C2FF285"
}
},
@@ -4409,9 +4355,7 @@
"LedgerEntryType": "DirectoryNode",
"Flags": 0,
"RootIndex": "F95F6D3A1EF7981E5CA4D5AEC4DA63392B126C76469735BCCA26150A1AF6D9C3",
- "Indexes": [
- "CAD951AB279A749AE648FD1DFF56C021BD66E36187022E772C31FE52106CB13B"
- ]
+ "Indexes": ["CAD951AB279A749AE648FD1DFF56C021BD66E36187022E772C31FE52106CB13B"]
}
},
{
@@ -4491,36 +4435,239 @@
}
}
],
- "transactions": [
+ "transactions": [{
+ "binary": "1200002200000000240000003E6140000002540BE40068400000000000000A7321034AADB09CFF4A4804073701EC53C3510CDC95917C2BB0150FB742D0C66E6CEE9E74473045022022EB32AECEF7C644C891C19F87966DF9C62B1F34BABA6BE774325E4BB8E2DD62022100A51437898C28C2B297112DF8131F2BB39EA5FE613487DDD611525F17962646398114550FC62003E785DC231A1058A05E56E3F09CF4E68314D4CC8AB5B21D86A82C3E9E8D0ECF2404B77FECBA",
+ "json": {
+ "Account": "r3kmLJN5D28dHuH8vZNUZpMC43pEHpaocV",
+ "Destination": "rLQBHVhFnaC5gLEkgr6HgBJJ3bgeZHg9cj",
+ "TransactionType": "Payment",
+ "TxnSignature": "3045022022EB32AECEF7C644C891C19F87966DF9C62B1F34BABA6BE774325E4BB8E2DD62022100A51437898C28C2B297112DF8131F2BB39EA5FE613487DDD611525F1796264639",
+ "SigningPubKey": "034AADB09CFF4A4804073701EC53C3510CDC95917C2BB0150FB742D0C66E6CEE9E",
+ "Amount": "10000000000",
+ "Fee": "10",
+ "Flags": 0,
+ "Sequence": 62
+ }
+ },
+ {
+ "binary": "12002315000A2200000000240015DAE161400000000000271068400000000000000A6BD5838D7EA4C680000000000000000000000000004554480000000000FBEF9A3A2B814E807745FA3D9C32FFD155FA2E8C7321ED7453D2572A2104E7B266A45888C53F503CEB1F11DC4BB3710EB2995238EC65B87440B3154D968314FCEB58001E1B0C3A4CFB33DF9FF6C73207E5EAEB9BD07E2747672168E1A2786D950495C38BD8DEE3391BF45F3008DD36F4B12E7C07D82CA5250E8114F92F27CC5EE2F2760278FE096D0CBE32BDD3653A",
+ "json": {
+ "Account": "rP5ZkB5RZQaECsSVR4DeSFK4fAw52BYtbw",
+ "TransactionType": "AMMCreate",
+ "TxnSignature": "B3154D968314FCEB58001E1B0C3A4CFB33DF9FF6C73207E5EAEB9BD07E2747672168E1A2786D950495C38BD8DEE3391BF45F3008DD36F4B12E7C07D82CA5250E",
+ "Amount": "10000",
+ "Amount2": {
+ "currency": "ETH",
+ "issuer": "rPyfep3gcLzkosKC9XiE77Y8DZWG6iWDT9",
+ "value": "10000"
+ },
+ "TradingFee": 10,
+ "Fee": "10",
+ "Flags": 0,
+ "Sequence": 1432289,
+ "SigningPubKey": "ED7453D2572A2104E7B266A45888C53F503CEB1F11DC4BB3710EB2995238EC65B8"
+ }
+ },
{
- "binary": "1200002200000000240000003E6140000002540BE40068400000000000000A7321034AADB09CFF4A4804073701EC53C3510CDC95917C2BB0150FB742D0C66E6CEE9E74473045022022EB32AECEF7C644C891C19F87966DF9C62B1F34BABA6BE774325E4BB8E2DD62022100A51437898C28C2B297112DF8131F2BB39EA5FE613487DDD611525F17962646398114550FC62003E785DC231A1058A05E56E3F09CF4E68314D4CC8AB5B21D86A82C3E9E8D0ECF2404B77FECBA",
+ "binary": "1200242200010000240015DAE168400000000000000A6019D5438D7EA4C68000B3813FCAB4EE68B3D0D735D6849465A9113EE048B3813FCAB4EE68B3D0D735D6849465A9113EE0487321ED7453D2572A2104E7B266A45888C53F503CEB1F11DC4BB3710EB2995238EC65B874408073C588E7EF672DD171E414638D9AF8DBE9A1359E030DE3E1C9AA6A38A2CE9E138CB56482BB844F7228D48B1E4AD7D09BB7E9F639C115958EEEA374749CA00B8114F92F27CC5EE2F2760278FE096D0CBE32BDD3653A0318000000000000000000000000000000000000000004180000000000000000000000004554480000000000FBEF9A3A2B814E807745FA3D9C32FFD155FA2E8C",
"json": {
- "Account": "r3kmLJN5D28dHuH8vZNUZpMC43pEHpaocV",
- "Destination": "rLQBHVhFnaC5gLEkgr6HgBJJ3bgeZHg9cj",
- "TransactionType": "Payment",
- "TxnSignature": "3045022022EB32AECEF7C644C891C19F87966DF9C62B1F34BABA6BE774325E4BB8E2DD62022100A51437898C28C2B297112DF8131F2BB39EA5FE613487DDD611525F1796264639",
- "SigningPubKey": "034AADB09CFF4A4804073701EC53C3510CDC95917C2BB0150FB742D0C66E6CEE9E",
- "Amount": "10000000000",
+ "Account": "rP5ZkB5RZQaECsSVR4DeSFK4fAw52BYtbw",
+ "TransactionType": "AMMDeposit",
+ "TxnSignature": "8073C588E7EF672DD171E414638D9AF8DBE9A1359E030DE3E1C9AA6A38A2CE9E138CB56482BB844F7228D48B1E4AD7D09BB7E9F639C115958EEEA374749CA00B",
+ "Asset": {"currency": "XRP"},
+ "Asset2": {"currency": "ETH", "issuer": "rPyfep3gcLzkosKC9XiE77Y8DZWG6iWDT9"},
+ "LPTokenOut": {"currency": "B3813FCAB4EE68B3D0D735D6849465A9113EE048", "issuer": "rH438jEAzTs5PYtV6CHZqpDpwCKQmPW9Cg", "value": "1000"},
"Fee": "10",
- "Flags": 0,
- "Sequence": 62
+ "Flags": 65536,
+ "Sequence": 1432289,
+ "SigningPubKey": "ED7453D2572A2104E7B266A45888C53F503CEB1F11DC4BB3710EB2995238EC65B8"
}
- }
- ],
- "ledgerData": [
+ },
{
- "binary": "01E91435016340767BF1C4A3EACEB081770D8ADE216C85445DD6FB002C6B5A2930F2DECE006DA18150CB18F6DD33F6F0990754C962A7CCE62F332FF9C13939B03B864117F0BDA86B6E9B4F873B5C3E520634D343EF5D9D9A4246643D64DAD278BA95DC0EAC6EB5350CF970D521276CDE21276CE60A00",
+ "binary": "1200242200080000240015DAE16140000000000003E868400000000000000A7321ED7453D2572A2104E7B266A45888C53F503CEB1F11DC4BB3710EB2995238EC65B8744096CA066F42871C55088D2758D64148921B1ACAA5C6C648D0F7D675BBF47F87DF711F17C5BD172666D5AEC257520C587A849A6E063345609D91E121A78816EB048114F92F27CC5EE2F2760278FE096D0CBE32BDD3653A0318000000000000000000000000000000000000000004180000000000000000000000004554480000000000FBEF9A3A2B814E807745FA3D9C32FFD155FA2E8C",
"json": {
- "account_hash": "3B5C3E520634D343EF5D9D9A4246643D64DAD278BA95DC0EAC6EB5350CF970D5",
- "close_flags": 0,
- "close_time": 556231910,
- "close_time_resolution": 10,
- "ledger_index": 32052277,
- "parent_close_time": 556231902,
- "parent_hash": "EACEB081770D8ADE216C85445DD6FB002C6B5A2930F2DECE006DA18150CB18F6",
- "total_coins": "99994494362043555",
- "transaction_hash": "DD33F6F0990754C962A7CCE62F332FF9C13939B03B864117F0BDA86B6E9B4F87"
+ "Account": "rP5ZkB5RZQaECsSVR4DeSFK4fAw52BYtbw",
+ "TransactionType": "AMMDeposit",
+ "Asset": {"currency": "XRP"},
+ "Asset2": {"currency": "ETH", "issuer": "rPyfep3gcLzkosKC9XiE77Y8DZWG6iWDT9"},
+ "Amount": "1000",
+ "Fee": "10",
+ "Flags": 524288,
+ "Sequence": 1432289,
+ "SigningPubKey": "ED7453D2572A2104E7B266A45888C53F503CEB1F11DC4BB3710EB2995238EC65B8",
+ "TxnSignature": "96CA066F42871C55088D2758D64148921B1ACAA5C6C648D0F7D675BBF47F87DF711F17C5BD172666D5AEC257520C587A849A6E063345609D91E121A78816EB04"
}
+ },
+ {
+ "binary": "1200242200100000240015DAE16140000000000003E868400000000000000A6BD511C37937E080000000000000000000000000004554480000000000FBEF9A3A2B814E807745FA3D9C32FFD155FA2E8C7321ED7453D2572A2104E7B266A45888C53F503CEB1F11DC4BB3710EB2995238EC65B87440FC22B16A098C236ED7EDB3EBC983026DFD218A03C8BAA848F3E1D5389D5B8B00473C1178C5BA257BFA2DCD433C414690A430A5CFD71C1C0A7F7BF725EC1759018114F92F27CC5EE2F2760278FE096D0CBE32BDD3653A0318000000000000000000000000000000000000000004180000000000000000000000004554480000000000FBEF9A3A2B814E807745FA3D9C32FFD155FA2E8C",
+ "json": {
+ "Account": "rP5ZkB5RZQaECsSVR4DeSFK4fAw52BYtbw",
+ "TransactionType": "AMMDeposit",
+ "Asset": {"currency": "XRP"},
+ "Asset2": {"currency": "ETH", "issuer": "rPyfep3gcLzkosKC9XiE77Y8DZWG6iWDT9"},
+ "Amount": "1000",
+ "Amount2": {"currency": "ETH", "issuer": "rPyfep3gcLzkosKC9XiE77Y8DZWG6iWDT9", "value": "500"},
+ "Fee": "10",
+ "Flags": 1048576,
+ "Sequence": 1432289,
+ "SigningPubKey": "ED7453D2572A2104E7B266A45888C53F503CEB1F11DC4BB3710EB2995238EC65B8",
+ "TxnSignature": "FC22B16A098C236ED7EDB3EBC983026DFD218A03C8BAA848F3E1D5389D5B8B00473C1178C5BA257BFA2DCD433C414690A430A5CFD71C1C0A7F7BF725EC175901"
+ }
+ },
+ {
+ "binary": "1200242200200000240015DAE16140000000000003E868400000000000000A6019D5438D7EA4C68000B3813FCAB4EE68B3D0D735D6849465A9113EE048B3813FCAB4EE68B3D0D735D6849465A9113EE0487321ED7453D2572A2104E7B266A45888C53F503CEB1F11DC4BB3710EB2995238EC65B87440117CF90F9B113AD3BD638B6DB63562B37C287D5180F278B3CCF58FC14A5BAEE98307EA0F6DFE19E2FBA887C92955BA5D1A04F92ADAAEB309DE89C3610D074C098114F92F27CC5EE2F2760278FE096D0CBE32BDD3653A0318000000000000000000000000000000000000000004180000000000000000000000004554480000000000FBEF9A3A2B814E807745FA3D9C32FFD155FA2E8C",
+ "json": {
+ "Account": "rP5ZkB5RZQaECsSVR4DeSFK4fAw52BYtbw",
+ "TransactionType": "AMMDeposit",
+ "Asset": {"currency": "XRP"},
+ "Asset2": {"currency": "ETH", "issuer": "rPyfep3gcLzkosKC9XiE77Y8DZWG6iWDT9"},
+ "Amount": "1000",
+ "LPTokenOut": {"currency": "B3813FCAB4EE68B3D0D735D6849465A9113EE048", "issuer": "rH438jEAzTs5PYtV6CHZqpDpwCKQmPW9Cg", "value": "1000"},
+ "Fee": "10",
+ "Flags": 2097152,
+ "Sequence": 1432289,
+ "SigningPubKey": "ED7453D2572A2104E7B266A45888C53F503CEB1F11DC4BB3710EB2995238EC65B8",
+ "TxnSignature": "117CF90F9B113AD3BD638B6DB63562B37C287D5180F278B3CCF58FC14A5BAEE98307EA0F6DFE19E2FBA887C92955BA5D1A04F92ADAAEB309DE89C3610D074C09"
+ }
+ },
+ {
+ "binary": "1200242200400000240015DAE16140000000000003E868400000000000000A601B40000000000000197321ED7453D2572A2104E7B266A45888C53F503CEB1F11DC4BB3710EB2995238EC65B874405E51EBC6B52A7C3BA5D0AE2FC8F62E779B80182009B3108A87AB6D770D68F56053C193DB0640128E4765565970625B1E2878E116AC854E6DED412202CCDE0B0D8114F92F27CC5EE2F2760278FE096D0CBE32BDD3653A0318000000000000000000000000000000000000000004180000000000000000000000004554480000000000FBEF9A3A2B814E807745FA3D9C32FFD155FA2E8C",
+ "json": {
+ "Account": "rP5ZkB5RZQaECsSVR4DeSFK4fAw52BYtbw",
+ "TransactionType": "AMMDeposit",
+ "Asset": {"currency": "XRP"},
+ "Asset2": {"currency": "ETH", "issuer": "rPyfep3gcLzkosKC9XiE77Y8DZWG6iWDT9"},
+ "Amount": "1000",
+ "EPrice": "25",
+ "Fee": "10",
+ "Flags": 4194304,
+ "Sequence": 1432289,
+ "SigningPubKey": "ED7453D2572A2104E7B266A45888C53F503CEB1F11DC4BB3710EB2995238EC65B8",
+ "TxnSignature": "5E51EBC6B52A7C3BA5D0AE2FC8F62E779B80182009B3108A87AB6D770D68F56053C193DB0640128E4765565970625B1E2878E116AC854E6DED412202CCDE0B0D"
+ }
+ },
+ {
+ "binary": "1200252200010000240015DAE168400000000000000A601AD5438D7EA4C68000B3813FCAB4EE68B3D0D735D6849465A9113EE048B3813FCAB4EE68B3D0D735D6849465A9113EE0487321ED7453D2572A2104E7B266A45888C53F503CEB1F11DC4BB3710EB2995238EC65B874409D4F41FC452526C0AD17191959D9B6D04A3C73B3A6C29E0F34C8459675A83A7A7D6E3021390EC8C9BE6C93E11C167E12016465E523F64F9EB3194B0A52E418028114F92F27CC5EE2F2760278FE096D0CBE32BDD3653A0318000000000000000000000000000000000000000004180000000000000000000000004554480000000000FBEF9A3A2B814E807745FA3D9C32FFD155FA2E8C",
+ "json": {
+ "Account": "rP5ZkB5RZQaECsSVR4DeSFK4fAw52BYtbw",
+ "TransactionType": "AMMWithdraw",
+ "Asset": {"currency": "XRP"},
+ "Asset2": {"currency": "ETH", "issuer": "rPyfep3gcLzkosKC9XiE77Y8DZWG6iWDT9"},
+ "LPTokenIn": {"currency": "B3813FCAB4EE68B3D0D735D6849465A9113EE048", "issuer": "rH438jEAzTs5PYtV6CHZqpDpwCKQmPW9Cg", "value": "1000"},
+ "Fee": "10",
+ "Flags": 65536,
+ "Sequence": 1432289,
+ "SigningPubKey": "ED7453D2572A2104E7B266A45888C53F503CEB1F11DC4BB3710EB2995238EC65B8",
+ "TxnSignature": "9D4F41FC452526C0AD17191959D9B6D04A3C73B3A6C29E0F34C8459675A83A7A7D6E3021390EC8C9BE6C93E11C167E12016465E523F64F9EB3194B0A52E41802"
+ }
+ },
+ {
+ "binary": "1200252200080000240015DAE16140000000000003E868400000000000000A7321ED7453D2572A2104E7B266A45888C53F503CEB1F11DC4BB3710EB2995238EC65B87440E2C60D56C337D6D73E4B7D53579C93C666605494E82A89DD58CFDE79E2A4866BCF52370A2146877A2EF748E98168373710001133A51B645D89491849079035018114F92F27CC5EE2F2760278FE096D0CBE32BDD3653A0318000000000000000000000000000000000000000004180000000000000000000000004554480000000000FBEF9A3A2B814E807745FA3D9C32FFD155FA2E8C",
+ "json": {
+ "Account": "rP5ZkB5RZQaECsSVR4DeSFK4fAw52BYtbw",
+ "TransactionType": "AMMWithdraw",
+ "Asset": {"currency": "XRP"},
+ "Asset2": {"currency": "ETH", "issuer": "rPyfep3gcLzkosKC9XiE77Y8DZWG6iWDT9"},
+ "Amount": "1000",
+ "Fee": "10",
+ "Flags": 524288,
+ "Sequence": 1432289,
+ "SigningPubKey": "ED7453D2572A2104E7B266A45888C53F503CEB1F11DC4BB3710EB2995238EC65B8",
+ "TxnSignature": "E2C60D56C337D6D73E4B7D53579C93C666605494E82A89DD58CFDE79E2A4866BCF52370A2146877A2EF748E98168373710001133A51B645D8949184907903501"
+ }
+ },
+ {
+ "binary": "1200252200100000240015DAE16140000000000003E868400000000000000A6BD511C37937E080000000000000000000000000004554480000000000FBEF9A3A2B814E807745FA3D9C32FFD155FA2E8C7321ED7453D2572A2104E7B266A45888C53F503CEB1F11DC4BB3710EB2995238EC65B87440D2FCD7D03E53358BC6188BA88A7BA4FF2519B639C3B5C0EBCBDCB704426CA2837111430E92A6003D1CD0D81C63682C74839320539EC4F89B82AA5607714952028114F92F27CC5EE2F2760278FE096D0CBE32BDD3653A0318000000000000000000000000000000000000000004180000000000000000000000004554480000000000FBEF9A3A2B814E807745FA3D9C32FFD155FA2E8C",
+ "json": {
+ "Account": "rP5ZkB5RZQaECsSVR4DeSFK4fAw52BYtbw",
+ "TransactionType": "AMMWithdraw",
+ "Asset": {"currency": "XRP"},
+ "Asset2": {"currency": "ETH", "issuer": "rPyfep3gcLzkosKC9XiE77Y8DZWG6iWDT9"},
+ "Amount": "1000",
+ "Amount2": {"currency": "ETH", "issuer": "rPyfep3gcLzkosKC9XiE77Y8DZWG6iWDT9", "value": "500"},
+ "Fee": "10",
+ "Flags": 1048576,
+ "Sequence": 1432289,
+ "SigningPubKey": "ED7453D2572A2104E7B266A45888C53F503CEB1F11DC4BB3710EB2995238EC65B8",
+ "TxnSignature": "D2FCD7D03E53358BC6188BA88A7BA4FF2519B639C3B5C0EBCBDCB704426CA2837111430E92A6003D1CD0D81C63682C74839320539EC4F89B82AA560771495202"
+ }
+ },
+ {
+ "binary": "1200252200200000240015DAE16140000000000003E868400000000000000A601AD5438D7EA4C68000B3813FCAB4EE68B3D0D735D6849465A9113EE048B3813FCAB4EE68B3D0D735D6849465A9113EE0487321ED7453D2572A2104E7B266A45888C53F503CEB1F11DC4BB3710EB2995238EC65B8744042DA5620E924E2D2059BBB4E0C4F03244140ACED93B543136FEEDF802165F814D09F45C7E2A4618468442516F4712A23B1D3332D5DBDBAE830337F39F259C90F8114F92F27CC5EE2F2760278FE096D0CBE32BDD3653A0318000000000000000000000000000000000000000004180000000000000000000000004554480000000000FBEF9A3A2B814E807745FA3D9C32FFD155FA2E8C",
+ "json": {
+ "Account": "rP5ZkB5RZQaECsSVR4DeSFK4fAw52BYtbw",
+ "TransactionType": "AMMWithdraw",
+ "Asset": {"currency": "XRP"},
+ "Asset2": {"currency": "ETH", "issuer": "rPyfep3gcLzkosKC9XiE77Y8DZWG6iWDT9"},
+ "Amount": "1000",
+ "LPTokenIn": {"currency": "B3813FCAB4EE68B3D0D735D6849465A9113EE048", "issuer": "rH438jEAzTs5PYtV6CHZqpDpwCKQmPW9Cg", "value": "1000"},
+ "Fee": "10",
+ "Flags": 2097152,
+ "Sequence": 1432289,
+ "SigningPubKey": "ED7453D2572A2104E7B266A45888C53F503CEB1F11DC4BB3710EB2995238EC65B8",
+ "TxnSignature": "42DA5620E924E2D2059BBB4E0C4F03244140ACED93B543136FEEDF802165F814D09F45C7E2A4618468442516F4712A23B1D3332D5DBDBAE830337F39F259C90F"
+ }
+ },
+ {
+ "binary": "1200252200400000240015DAE16140000000000003E868400000000000000A601B40000000000000197321ED7453D2572A2104E7B266A45888C53F503CEB1F11DC4BB3710EB2995238EC65B8744045BCEE5A12E5F5F1FB085A24F2F7FD962BBCB0D89A44A5319E3F7E3799E1870341880B6F684132971DDDF2E6B15356B3F407962D6D4E8DE10989F3B16E3CB90D8114F92F27CC5EE2F2760278FE096D0CBE32BDD3653A0318000000000000000000000000000000000000000004180000000000000000000000004554480000000000FBEF9A3A2B814E807745FA3D9C32FFD155FA2E8C",
+ "json": {
+ "Account": "rP5ZkB5RZQaECsSVR4DeSFK4fAw52BYtbw",
+ "TransactionType": "AMMWithdraw",
+ "Asset": {"currency": "XRP"},
+ "Asset2": {"currency": "ETH", "issuer": "rPyfep3gcLzkosKC9XiE77Y8DZWG6iWDT9"},
+ "Amount": "1000",
+ "EPrice": "25",
+ "Fee": "10",
+ "Flags": 4194304,
+ "Sequence": 1432289,
+ "SigningPubKey": "ED7453D2572A2104E7B266A45888C53F503CEB1F11DC4BB3710EB2995238EC65B8",
+ "TxnSignature": "45BCEE5A12E5F5F1FB085A24F2F7FD962BBCB0D89A44A5319E3F7E3799E1870341880B6F684132971DDDF2E6B15356B3F407962D6D4E8DE10989F3B16E3CB90D"
+ }
+ },
+ {
+ "binary": "1200272200000000240015DAE168400000000000000A6CD4C8E1BC9BF04000B3813FCAB4EE68B3D0D735D6849465A9113EE048B3813FCAB4EE68B3D0D735D6849465A9113EE0486DD4CC6F3B40B6C000B3813FCAB4EE68B3D0D735D6849465A9113EE048B3813FCAB4EE68B3D0D735D6849465A9113EE0487321ED7453D2572A2104E7B266A45888C53F503CEB1F11DC4BB3710EB2995238EC65B87440F8EAAFB5EC1A69275167589969F0B9764BACE6BC8CC81482C2FC5ACCE691EDBD0D88D141137B1253BB1B9AC90A8A52CB37F5B6F7E1028B06DD06F91BE06F5A0F8114F92F27CC5EE2F2760278FE096D0CBE32BDD3653AF019E01B81149A91957F8F16BC57F3F200CD8C98375BF1791586E1F10318000000000000000000000000000000000000000004180000000000000000000000004554480000000000FBEF9A3A2B814E807745FA3D9C32FFD155FA2E8C",
+ "json": {
+ "Account": "rP5ZkB5RZQaECsSVR4DeSFK4fAw52BYtbw",
+ "TransactionType": "AMMBid",
+ "Asset": {"currency": "XRP"},
+ "Asset2": {"currency": "ETH", "issuer": "rPyfep3gcLzkosKC9XiE77Y8DZWG6iWDT9"},
+ "AuthAccounts": [{"AuthAccount": {"Account": "rEaHTti4HZsMBpxTAF4ncWxkcdqDh1h6P7"}}],
+ "BidMax": {"currency": "B3813FCAB4EE68B3D0D735D6849465A9113EE048", "issuer": "rH438jEAzTs5PYtV6CHZqpDpwCKQmPW9Cg", "value": "35"},
+ "BidMin": {"currency": "B3813FCAB4EE68B3D0D735D6849465A9113EE048", "issuer": "rH438jEAzTs5PYtV6CHZqpDpwCKQmPW9Cg", "value": "25"},
+ "Fee": "10",
+ "Flags": 0,
+ "Sequence": 1432289,
+ "SigningPubKey": "ED7453D2572A2104E7B266A45888C53F503CEB1F11DC4BB3710EB2995238EC65B8",
+ "TxnSignature": "F8EAAFB5EC1A69275167589969F0B9764BACE6BC8CC81482C2FC5ACCE691EDBD0D88D141137B1253BB1B9AC90A8A52CB37F5B6F7E1028B06DD06F91BE06F5A0F"
+ }
+ },
+ {
+ "binary": "1200261500EA2200000000240015DAE168400000000000000A7321ED7453D2572A2104E7B266A45888C53F503CEB1F11DC4BB3710EB2995238EC65B87440BC2F6E76969E3747E9BDE183C97573B086212F09D5387460E6EE2F32953E85EAEB9618FBBEF077276E30E59D619FCF7C7BDCDDDD9EB94D7CE1DD5CE9246B21078114F92F27CC5EE2F2760278FE096D0CBE32BDD3653A0318000000000000000000000000000000000000000004180000000000000000000000004554480000000000FBEF9A3A2B814E807745FA3D9C32FFD155FA2E8C",
+ "json": {
+ "Account": "rP5ZkB5RZQaECsSVR4DeSFK4fAw52BYtbw",
+ "TransactionType": "AMMVote",
+ "Asset": {"currency": "XRP"},
+ "Asset2": {"currency": "ETH", "issuer": "rPyfep3gcLzkosKC9XiE77Y8DZWG6iWDT9"},
+ "TradingFee": 234,
+ "Fee": "10",
+ "Flags": 0,
+ "Sequence": 1432289,
+ "SigningPubKey": "ED7453D2572A2104E7B266A45888C53F503CEB1F11DC4BB3710EB2995238EC65B8",
+ "TxnSignature": "BC2F6E76969E3747E9BDE183C97573B086212F09D5387460E6EE2F32953E85EAEB9618FBBEF077276E30E59D619FCF7C7BDCDDDD9EB94D7CE1DD5CE9246B2107"
+ }
+ }],
+ "ledgerData": [{
+ "binary": "01E91435016340767BF1C4A3EACEB081770D8ADE216C85445DD6FB002C6B5A2930F2DECE006DA18150CB18F6DD33F6F0990754C962A7CCE62F332FF9C13939B03B864117F0BDA86B6E9B4F873B5C3E520634D343EF5D9D9A4246643D64DAD278BA95DC0EAC6EB5350CF970D521276CDE21276CE60A00",
+ "json": {
+ "account_hash": "3B5C3E520634D343EF5D9D9A4246643D64DAD278BA95DC0EAC6EB5350CF970D5",
+ "close_flags": 0,
+ "close_time": 556231910,
+ "close_time_resolution": 10,
+ "ledger_index": 32052277,
+ "parent_close_time": 556231902,
+ "parent_hash": "EACEB081770D8ADE216C85445DD6FB002C6B5A2930F2DECE006DA18150CB18F6",
+ "total_coins": "99994494362043555",
+ "transaction_hash": "DD33F6F0990754C962A7CCE62F332FF9C13939B03B864117F0BDA86B6E9B4F87"
}
- ]
-}
+ }]
+}
\ No newline at end of file
diff --git a/xrpl4j-core/src/test/resources/data-driven-tests.json b/xrpl4j-core/src/test/resources/data-driven-tests.json
index ed7647ea0..5e82b5a00 100644
--- a/xrpl4j-core/src/test/resources/data-driven-tests.json
+++ b/xrpl4j-core/src/test/resources/data-driven-tests.json
@@ -952,9 +952,7 @@
[
"TransactionType",
{
- "binary": [
- "0007"
- ],
+ "binary": ["0007"],
"json": "OfferCreate",
"field_header": "12"
}
@@ -962,9 +960,7 @@
[
"Flags",
{
- "binary": [
- "00000000"
- ],
+ "binary": ["00000000"],
"json": 0,
"field_header": "22"
}
@@ -972,9 +968,7 @@
[
"Sequence",
{
- "binary": [
- "000017B4"
- ],
+ "binary": ["000017B4"],
"json": 6068,
"field_header": "24"
}
@@ -982,9 +976,7 @@
[
"Expiration",
{
- "binary": [
- "535A8CF1"
- ],
+ "binary": ["535A8CF1"],
"json": 1398443249,
"field_header": "2A"
}
@@ -992,9 +984,7 @@
[
"TakerPays",
{
- "binary": [
- "4000000006084340"
- ],
+ "binary": ["4000000006084340"],
"json": "101204800",
"field_header": "64"
}
@@ -1018,9 +1008,7 @@
[
"Fee",
{
- "binary": [
- "400000000000000C"
- ],
+ "binary": ["400000000000000C"],
"json": "12",
"field_header": "68"
}
@@ -1028,9 +1016,7 @@
[
"Account",
{
- "binary": [
- "AD6E583D47F90F29FD8B23225E6F905602B0292E"
- ],
+ "binary": ["AD6E583D47F90F29FD8B23225E6F905602B0292E"],
"vl_length": "14",
"json": "rGFpans8aW7XZNEcNky6RHKyEdLvXPMnUn",
"field_header": "81"
@@ -1058,9 +1044,7 @@
[
"TransactionType",
{
- "binary": [
- "0007"
- ],
+ "binary": ["0007"],
"json": "OfferCreate",
"field_header": "12"
}
@@ -1068,9 +1052,7 @@
[
"Flags",
{
- "binary": [
- "00000000"
- ],
+ "binary": ["00000000"],
"json": 0,
"field_header": "22"
}
@@ -1078,9 +1060,7 @@
[
"Sequence",
{
- "binary": [
- "00005124"
- ],
+ "binary": ["00005124"],
"json": 20772,
"field_header": "24"
}
@@ -1088,9 +1068,7 @@
[
"LastLedgerSequence",
{
- "binary": [
- "005EE8EC"
- ],
+ "binary": ["005EE8EC"],
"json": 6220012,
"field_header": "201B"
}
@@ -1098,9 +1076,7 @@
[
"TakerPays",
{
- "binary": [
- "4000000000140251"
- ],
+ "binary": ["4000000000140251"],
"json": "1311313",
"field_header": "64"
}
@@ -1124,9 +1100,7 @@
[
"Fee",
{
- "binary": [
- "400000000000000C"
- ],
+ "binary": ["400000000000000C"],
"json": "12",
"field_header": "68"
}
@@ -1134,9 +1108,7 @@
[
"Account",
{
- "binary": [
- "D0B32295596E50017E246FE85FC5982A1BD89CE4"
- ],
+ "binary": ["D0B32295596E50017E246FE85FC5982A1BD89CE4"],
"vl_length": "14",
"json": "rLpW9Reyn9YqZ8mxbq8nviXSp4TnHafVJQ",
"field_header": "81"
@@ -1150,14 +1122,10 @@
"TakerPays": "223174650",
"Account": "rPk2dXr27rMw9G5Ej9ad2Tt7RJzGy8ycBp",
"TransactionType": "OfferCreate",
- "Memos": [
- {
- "Memo": {
- "MemoType": "584D4D2076616C7565",
- "MemoData": "322E3230393635"
- }
- }
- ],
+ "Memos": [{"Memo": {
+ "MemoType": "584D4D2076616C7565",
+ "MemoData": "322E3230393635"
+ }}],
"Fee": "15",
"OfferSequence": 1002,
"TakerGets": {
@@ -1173,9 +1141,7 @@
[
"TransactionType",
{
- "binary": [
- "0007"
- ],
+ "binary": ["0007"],
"json": "OfferCreate",
"field_header": "12"
}
@@ -1183,9 +1149,7 @@
[
"Flags",
{
- "binary": [
- "00080000"
- ],
+ "binary": ["00080000"],
"json": 524288,
"field_header": "22"
}
@@ -1193,9 +1157,7 @@
[
"Sequence",
{
- "binary": [
- "000003EB"
- ],
+ "binary": ["000003EB"],
"json": 1003,
"field_header": "24"
}
@@ -1203,9 +1165,7 @@
[
"OfferSequence",
{
- "binary": [
- "000003EA"
- ],
+ "binary": ["000003EA"],
"json": 1002,
"field_header": "2019"
}
@@ -1213,9 +1173,7 @@
[
"LastLedgerSequence",
{
- "binary": [
- "005EE967"
- ],
+ "binary": ["005EE967"],
"json": 6220135,
"field_header": "201B"
}
@@ -1223,9 +1181,7 @@
[
"TakerPays",
{
- "binary": [
- "400000000D4D5FFA"
- ],
+ "binary": ["400000000D4D5FFA"],
"json": "223174650",
"field_header": "64"
}
@@ -1249,9 +1205,7 @@
[
"Fee",
{
- "binary": [
- "400000000000000F"
- ],
+ "binary": ["400000000000000F"],
"json": "15",
"field_header": "68"
}
@@ -1259,9 +1213,7 @@
[
"Account",
{
- "binary": [
- "F990B9E746546554A7B50A5E013BCB57095C6BB8"
- ],
+ "binary": ["F990B9E746546554A7B50A5E013BCB57095C6BB8"],
"vl_length": "14",
"json": "rPk2dXr27rMw9G5Ej9ad2Tt7RJzGy8ycBp",
"field_header": "81"
@@ -1280,14 +1232,10 @@
"322E3230393635",
"E1"
],
- "json": [
- {
- "Memo": {
- "MemoType": "584D4D2076616C7565",
- "MemoData": "322E3230393635"
- }
- }
- ],
+ "json": [{"Memo": {
+ "MemoType": "584D4D2076616C7565",
+ "MemoData": "322E3230393635"
+ }}],
"field_header": "F9"
}
]
@@ -1312,9 +1260,7 @@
[
"TransactionType",
{
- "binary": [
- "0007"
- ],
+ "binary": ["0007"],
"json": "OfferCreate",
"field_header": "12"
}
@@ -1322,9 +1268,7 @@
[
"Sequence",
{
- "binary": [
- "00080917"
- ],
+ "binary": ["00080917"],
"json": 526615,
"field_header": "24"
}
@@ -1332,9 +1276,7 @@
[
"OfferSequence",
{
- "binary": [
- "000808DA"
- ],
+ "binary": ["000808DA"],
"json": 526554,
"field_header": "2019"
}
@@ -1358,9 +1300,7 @@
[
"TakerGets",
{
- "binary": [
- "400000003C3945C2"
- ],
+ "binary": ["400000003C3945C2"],
"json": "1010386370",
"field_header": "65"
}
@@ -1368,9 +1308,7 @@
[
"Fee",
{
- "binary": [
- "4000000000000032"
- ],
+ "binary": ["4000000000000032"],
"json": "50",
"field_header": "68"
}
@@ -1378,9 +1316,7 @@
[
"Account",
{
- "binary": [
- "F4141D8B4EF33BC3EE224088CA418DFCD2847193"
- ],
+ "binary": ["F4141D8B4EF33BC3EE224088CA418DFCD2847193"],
"vl_length": "14",
"json": "rPEZyTnSyQyXBCwMVYyaafSVPL8oMtfG6a",
"field_header": "81"
@@ -1407,22 +1343,16 @@
},
"Flags": 0,
"Sequence": 6,
- "Paths": [
- [
- {
- "account": "razqQKzJRdB4UxFPWf5NEpEG3WMkmwgcXA"
- }
- ]
- ],
+ "Paths": [[{
+ "account": "razqQKzJRdB4UxFPWf5NEpEG3WMkmwgcXA"
+ }]],
"DestinationTag": 736049272
},
"fields": [
[
"TransactionType",
{
- "binary": [
- "0000"
- ],
+ "binary": ["0000"],
"json": "Payment",
"field_header": "12"
}
@@ -1430,9 +1360,7 @@
[
"Flags",
{
- "binary": [
- "00000000"
- ],
+ "binary": ["00000000"],
"json": 0,
"field_header": "22"
}
@@ -1440,9 +1368,7 @@
[
"Sequence",
{
- "binary": [
- "00000006"
- ],
+ "binary": ["00000006"],
"json": 6,
"field_header": "24"
}
@@ -1450,9 +1376,7 @@
[
"DestinationTag",
{
- "binary": [
- "2BDF3878"
- ],
+ "binary": ["2BDF3878"],
"json": 736049272,
"field_header": "2E"
}
@@ -1476,9 +1400,7 @@
[
"Fee",
{
- "binary": [
- "400000000000000C"
- ],
+ "binary": ["400000000000000C"],
"json": "12",
"field_header": "68"
}
@@ -1502,9 +1424,7 @@
[
"Account",
{
- "binary": [
- "B53847FA45E828BF9A52E38F7FB39E363493CE8B"
- ],
+ "binary": ["B53847FA45E828BF9A52E38F7FB39E363493CE8B"],
"vl_length": "14",
"json": "rHXUjUtk5eiPFYpg27izxHeZ1t4x835Ecn",
"field_header": "81"
@@ -1513,9 +1433,7 @@
[
"Destination",
{
- "binary": [
- "EE39E6D05CFD6A90DAB700A1D70149ECEE29DFEC"
- ],
+ "binary": ["EE39E6D05CFD6A90DAB700A1D70149ECEE29DFEC"],
"vl_length": "14",
"json": "r45dBj4S3VvMMYXxr9vHX4Z4Ma6ifPMCkK",
"field_header": "83"
@@ -1529,13 +1447,9 @@
"41C8BE2C0A6AA17471B9F6D0AF92AAB1C94D5A25",
"00"
],
- "json": [
- [
- {
- "account": "razqQKzJRdB4UxFPWf5NEpEG3WMkmwgcXA"
- }
- ]
- ],
+ "json": [[{
+ "account": "razqQKzJRdB4UxFPWf5NEpEG3WMkmwgcXA"
+ }]],
"field_header": "0112"
}
]
@@ -1565,9 +1479,7 @@
[
"TransactionType",
{
- "binary": [
- "0000"
- ],
+ "binary": ["0000"],
"json": "Payment",
"field_header": "12"
}
@@ -1575,9 +1487,7 @@
[
"Flags",
{
- "binary": [
- "80000000"
- ],
+ "binary": ["80000000"],
"json": 2147483648,
"field_header": "22"
}
@@ -1585,9 +1495,7 @@
[
"Sequence",
{
- "binary": [
- "000054C7"
- ],
+ "binary": ["000054C7"],
"json": 21703,
"field_header": "24"
}
@@ -1611,9 +1519,7 @@
[
"Fee",
{
- "binary": [
- "400000000000000A"
- ],
+ "binary": ["400000000000000A"],
"json": "10",
"field_header": "68"
}
@@ -1637,9 +1543,7 @@
[
"Account",
{
- "binary": [
- "F7B414E9D25EE050553D8A0BB27202F4249AD328"
- ],
+ "binary": ["F7B414E9D25EE050553D8A0BB27202F4249AD328"],
"vl_length": "14",
"json": "rP2jdgJhtY1pwDJQEMLfCixesg4cw8HcrW",
"field_header": "81"
@@ -1648,9 +1552,7 @@
[
"Destination",
{
- "binary": [
- "B83EB506BBE5BCF3E89C638FDB185B1DEAC96584"
- ],
+ "binary": ["B83EB506BBE5BCF3E89C638FDB185B1DEAC96584"],
"vl_length": "14",
"json": "rHoUTGMxWKbrTTF8tpAjysjpu8PWrbt1Wx",
"field_header": "83"
@@ -1673,9 +1575,7 @@
[
"TransactionType",
{
- "binary": [
- "0000"
- ],
+ "binary": ["0000"],
"json": "Payment",
"field_header": "12"
}
@@ -1683,9 +1583,7 @@
[
"Flags",
{
- "binary": [
- "00000000"
- ],
+ "binary": ["00000000"],
"json": 0,
"field_header": "22"
}
@@ -1693,9 +1591,7 @@
[
"Sequence",
{
- "binary": [
- "00000002"
- ],
+ "binary": ["00000002"],
"json": 2,
"field_header": "24"
}
@@ -1703,9 +1599,7 @@
[
"Amount",
{
- "binary": [
- "40000000017D7840"
- ],
+ "binary": ["40000000017D7840"],
"json": "25000000",
"field_header": "61"
}
@@ -1713,9 +1607,7 @@
[
"Fee",
{
- "binary": [
- "400000000000000A"
- ],
+ "binary": ["400000000000000A"],
"json": "10",
"field_header": "68"
}
@@ -1723,9 +1615,7 @@
[
"Account",
{
- "binary": [
- "5CCB151F6E9D603F394AE778ACF10D3BECE874F6"
- ],
+ "binary": ["5CCB151F6E9D603F394AE778ACF10D3BECE874F6"],
"vl_length": "14",
"json": "r9TeThyi5xiuUUrFjtPKZiHcDxs7K9H6Rb",
"field_header": "81"
@@ -1734,9 +1624,7 @@
[
"Destination",
{
- "binary": [
- "E851BBBE79E328E43D68F43445368133DF5FBA5A"
- ],
+ "binary": ["E851BBBE79E328E43D68F43445368133DF5FBA5A"],
"vl_length": "14",
"json": "r4BPgS7DHebQiU31xWELvZawwSG2fSPJ7C",
"field_header": "83"
@@ -1760,9 +1648,7 @@
[
"TransactionType",
{
- "binary": [
- "0000"
- ],
+ "binary": ["0000"],
"json": "Payment",
"field_header": "12"
}
@@ -1770,9 +1656,7 @@
[
"Flags",
{
- "binary": [
- "00000000"
- ],
+ "binary": ["00000000"],
"json": 0,
"field_header": "22"
}
@@ -1780,9 +1664,7 @@
[
"Sequence",
{
- "binary": [
- "00000090"
- ],
+ "binary": ["00000090"],
"json": 144,
"field_header": "24"
}
@@ -1790,9 +1672,7 @@
[
"LastLedgerSequence",
{
- "binary": [
- "005EE9BA"
- ],
+ "binary": ["005EE9BA"],
"json": 6220218,
"field_header": "201B"
}
@@ -1800,9 +1680,7 @@
[
"Amount",
{
- "binary": [
- "4000000000030D40"
- ],
+ "binary": ["4000000000030D40"],
"json": "200000",
"field_header": "61"
}
@@ -1810,9 +1688,7 @@
[
"Fee",
{
- "binary": [
- "400000000000000F"
- ],
+ "binary": ["400000000000000F"],
"json": "15",
"field_header": "68"
}
@@ -1820,9 +1696,7 @@
[
"Account",
{
- "binary": [
- "AA1BD19D9E87BE8069FDBF6843653C43837C03C6"
- ],
+ "binary": ["AA1BD19D9E87BE8069FDBF6843653C43837C03C6"],
"vl_length": "14",
"json": "rGWTUVmm1fB5QUjMYn8KfnyrFNgDiD9H9e",
"field_header": "81"
@@ -1831,9 +1705,7 @@
[
"Destination",
{
- "binary": [
- "67FE6EC28E0464DD24FB2D62A492AAC697CFAD02"
- ],
+ "binary": ["67FE6EC28E0464DD24FB2D62A492AAC697CFAD02"],
"vl_length": "14",
"json": "rw71Qs1UYQrSQ9hSgRohqNNQcyjCCfffkQ",
"field_header": "83"
@@ -1857,9 +1729,7 @@
[
"TransactionType",
{
- "binary": [
- "0000"
- ],
+ "binary": ["0000"],
"json": "Payment",
"field_header": "12"
}
@@ -1867,9 +1737,7 @@
[
"Flags",
{
- "binary": [
- "00000000"
- ],
+ "binary": ["00000000"],
"json": 0,
"field_header": "22"
}
@@ -1877,9 +1745,7 @@
[
"Sequence",
{
- "binary": [
- "00000001"
- ],
+ "binary": ["00000001"],
"json": 1,
"field_header": "24"
}
@@ -1887,9 +1753,7 @@
[
"DestinationTag",
{
- "binary": [
- "F72D50CA"
- ],
+ "binary": ["F72D50CA"],
"json": 4146942154,
"field_header": "2E"
}
@@ -1897,9 +1761,7 @@
[
"Amount",
{
- "binary": [
- "40000000017D7840"
- ],
+ "binary": ["40000000017D7840"],
"json": "25000000",
"field_header": "61"
}
@@ -1907,9 +1769,7 @@
[
"Fee",
{
- "binary": [
- "400000000000000C"
- ],
+ "binary": ["400000000000000C"],
"json": "12",
"field_header": "68"
}
@@ -1917,9 +1777,7 @@
[
"Account",
{
- "binary": [
- "E851BBBE79E328E43D68F43445368133DF5FBA5A"
- ],
+ "binary": ["E851BBBE79E328E43D68F43445368133DF5FBA5A"],
"vl_length": "14",
"json": "r4BPgS7DHebQiU31xWELvZawwSG2fSPJ7C",
"field_header": "81"
@@ -1928,9 +1786,7 @@
[
"Destination",
{
- "binary": [
- "76DAC5E814CD4AA74142C3AB45E69A900E637AA2"
- ],
+ "binary": ["76DAC5E814CD4AA74142C3AB45E69A900E637AA2"],
"vl_length": "14",
"json": "rBqSFEFg2B6GBMobtxnU1eLA1zbNC9NDGM",
"field_header": "83"
@@ -1953,9 +1809,7 @@
[
"TransactionType",
{
- "binary": [
- "0000"
- ],
+ "binary": ["0000"],
"json": "Payment",
"field_header": "12"
}
@@ -1963,9 +1817,7 @@
[
"SourceTag",
{
- "binary": [
- "000A34F8"
- ],
+ "binary": ["000A34F8"],
"json": 668920,
"field_header": "23"
}
@@ -1973,9 +1825,7 @@
[
"Sequence",
{
- "binary": [
- "0000888A"
- ],
+ "binary": ["0000888A"],
"json": 34954,
"field_header": "24"
}
@@ -1983,9 +1833,7 @@
[
"Amount",
{
- "binary": [
- "400000000007A120"
- ],
+ "binary": ["400000000007A120"],
"json": "500000",
"field_header": "61"
}
@@ -1993,9 +1841,7 @@
[
"Fee",
{
- "binary": [
- "4000000000000014"
- ],
+ "binary": ["4000000000000014"],
"json": "20",
"field_header": "68"
}
@@ -2003,9 +1849,7 @@
[
"Account",
{
- "binary": [
- "08F41F116A1F60D60296B16907F0A041BF106197"
- ],
+ "binary": ["08F41F116A1F60D60296B16907F0A041BF106197"],
"vl_length": "14",
"json": "rFLiPGytDEwC5heoqFcFAZoqPPmKBzX1o",
"field_header": "81"
@@ -2014,9 +1858,7 @@
[
"Destination",
{
- "binary": [
- "6E2F0455C46CF5DF61A1E58419A89D45459045EA"
- ],
+ "binary": ["6E2F0455C46CF5DF61A1E58419A89D45459045EA"],
"vl_length": "14",
"json": "rBsbetvMYuMkEeHZYizPMkpveCVH8EVQYd",
"field_header": "83"
@@ -2039,14 +1881,10 @@
"SendMax": "3267350000",
"Flags": 0,
"Sequence": 10,
- "Paths": [
- [
- {
- "currency": "BTC",
- "issuer": "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q"
- }
- ]
- ],
+ "Paths": [[{
+ "currency": "BTC",
+ "issuer": "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q"
+ }]],
"InvoiceID": "342B8D16BEE494D169034AFF0908FDE35874A38E548D4CEC8DFC5C49E9A33B76",
"DestinationTag": 1403334172
},
@@ -2054,9 +1892,7 @@
[
"TransactionType",
{
- "binary": [
- "0000"
- ],
+ "binary": ["0000"],
"json": "Payment",
"field_header": "12"
}
@@ -2064,9 +1900,7 @@
[
"Flags",
{
- "binary": [
- "00000000"
- ],
+ "binary": ["00000000"],
"json": 0,
"field_header": "22"
}
@@ -2074,9 +1908,7 @@
[
"Sequence",
{
- "binary": [
- "0000000A"
- ],
+ "binary": ["0000000A"],
"json": 10,
"field_header": "24"
}
@@ -2084,9 +1916,7 @@
[
"DestinationTag",
{
- "binary": [
- "53A52E1C"
- ],
+ "binary": ["53A52E1C"],
"json": 1403334172,
"field_header": "2E"
}
@@ -2094,9 +1924,7 @@
[
"InvoiceID",
{
- "binary": [
- "342B8D16BEE494D169034AFF0908FDE35874A38E548D4CEC8DFC5C49E9A33B76"
- ],
+ "binary": ["342B8D16BEE494D169034AFF0908FDE35874A38E548D4CEC8DFC5C49E9A33B76"],
"json": "342B8D16BEE494D169034AFF0908FDE35874A38E548D4CEC8DFC5C49E9A33B76",
"field_header": "5011"
}
@@ -2120,9 +1948,7 @@
[
"Fee",
{
- "binary": [
- "400000000000006A"
- ],
+ "binary": ["400000000000006A"],
"json": "106",
"field_header": "68"
}
@@ -2130,9 +1956,7 @@
[
"SendMax",
{
- "binary": [
- "40000000C2BFCDF0"
- ],
+ "binary": ["40000000C2BFCDF0"],
"json": "3267350000",
"field_header": "69"
}
@@ -2140,9 +1964,7 @@
[
"Account",
{
- "binary": [
- "52E0F910686FB449A23BC78C3D4CE564C988C6C0"
- ],
+ "binary": ["52E0F910686FB449A23BC78C3D4CE564C988C6C0"],
"vl_length": "14",
"json": "r3ZDv3hLmTKwkgAqcXtX2yaMfnhRD3Grjc",
"field_header": "81"
@@ -2151,9 +1973,7 @@
[
"Destination",
{
- "binary": [
- "DD39C650A96EDA48334E70CC4A85B8B2E8502CD3"
- ],
+ "binary": ["DD39C650A96EDA48334E70CC4A85B8B2E8502CD3"],
"vl_length": "14",
"json": "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q",
"field_header": "83"
@@ -2168,14 +1988,10 @@
"DD39C650A96EDA48334E70CC4A85B8B2E8502CD3",
"00"
],
- "json": [
- [
- {
- "currency": "BTC",
- "issuer": "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q"
- }
- ]
- ],
+ "json": [[{
+ "currency": "BTC",
+ "issuer": "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q"
+ }]],
"field_header": "0112"
}
]
@@ -2196,9 +2012,7 @@
[
"TransactionType",
{
- "binary": [
- "0008"
- ],
+ "binary": ["0008"],
"json": "OfferCancel",
"field_header": "12"
}
@@ -2206,9 +2020,7 @@
[
"Flags",
{
- "binary": [
- "00000000"
- ],
+ "binary": ["00000000"],
"json": 0,
"field_header": "22"
}
@@ -2216,9 +2028,7 @@
[
"Sequence",
{
- "binary": [
- "00005121"
- ],
+ "binary": ["00005121"],
"json": 20769,
"field_header": "24"
}
@@ -2226,9 +2036,7 @@
[
"OfferSequence",
{
- "binary": [
- "0000511B"
- ],
+ "binary": ["0000511B"],
"json": 20763,
"field_header": "2019"
}
@@ -2236,9 +2044,7 @@
[
"LastLedgerSequence",
{
- "binary": [
- "005EE8E9"
- ],
+ "binary": ["005EE8E9"],
"json": 6220009,
"field_header": "201B"
}
@@ -2246,9 +2052,7 @@
[
"Fee",
{
- "binary": [
- "400000000000000C"
- ],
+ "binary": ["400000000000000C"],
"json": "12",
"field_header": "68"
}
@@ -2256,9 +2060,7 @@
[
"Account",
{
- "binary": [
- "D0B32295596E50017E246FE85FC5982A1BD89CE4"
- ],
+ "binary": ["D0B32295596E50017E246FE85FC5982A1BD89CE4"],
"vl_length": "14",
"json": "rLpW9Reyn9YqZ8mxbq8nviXSp4TnHafVJQ",
"field_header": "81"
@@ -2280,9 +2082,7 @@
[
"TransactionType",
{
- "binary": [
- "0005"
- ],
+ "binary": ["0005"],
"json": "SetRegularKey",
"field_header": "12"
}
@@ -2290,9 +2090,7 @@
[
"Flags",
{
- "binary": [
- "80000000"
- ],
+ "binary": ["80000000"],
"json": 2147483648,
"field_header": "22"
}
@@ -2300,9 +2098,7 @@
[
"Sequence",
{
- "binary": [
- "00000003"
- ],
+ "binary": ["00000003"],
"json": 3,
"field_header": "24"
}
@@ -2310,9 +2106,7 @@
[
"Fee",
{
- "binary": [
- "400000000000000A"
- ],
+ "binary": ["400000000000000A"],
"json": "10",
"field_header": "68"
}
@@ -2320,9 +2114,7 @@
[
"Account",
{
- "binary": [
- "48E143E2384A1B3C69A412789F2CA3FCE2F65F0B"
- ],
+ "binary": ["48E143E2384A1B3C69A412789F2CA3FCE2F65F0B"],
"vl_length": "14",
"json": "rfeMWWbSaGqc6Yth2dTetLBeKeUTTfE2pG",
"field_header": "81"
@@ -2331,9 +2123,7 @@
[
"RegularKey",
{
- "binary": [
- "48E143E2384A1B3C69A412789F2CA3FCE2F65F0B"
- ],
+ "binary": ["48E143E2384A1B3C69A412789F2CA3FCE2F65F0B"],
"vl_length": "14",
"json": "rfeMWWbSaGqc6Yth2dTetLBeKeUTTfE2pG",
"field_header": "88"
@@ -2356,9 +2146,7 @@
[
"TransactionType",
{
- "binary": [
- "0005"
- ],
+ "binary": ["0005"],
"json": "SetRegularKey",
"field_header": "12"
}
@@ -2366,9 +2154,7 @@
[
"Flags",
{
- "binary": [
- "80000000"
- ],
+ "binary": ["80000000"],
"json": 2147483648,
"field_header": "22"
}
@@ -2376,9 +2162,7 @@
[
"Sequence",
{
- "binary": [
- "000000EE"
- ],
+ "binary": ["000000EE"],
"json": 238,
"field_header": "24"
}
@@ -2386,9 +2170,7 @@
[
"LastLedgerSequence",
{
- "binary": [
- "005EF94C"
- ],
+ "binary": ["005EF94C"],
"json": 6224204,
"field_header": "201B"
}
@@ -2396,9 +2178,7 @@
[
"Fee",
{
- "binary": [
- "400000000000000C"
- ],
+ "binary": ["400000000000000C"],
"json": "12",
"field_header": "68"
}
@@ -2406,9 +2186,7 @@
[
"Account",
{
- "binary": [
- "CB3F392892D0772FF5AD155D8D70404B1DB2ACFE"
- ],
+ "binary": ["CB3F392892D0772FF5AD155D8D70404B1DB2ACFE"],
"vl_length": "14",
"json": "rKXCummUHnenhYudNb9UoJ4mGBR75vFcgz",
"field_header": "81"
@@ -2417,9 +2195,7 @@
[
"RegularKey",
{
- "binary": [
- "F2F9A54D9CEBBE64342B52DE3450FFA0738C8D00"
- ],
+ "binary": ["F2F9A54D9CEBBE64342B52DE3450FFA0738C8D00"],
"vl_length": "14",
"json": "rP9jbfTepHAHWB4q9YjNkLyaZT15uvexiZ",
"field_header": "88"
@@ -2445,9 +2221,7 @@
[
"TransactionType",
{
- "binary": [
- "0014"
- ],
+ "binary": ["0014"],
"json": "TrustSet",
"field_header": "12"
}
@@ -2455,9 +2229,7 @@
[
"Flags",
{
- "binary": [
- "00020000"
- ],
+ "binary": ["00020000"],
"json": 131072,
"field_header": "22"
}
@@ -2465,9 +2237,7 @@
[
"Sequence",
{
- "binary": [
- "0000002C"
- ],
+ "binary": ["0000002C"],
"json": 44,
"field_header": "24"
}
@@ -2491,9 +2261,7 @@
[
"Fee",
{
- "binary": [
- "400000000000000C"
- ],
+ "binary": ["400000000000000C"],
"json": "12",
"field_header": "68"
}
@@ -2501,9 +2269,7 @@
[
"Account",
{
- "binary": [
- "BE6C30732AE33CF2AF3344CE8172A6B9300183E3"
- ],
+ "binary": ["BE6C30732AE33CF2AF3344CE8172A6B9300183E3"],
"vl_length": "14",
"json": "rJMiz2rCMjZzEMijXNH1exNBryTQEjFd9S",
"field_header": "81"
@@ -2530,9 +2296,7 @@
[
"TransactionType",
{
- "binary": [
- "0014"
- ],
+ "binary": ["0014"],
"json": "TrustSet",
"field_header": "12"
}
@@ -2540,9 +2304,7 @@
[
"Flags",
{
- "binary": [
- "80020000"
- ],
+ "binary": ["80020000"],
"json": 2147614720,
"field_header": "22"
}
@@ -2550,9 +2312,7 @@
[
"Sequence",
{
- "binary": [
- "0000002B"
- ],
+ "binary": ["0000002B"],
"json": 43,
"field_header": "24"
}
@@ -2560,9 +2320,7 @@
[
"LastLedgerSequence",
{
- "binary": [
- "005EEAAF"
- ],
+ "binary": ["005EEAAF"],
"json": 6220463,
"field_header": "201B"
}
@@ -2586,9 +2344,7 @@
[
"Fee",
{
- "binary": [
- "400000000000000C"
- ],
+ "binary": ["400000000000000C"],
"json": "12",
"field_header": "68"
}
@@ -2596,9 +2352,7 @@
[
"Account",
{
- "binary": [
- "8353C031DF5AA061A23535E6ABCEEEA23F152B1E"
- ],
+ "binary": ["8353C031DF5AA061A23535E6ABCEEEA23F152B1E"],
"vl_length": "14",
"json": "rUyPiNcSFFj6uMR2gEaD8jUerQ59G1qvwN",
"field_header": "81"
@@ -2619,9 +2373,7 @@
[
"TransactionType",
{
- "binary": [
- "0003"
- ],
+ "binary": ["0003"],
"json": "AccountSet",
"field_header": "12"
}
@@ -2629,9 +2381,7 @@
[
"Flags",
{
- "binary": [
- "00000000"
- ],
+ "binary": ["00000000"],
"json": 0,
"field_header": "22"
}
@@ -2639,9 +2389,7 @@
[
"Sequence",
{
- "binary": [
- "00002966"
- ],
+ "binary": ["00002966"],
"json": 10598,
"field_header": "24"
}
@@ -2649,9 +2397,7 @@
[
"Fee",
{
- "binary": [
- "400000000000000A"
- ],
+ "binary": ["400000000000000A"],
"json": "10",
"field_header": "68"
}
@@ -2659,9 +2405,7 @@
[
"Account",
{
- "binary": [
- "0F3D0C7D2CFAB2EC8295451F0B3CA038E8E9CDCD"
- ],
+ "binary": ["0F3D0C7D2CFAB2EC8295451F0B3CA038E8E9CDCD"],
"vl_length": "14",
"json": "rpP2GdsQwenNnFPefbXFgiTvEgJWQpq8Rw",
"field_header": "81"
@@ -2683,9 +2427,7 @@
[
"TransactionType",
{
- "binary": [
- "0003"
- ],
+ "binary": ["0003"],
"json": "AccountSet",
"field_header": "12"
}
@@ -2693,9 +2435,7 @@
[
"Flags",
{
- "binary": [
- "00000000"
- ],
+ "binary": ["00000000"],
"json": 0,
"field_header": "22"
}
@@ -2703,9 +2443,7 @@
[
"Sequence",
{
- "binary": [
- "00000122"
- ],
+ "binary": ["00000122"],
"json": 290,
"field_header": "24"
}
@@ -2713,9 +2451,7 @@
[
"LastLedgerSequence",
{
- "binary": [
- "005EECD6"
- ],
+ "binary": ["005EECD6"],
"json": 6221014,
"field_header": "201B"
}
@@ -2723,9 +2459,7 @@
[
"Fee",
{
- "binary": [
- "400000000000000F"
- ],
+ "binary": ["400000000000000F"],
"json": "15",
"field_header": "68"
}
@@ -2733,9 +2467,7 @@
[
"Account",
{
- "binary": [
- "ABBD4A3AF95FDFD6D072F11421D8F107CAEA1852"
- ],
+ "binary": ["ABBD4A3AF95FDFD6D072F11421D8F107CAEA1852"],
"vl_length": "14",
"json": "rGCnJuD31Kx4QGZJ2dX7xoje6T4Zr5s9EB",
"field_header": "81"
diff --git a/xrpl4j-core/src/test/resources/tx_metadata_fixtures.json.zip b/xrpl4j-core/src/test/resources/tx_metadata_fixtures.json.zip
index f119f16af..14d3522c7 100644
Binary files a/xrpl4j-core/src/test/resources/tx_metadata_fixtures.json.zip and b/xrpl4j-core/src/test/resources/tx_metadata_fixtures.json.zip differ
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 6b240ac37..53307bcba 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
@@ -22,10 +22,12 @@
import static org.assertj.core.api.Assertions.assertThat;
import static org.awaitility.Awaitility.given;
+import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.hamcrest.core.Is.is;
import com.fasterxml.jackson.core.JsonProcessingException;
+import com.google.common.primitives.UnsignedInteger;
import com.google.common.primitives.UnsignedLong;
import org.awaitility.Durations;
import org.slf4j.Logger;
@@ -44,6 +46,8 @@
import org.xrpl.xrpl4j.crypto.signing.SingleSignedTransaction;
import org.xrpl.xrpl4j.crypto.signing.bc.BcDerivedKeySignatureService;
import org.xrpl.xrpl4j.crypto.signing.bc.BcSignatureService;
+import org.xrpl.xrpl4j.model.client.Finality;
+import org.xrpl.xrpl4j.model.client.FinalityStatus;
import org.xrpl.xrpl4j.model.client.XrplResult;
import org.xrpl.xrpl4j.model.client.accounts.AccountChannelsRequestParams;
import org.xrpl.xrpl4j.model.client.accounts.AccountChannelsResult;
@@ -54,6 +58,7 @@
import org.xrpl.xrpl4j.model.client.accounts.AccountObjectsRequestParams;
import org.xrpl.xrpl4j.model.client.accounts.AccountObjectsResult;
import org.xrpl.xrpl4j.model.client.accounts.TrustLine;
+import org.xrpl.xrpl4j.model.client.common.LedgerIndex;
import org.xrpl.xrpl4j.model.client.common.LedgerSpecifier;
import org.xrpl.xrpl4j.model.client.ledger.LedgerRequestParams;
import org.xrpl.xrpl4j.model.client.ledger.LedgerResult;
@@ -204,6 +209,36 @@ protected void fundAccount(final Address address) {
// Ledger Helpers
//////////////////////
+ protected Finality scanForFinality(
+ Hash256 transactionHash,
+ LedgerIndex submittedOnLedgerIndex,
+ UnsignedInteger lastLedgerSequence,
+ UnsignedInteger transactionAccountSequence,
+ Address account
+ ) {
+ return given()
+ .pollInterval(POLL_INTERVAL)
+ .atMost(Durations.ONE_MINUTE.dividedBy(2))
+ .ignoreException(RuntimeException.class)
+ .await()
+ .until(
+ () -> xrplClient.isFinal(
+ transactionHash,
+ submittedOnLedgerIndex,
+ lastLedgerSequence,
+ transactionAccountSequence,
+ account
+ ),
+ is(equalTo(
+ Finality.builder()
+ .finalityStatus(FinalityStatus.VALIDATED_SUCCESS)
+ .resultCode(TransactionResultCodes.TES_SUCCESS)
+ .build()
+ )
+ )
+ );
+ }
+
protected T scanForResult(Supplier resultSupplier, Predicate condition) {
return given()
.atMost(Durations.ONE_MINUTE.dividedBy(2))
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 0ea468489..18e2e9378 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
@@ -30,12 +30,17 @@
import org.xrpl.xrpl4j.crypto.signing.SingleSignedTransaction;
import org.xrpl.xrpl4j.crypto.signing.bc.BcSignatureService;
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.fees.FeeUtils;
+import org.xrpl.xrpl4j.model.client.ledger.LedgerEntryRequestParams;
+import org.xrpl.xrpl4j.model.client.ledger.LedgerEntryResult;
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.ledger.AccountRootObject;
+import org.xrpl.xrpl4j.model.ledger.LedgerObject;
import org.xrpl.xrpl4j.model.transactions.AccountSet;
import org.xrpl.xrpl4j.model.transactions.AccountSet.AccountSetFlag;
@@ -59,9 +64,12 @@ public void enableAllAndDisableOne() throws JsonRpcClientErrorException, JsonPro
AccountInfoResult accountInfo = this.scanForResult(
() -> this.getValidatedAccountInfo(keyPair.publicKey().deriveAddress())
);
+
assertThat(accountInfo.status()).isNotEmpty().get().isEqualTo("success");
assertThat(accountInfo.accountData().flags().lsfGlobalFreeze()).isEqualTo(false);
+ assertEntryEqualsAccountInfo(keyPair, accountInfo);
+
UnsignedInteger sequence = accountInfo.accountData().sequence();
//////////////////////
// Set asfAccountTxnID (no corresponding ledger flag)
@@ -384,10 +392,34 @@ void submitAndRetrieveAccountSetWithZeroClearFlagAndSetFlag()
assertThat(accountSetTransactionResult.transaction().clearFlag()).isNotEmpty().get().isEqualTo(AccountSetFlag.NONE);
}
+
//////////////////////
// Test Helpers
//////////////////////
+ private void assertEntryEqualsAccountInfo(
+ KeyPair keyPair,
+ AccountInfoResult accountInfo
+ ) throws JsonRpcClientErrorException {
+ LedgerEntryResult accountRoot = xrplClient.ledgerEntry(
+ LedgerEntryRequestParams.accountRoot(keyPair.publicKey().deriveAddress(), LedgerSpecifier.VALIDATED)
+ );
+
+ assertThat(accountInfo.accountData()).isEqualTo(accountRoot.node());
+
+ LedgerEntryResult entryByIndex = xrplClient.ledgerEntry(
+ LedgerEntryRequestParams.index(accountRoot.index(), AccountRootObject.class, LedgerSpecifier.VALIDATED)
+ );
+
+ assertThat(entryByIndex.node()).isEqualTo(accountRoot.node());
+
+ LedgerEntryResult entryByIndexUnTyped = xrplClient.ledgerEntry(
+ LedgerEntryRequestParams.index(accountRoot.index(), LedgerSpecifier.VALIDATED)
+ );
+
+ assertThat(entryByIndex.node()).isEqualTo(entryByIndexUnTyped.node());
+ }
+
private void assertSetFlag(
final KeyPair keyPair,
final UnsignedInteger sequence,
@@ -420,12 +452,14 @@ private void assertSetFlag(
/////////////////////////
// Validate Account State
- this.scanForResult(
+ AccountInfoResult accountInfo = this.scanForResult(
() -> this.getValidatedAccountInfo(keyPair.publicKey().deriveAddress()),
accountInfoResult -> {
logger.info("AccountInfoResponse Flags: {}", accountInfoResult.accountData().flags());
return accountInfoResult.accountData().flags().isSet(accountRootFlag);
});
+
+ assertEntryEqualsAccountInfo(keyPair, accountInfo);
}
private void assertClearFlag(
@@ -460,11 +494,13 @@ private void assertClearFlag(
/////////////////////////
// Validate Account State
- this.scanForResult(
+ AccountInfoResult accountInfo = this.scanForResult(
() -> this.getValidatedAccountInfo(keyPair.publicKey().deriveAddress()),
accountInfoResult -> {
logger.info("AccountInfoResponse Flags: {}", accountInfoResult.accountData().flags());
return !accountInfoResult.accountData().flags().isSet(accountRootFlag);
});
+
+ assertEntryEqualsAccountInfo(keyPair, accountInfo);
}
}
diff --git a/xrpl4j-integration-tests/src/test/java/org/xrpl/xrpl4j/tests/AmmIT.java b/xrpl4j-integration-tests/src/test/java/org/xrpl/xrpl4j/tests/AmmIT.java
new file mode 100644
index 000000000..b908e9287
--- /dev/null
+++ b/xrpl4j-integration-tests/src/test/java/org/xrpl/xrpl4j/tests/AmmIT.java
@@ -0,0 +1,504 @@
+package org.xrpl.xrpl4j.tests;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.google.common.base.Strings;
+import com.google.common.io.BaseEncoding;
+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.PrivateKey;
+import org.xrpl.xrpl4j.crypto.signing.SignatureService;
+import org.xrpl.xrpl4j.crypto.signing.SingleSignedTransaction;
+import org.xrpl.xrpl4j.model.client.accounts.AccountInfoRequestParams;
+import org.xrpl.xrpl4j.model.client.accounts.AccountInfoResult;
+import org.xrpl.xrpl4j.model.client.accounts.AccountLinesRequestParams;
+import org.xrpl.xrpl4j.model.client.accounts.AccountLinesResult;
+import org.xrpl.xrpl4j.model.client.accounts.TrustLine;
+import org.xrpl.xrpl4j.model.client.amm.AmmInfoAuctionSlot;
+import org.xrpl.xrpl4j.model.client.amm.AmmInfoAuthAccount;
+import org.xrpl.xrpl4j.model.client.amm.AmmInfoRequestParams;
+import org.xrpl.xrpl4j.model.client.amm.AmmInfoResult;
+import org.xrpl.xrpl4j.model.client.common.LedgerSpecifier;
+import org.xrpl.xrpl4j.model.client.common.TimeUtils;
+import org.xrpl.xrpl4j.model.client.fees.FeeResult;
+import org.xrpl.xrpl4j.model.client.fees.FeeUtils;
+import org.xrpl.xrpl4j.model.client.ledger.AmmLedgerEntryParams;
+import org.xrpl.xrpl4j.model.client.ledger.LedgerEntryRequestParams;
+import org.xrpl.xrpl4j.model.client.ledger.LedgerEntryResult;
+import org.xrpl.xrpl4j.model.client.transactions.SubmitResult;
+import org.xrpl.xrpl4j.model.flags.AmmDepositFlags;
+import org.xrpl.xrpl4j.model.flags.AmmWithdrawFlags;
+import org.xrpl.xrpl4j.model.ledger.AmmObject;
+import org.xrpl.xrpl4j.model.ledger.AuctionSlot;
+import org.xrpl.xrpl4j.model.ledger.AuthAccount;
+import org.xrpl.xrpl4j.model.ledger.AuthAccountWrapper;
+import org.xrpl.xrpl4j.model.ledger.Issue;
+import org.xrpl.xrpl4j.model.ledger.LedgerObject;
+import org.xrpl.xrpl4j.model.transactions.AccountSet;
+import org.xrpl.xrpl4j.model.transactions.AccountSet.AccountSetFlag;
+import org.xrpl.xrpl4j.model.transactions.AmmBid;
+import org.xrpl.xrpl4j.model.transactions.AmmCreate;
+import org.xrpl.xrpl4j.model.transactions.AmmDeposit;
+import org.xrpl.xrpl4j.model.transactions.AmmVote;
+import org.xrpl.xrpl4j.model.transactions.AmmWithdraw;
+import org.xrpl.xrpl4j.model.transactions.IssuedCurrencyAmount;
+import org.xrpl.xrpl4j.model.transactions.TradingFee;
+import org.xrpl.xrpl4j.model.transactions.TransactionResultCodes;
+import org.xrpl.xrpl4j.model.transactions.XrpCurrencyAmount;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.util.stream.Collectors;
+
+@DisabledIf(value = "shouldNotRun", disabledReason = "AmmIT only runs on local rippled node or devnet.")
+public class AmmIT extends AbstractIT {
+
+ static boolean shouldNotRun() {
+ return System.getProperty("useTestnet") != null ||
+ System.getProperty("useClioTestnet") != null;
+ }
+
+ String xrpl4jCoin = Strings.padEnd(BaseEncoding.base16().encode("xrpl4jCoin".getBytes()), 40, '0');
+
+ @Test
+ void depositAndVoteOnTradingFee() throws JsonRpcClientErrorException, JsonProcessingException {
+ KeyPair issuerKeyPair = createRandomAccountEd25519();
+ FeeResult feeResult = xrplClient.fee();
+ AmmInfoResult amm = createAmm(issuerKeyPair, feeResult);
+ KeyPair traderKeyPair = createRandomAccountEd25519();
+
+ AccountInfoResult traderAccount = scanForResult(
+ () -> this.getValidatedAccountInfo(traderKeyPair.publicKey().deriveAddress())
+ );
+
+ AccountInfoResult traderAccountAfterDeposit = depositXrp(
+ issuerKeyPair,
+ traderKeyPair,
+ traderAccount,
+ amm,
+ signatureService,
+ feeResult
+ );
+
+ TradingFee newTradingFee = TradingFee.ofPercent(BigDecimal.valueOf(0.24));
+ AmmVote ammVote = AmmVote.builder()
+ .account(traderAccount.accountData().account())
+ .sequence(traderAccountAfterDeposit.accountData().sequence())
+ .fee(FeeUtils.computeNetworkFees(feeResult).recommendedFee())
+ .lastLedgerSequence(traderAccount.ledgerIndexSafe().plus(UnsignedInteger.valueOf(8)).unsignedIntegerValue())
+ .signingPublicKey(traderKeyPair.publicKey())
+ .asset2(
+ Issue.builder()
+ .currency(xrpl4jCoin)
+ .issuer(issuerKeyPair.publicKey().deriveAddress())
+ .build()
+ )
+ .asset(Issue.XRP)
+ .tradingFee(newTradingFee)
+ .build();
+
+ SingleSignedTransaction signedVote = signatureService.sign(traderKeyPair.privateKey(), ammVote);
+
+ SubmitResult voteSubmitResult = xrplClient.submit(signedVote);
+ assertThat(voteSubmitResult.engineResult()).isEqualTo(TransactionResultCodes.TES_SUCCESS);
+
+ scanForFinality(
+ signedVote.hash(),
+ traderAccount.ledgerIndexSafe(),
+ ammVote.lastLedgerSequence().get(),
+ ammVote.sequence(),
+ traderKeyPair.publicKey().deriveAddress()
+ );
+
+ BigDecimal issuerLpTokenBalance = new BigDecimal(xrplClient.accountLines(
+ AccountLinesRequestParams.builder()
+ .account(issuerKeyPair.publicKey().deriveAddress())
+ .peer(amm.amm().account())
+ .ledgerSpecifier(LedgerSpecifier.CURRENT)
+ .build()
+ ).lines().stream()
+ .filter(trustLine -> trustLine.currency().equals(amm.amm().lpToken().currency()))
+ .findFirst()
+ .orElseThrow(RuntimeException::new)
+ .balance());
+
+ BigDecimal traderLpTokenBalance = new BigDecimal(xrplClient.accountLines(
+ AccountLinesRequestParams.builder()
+ .account(traderKeyPair.publicKey().deriveAddress())
+ .peer(amm.amm().account())
+ .ledgerSpecifier(LedgerSpecifier.CURRENT)
+ .build()
+ ).lines().stream()
+ .filter(trustLine -> trustLine.currency().equals(amm.amm().lpToken().currency()))
+ .findFirst()
+ .orElseThrow(RuntimeException::new)
+ .balance());
+
+ // Expected trading fee is the weighted average of each vote, where the weight is number of LP tokens held
+ // by each voter
+ TradingFee expectedTradingFee = TradingFee.ofPercent(
+ issuerLpTokenBalance.multiply(amm.amm().tradingFee().bigDecimalValue()).add(
+ traderLpTokenBalance.multiply(newTradingFee.bigDecimalValue())
+ ).divide(issuerLpTokenBalance.add(traderLpTokenBalance), RoundingMode.FLOOR)
+ .setScale(3, RoundingMode.FLOOR)
+ );
+
+ AmmInfoResult ammAfterVote = getAmmInfo(issuerKeyPair);
+ assertThat(ammAfterVote.amm().tradingFee()).isEqualTo(expectedTradingFee);
+ }
+
+ @Test
+ void depositAndBid() throws JsonRpcClientErrorException, JsonProcessingException {
+ KeyPair issuerKeyPair = createRandomAccountEd25519();
+ FeeResult feeResult = xrplClient.fee();
+ AmmInfoResult amm = createAmm(issuerKeyPair, feeResult);
+ KeyPair traderKeyPair = createRandomAccountEd25519();
+ KeyPair authAccount1 = createRandomAccountEd25519();
+
+ AccountInfoResult traderAccount = scanForResult(
+ () -> this.getValidatedAccountInfo(traderKeyPair.publicKey().deriveAddress())
+ );
+
+ AccountInfoResult traderAccountAfterDeposit = depositXrp(
+ issuerKeyPair,
+ traderKeyPair,
+ traderAccount,
+ amm,
+ signatureService,
+ feeResult
+ );
+
+ AmmBid bid = AmmBid.builder()
+ .account(traderAccount.accountData().account())
+ .sequence(traderAccountAfterDeposit.accountData().sequence())
+ .fee(FeeUtils.computeNetworkFees(feeResult).recommendedFee())
+ .lastLedgerSequence(traderAccount.ledgerIndexSafe().plus(UnsignedInteger.valueOf(8)).unsignedIntegerValue())
+ .signingPublicKey(traderKeyPair.publicKey())
+ .asset2(
+ Issue.builder()
+ .currency(xrpl4jCoin)
+ .issuer(issuerKeyPair.publicKey().deriveAddress())
+ .build()
+ )
+ .asset(Issue.XRP)
+ .addAuthAccounts(
+ AuthAccountWrapper.of(AuthAccount.of(authAccount1.publicKey().deriveAddress()))
+ )
+ .bidMin(
+ IssuedCurrencyAmount.builder()
+ .from(amm.amm().lpToken())
+ .value("100")
+ .build()
+ )
+ .build();
+
+ SingleSignedTransaction signedBid = signatureService.sign(traderKeyPair.privateKey(), bid);
+
+ SubmitResult voteSubmitResult = xrplClient.submit(signedBid);
+ assertThat(voteSubmitResult.engineResult()).isEqualTo(TransactionResultCodes.TES_SUCCESS);
+
+ scanForFinality(
+ signedBid.hash(),
+ traderAccount.ledgerIndexSafe(),
+ bid.lastLedgerSequence().get(),
+ bid.sequence(),
+ traderKeyPair.publicKey().deriveAddress()
+ );
+
+ AmmInfoResult ammAfterBid = getAmmInfo(issuerKeyPair);
+
+ assertThat(ammAfterBid.amm().auctionSlot()).isNotEmpty();
+ AmmInfoAuctionSlot auctionSlot = ammAfterBid.amm().auctionSlot().get();
+ assertThat(auctionSlot.account()).isEqualTo(traderAccount.accountData().account());
+ assertThat(auctionSlot.authAccounts()).asList().extracting("account")
+ .containsExactly(authAccount1.publicKey().deriveAddress());
+ }
+
+ @Test
+ void depositAndWithdraw() throws JsonRpcClientErrorException, JsonProcessingException {
+ KeyPair issuerKeyPair = createRandomAccountEd25519();
+ FeeResult feeResult = xrplClient.fee();
+ AmmInfoResult amm = createAmm(issuerKeyPair, feeResult);
+ KeyPair traderKeyPair = createRandomAccountEd25519();
+
+ AccountInfoResult traderAccount = scanForResult(
+ () -> this.getValidatedAccountInfo(traderKeyPair.publicKey().deriveAddress())
+ );
+
+ AccountInfoResult traderAccountAfterDeposit = depositXrp(
+ issuerKeyPair,
+ traderKeyPair,
+ traderAccount,
+ amm,
+ signatureService,
+ feeResult
+ );
+
+ AmmInfoResult ammInfoAfterDeposit = getAmmInfo(issuerKeyPair);
+ AmmWithdraw withdraw = AmmWithdraw.builder()
+ .account(traderKeyPair.publicKey().deriveAddress())
+ .fee(FeeUtils.computeNetworkFees(feeResult).recommendedFee())
+ .sequence(traderAccountAfterDeposit.accountData().sequence())
+ .lastLedgerSequence(
+ traderAccountAfterDeposit.ledgerCurrentIndexSafe().plus(UnsignedInteger.valueOf(4)).unsignedIntegerValue()
+ )
+ .signingPublicKey(traderKeyPair.publicKey())
+ .asset2(
+ Issue.builder()
+ .currency(xrpl4jCoin)
+ .issuer(issuerKeyPair.publicKey().deriveAddress())
+ .build()
+ )
+ .asset(Issue.XRP)
+ .amount(XrpCurrencyAmount.ofXrp(BigDecimal.valueOf(90)))
+ .flags(AmmWithdrawFlags.SINGLE_ASSET)
+ .build();
+
+ SingleSignedTransaction signedWithdraw = signatureService.sign(traderKeyPair.privateKey(), withdraw);
+
+ SubmitResult voteSubmitResult = xrplClient.submit(signedWithdraw);
+ assertThat(voteSubmitResult.engineResult()).isEqualTo(TransactionResultCodes.TES_SUCCESS);
+
+ scanForFinality(
+ signedWithdraw.hash(),
+ traderAccount.ledgerIndexSafe(),
+ withdraw.lastLedgerSequence().get(),
+ withdraw.sequence(),
+ traderKeyPair.publicKey().deriveAddress()
+ );
+
+ AmmInfoResult ammAfterWithdraw = getAmmInfo(issuerKeyPair);
+ assertThat(ammAfterWithdraw.amm().amount()).isInstanceOf(XrpCurrencyAmount.class)
+ .isEqualTo(((XrpCurrencyAmount) ammInfoAfterDeposit.amm().amount())
+ .minus((XrpCurrencyAmount) withdraw.amount().get()));
+
+ AccountInfoResult traderAccountAfterWithdraw = xrplClient.accountInfo(
+ AccountInfoRequestParams.of(traderKeyPair.publicKey().deriveAddress())
+ );
+
+ assertThat(traderAccountAfterWithdraw.accountData().balance()).isEqualTo(
+ traderAccountAfterDeposit.accountData().balance()
+ .minus(withdraw.fee())
+ .plus((XrpCurrencyAmount) withdraw.amount().get())
+ );
+ }
+
+ private AccountInfoResult depositXrp(
+ KeyPair issuerKeyPair,
+ KeyPair traderKeyPair,
+ AccountInfoResult traderAccount,
+ AmmInfoResult amm,
+ SignatureService signatureService,
+ FeeResult feeResult
+ ) throws JsonRpcClientErrorException, JsonProcessingException {
+ XrpCurrencyAmount depositAmount = XrpCurrencyAmount.ofXrp(BigDecimal.valueOf(100));
+ AmmDeposit deposit = AmmDeposit.builder()
+ .account(traderAccount.accountData().account())
+ .asset2(
+ Issue.builder()
+ .currency(xrpl4jCoin)
+ .issuer(issuerKeyPair.publicKey().deriveAddress())
+ .build()
+ )
+ .asset(Issue.XRP)
+ .flags(AmmDepositFlags.SINGLE_ASSET)
+ .amount(depositAmount)
+ .fee(FeeUtils.computeNetworkFees(feeResult).recommendedFee())
+ .sequence(traderAccount.accountData().sequence())
+ .signingPublicKey(traderKeyPair.publicKey())
+ .lastLedgerSequence(traderAccount.ledgerIndexSafe().plus(UnsignedInteger.valueOf(4)).unsignedIntegerValue())
+ .build();
+
+ SingleSignedTransaction signedDeposit = signatureService.sign(traderKeyPair.privateKey(), deposit);
+ SubmitResult submitResult = xrplClient.submit(signedDeposit);
+ assertThat(submitResult.engineResult()).isEqualTo(TransactionResultCodes.TES_SUCCESS);
+
+ scanForFinality(
+ signedDeposit.hash(),
+ traderAccount.ledgerIndexSafe(),
+ deposit.lastLedgerSequence().get(),
+ deposit.sequence(),
+ traderKeyPair.publicKey().deriveAddress()
+ );
+
+ AccountInfoResult traderAccountAfterDeposit = xrplClient.accountInfo(
+ AccountInfoRequestParams.of(traderAccount.accountData().account())
+ );
+
+ assertThat(traderAccountAfterDeposit.accountData().balance())
+ .isEqualTo(traderAccount.accountData().balance().minus(deposit.fee()).minus(depositAmount));
+
+ AccountLinesResult traderLines = xrplClient.accountLines(
+ AccountLinesRequestParams.builder()
+ .account(traderAccount.accountData().account())
+ .peer(amm.amm().account())
+ .ledgerSpecifier(LedgerSpecifier.CURRENT)
+ .build()
+ );
+
+ assertThat(traderLines.lines()).asList().hasSize(1);
+ TrustLine lpLine = traderLines.lines().get(0);
+ assertThat(lpLine.currency()).isEqualTo(amm.amm().lpToken().currency());
+ assertThat(new BigDecimal(lpLine.balance())).isGreaterThan(BigDecimal.ZERO);
+
+ return traderAccountAfterDeposit;
+ }
+
+ private AmmInfoResult createAmm(
+ KeyPair issuerKeyPair,
+ FeeResult feeResult
+ ) throws JsonRpcClientErrorException, JsonProcessingException {
+ AccountInfoResult issuerAccount = scanForResult(
+ () -> this.getValidatedAccountInfo(issuerKeyPair.publicKey().deriveAddress())
+ );
+
+ enableRippling(issuerKeyPair, issuerAccount, feeResult);
+
+ XrpCurrencyAmount reserveAmount = xrplClient.serverInformation().info()
+ .map(
+ rippled -> rippled.closedLedger().orElse(rippled.validatedLedger().get()).reserveIncXrp(),
+ clio -> clio.validatedLedger().get().reserveIncXrp(),
+ reporting -> reporting.closedLedger().orElse(reporting.validatedLedger().get()).reserveIncXrp()
+ );
+ AmmCreate ammCreate = AmmCreate.builder()
+ .account(issuerKeyPair.publicKey().deriveAddress())
+ .sequence(issuerAccount.accountData().sequence().plus(UnsignedInteger.ONE))
+ .fee(reserveAmount)
+ .amount(
+ IssuedCurrencyAmount.builder()
+ .issuer(issuerKeyPair.publicKey().deriveAddress())
+ .currency(xrpl4jCoin)
+ .value("25")
+ .build()
+ )
+ .amount2(XrpCurrencyAmount.ofXrp(BigDecimal.valueOf(100)))
+ .tradingFee(TradingFee.ofPercent(BigDecimal.ONE))
+ .lastLedgerSequence(issuerAccount.ledgerIndexSafe().plus(UnsignedInteger.valueOf(4)).unsignedIntegerValue())
+ .signingPublicKey(issuerKeyPair.publicKey())
+ .build();
+
+ SingleSignedTransaction signedCreate = signatureService.sign(issuerKeyPair.privateKey(), ammCreate);
+ SubmitResult submitResult = xrplClient.submit(signedCreate);
+ assertThat(submitResult.engineResult()).isEqualTo(TransactionResultCodes.TES_SUCCESS);
+
+ scanForFinality(
+ signedCreate.hash(),
+ issuerAccount.ledgerIndexSafe(),
+ ammCreate.lastLedgerSequence().get(),
+ ammCreate.sequence(),
+ issuerKeyPair.publicKey().deriveAddress()
+ );
+
+ return getAmmInfo(issuerKeyPair);
+ }
+
+ private AmmInfoResult getAmmInfo(KeyPair issuerKeyPair) throws JsonRpcClientErrorException {
+ AmmInfoResult ammInfoResult = xrplClient.ammInfo(
+ AmmInfoRequestParams.from(
+ Issue.XRP,
+ Issue.builder()
+ .issuer(issuerKeyPair.publicKey().deriveAddress())
+ .currency(xrpl4jCoin)
+ .build()
+ ));
+
+ AccountInfoResult ammAccountInfo = xrplClient.accountInfo(
+ AccountInfoRequestParams.of(ammInfoResult.amm().account())
+ );
+
+ assertThat(ammAccountInfo.accountData().ammId()).isNotEmpty();
+
+ AmmInfoResult ammInfoByAccount = xrplClient.ammInfo(
+ AmmInfoRequestParams.from(ammAccountInfo.accountData().account())
+ );
+
+ assertThat(ammInfoByAccount).isEqualTo(ammInfoResult);
+
+ LedgerEntryResult ammObject = xrplClient.ledgerEntry(
+ LedgerEntryRequestParams.amm(
+ AmmLedgerEntryParams.builder()
+ .asset(Issue.XRP)
+ .asset2(
+ Issue.builder()
+ .issuer(issuerKeyPair.publicKey().deriveAddress())
+ .currency(xrpl4jCoin)
+ .build()
+ )
+ .build(),
+ LedgerSpecifier.VALIDATED
+ )
+ );
+
+ assertThat(ammObject.node().account()).isEqualTo(ammInfoByAccount.amm().account());
+ assertThat(ammObject.node().asset()).isEqualTo(Issue.XRP);
+ assertThat(ammObject.node().asset2()).isEqualTo(
+ Issue.builder()
+ .issuer(((IssuedCurrencyAmount) ammInfoByAccount.amm().amount2()).issuer())
+ .currency(((IssuedCurrencyAmount) ammInfoByAccount.amm().amount2()).currency())
+ .build()
+ );
+ assertThat(
+ (ammObject.node().auctionSlot().isPresent() && ammInfoByAccount.amm().auctionSlot().isPresent()) ||
+ (!ammObject.node().auctionSlot().isPresent() && !ammInfoByAccount.amm().auctionSlot().isPresent())
+ ).isTrue();
+ if (ammObject.node().auctionSlot().isPresent()) {
+ AuctionSlot entryAuctionSlot = ammObject.node().auctionSlot().get();
+ AmmInfoAuctionSlot infoAuctionSlot = ammInfoByAccount.amm().auctionSlot().get();
+ assertThat(entryAuctionSlot.account()).isEqualTo(infoAuctionSlot.account());
+ assertThat(entryAuctionSlot.authAccountsAddresses()).isEqualTo(infoAuctionSlot.authAccounts().stream().map(
+ AmmInfoAuthAccount::account).collect(Collectors.toList()));
+ assertThat(entryAuctionSlot.price()).isEqualTo(infoAuctionSlot.price());
+ assertThat(TimeUtils.xrplTimeToZonedDateTime(UnsignedLong.valueOf(entryAuctionSlot.expiration().longValue())))
+ .isEqualTo(infoAuctionSlot.expiration());
+ assertThat(entryAuctionSlot.discountedFee()).isEqualTo(infoAuctionSlot.discountedFee());
+ }
+
+ assertThat(ammObject.node().lpTokenBalance()).isEqualTo(ammInfoByAccount.amm().lpToken());
+ assertThat(ammObject.node().tradingFee()).isEqualTo(ammInfoByAccount.amm().tradingFee());
+
+ assertThat(ammObject.node().voteSlots().size()).isEqualTo(ammInfoByAccount.amm().voteSlots().size());
+
+ LedgerEntryResult entryByIndex = xrplClient.ledgerEntry(
+ LedgerEntryRequestParams.index(ammObject.index(), AmmObject.class, LedgerSpecifier.VALIDATED)
+ );
+
+ assertThat(entryByIndex.node()).isEqualTo(ammObject.node());
+
+ LedgerEntryResult entryByIndexUnTyped = xrplClient.ledgerEntry(
+ LedgerEntryRequestParams.index(ammObject.index(), LedgerSpecifier.VALIDATED)
+ );
+
+ assertThat(entryByIndex.node()).isEqualTo(entryByIndexUnTyped.node());
+
+ return ammInfoResult;
+ }
+
+ private void enableRippling(KeyPair issuerKeyPair, AccountInfoResult issuerAccount, FeeResult feeResult)
+ throws JsonRpcClientErrorException, JsonProcessingException {
+ AccountSet accountSet = AccountSet.builder()
+ .account(issuerKeyPair.publicKey().deriveAddress())
+ .fee(FeeUtils.computeNetworkFees(feeResult).recommendedFee())
+ .signingPublicKey(issuerKeyPair.publicKey())
+ .sequence(issuerAccount.accountData().sequence())
+ .setFlag(AccountSetFlag.DEFAULT_RIPPLE)
+ .build();
+
+ SingleSignedTransaction signed = signatureService.sign(issuerKeyPair.privateKey(), accountSet);
+ SubmitResult setResult = xrplClient.submit(signed);
+ assertThat(setResult.engineResult()).isEqualTo("tesSUCCESS");
+ logger.info(
+ "AccountSet transaction successful: https://testnet.xrpl.org/transactions/{}",
+ setResult.transactionResult().hash()
+ );
+
+ scanForResult(
+ () -> getValidatedAccountInfo(issuerKeyPair.publicKey().deriveAddress()),
+ info -> info.accountData().flags().lsfDefaultRipple()
+ );
+ }
+}
diff --git a/xrpl4j-integration-tests/src/test/java/org/xrpl/xrpl4j/tests/CheckIT.java b/xrpl4j-integration-tests/src/test/java/org/xrpl/xrpl4j/tests/CheckIT.java
index 40928ec6d..5e4b93ee2 100644
--- a/xrpl4j-integration-tests/src/test/java/org/xrpl/xrpl4j/tests/CheckIT.java
+++ b/xrpl4j-integration-tests/src/test/java/org/xrpl/xrpl4j/tests/CheckIT.java
@@ -30,10 +30,14 @@
import org.xrpl.xrpl4j.crypto.keys.KeyPair;
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.fees.FeeUtils;
+import org.xrpl.xrpl4j.model.client.ledger.LedgerEntryRequestParams;
+import org.xrpl.xrpl4j.model.client.ledger.LedgerEntryResult;
import org.xrpl.xrpl4j.model.client.transactions.SubmitResult;
import org.xrpl.xrpl4j.model.ledger.CheckObject;
+import org.xrpl.xrpl4j.model.ledger.EscrowObject;
import org.xrpl.xrpl4j.model.ledger.LedgerObject;
import org.xrpl.xrpl4j.model.transactions.CheckCancel;
import org.xrpl.xrpl4j.model.transactions.CheckCash;
@@ -97,6 +101,8 @@ public void createXrpCheckAndCash() throws JsonRpcClientErrorException, JsonProc
.filter(findCheck(sourceKeyPair, destinationKeyPair, invoiceId))
.findFirst().get();
+ assertEntryEqualsObjectFromAccountObjects(checkObject);
+
//////////////////////
// Destination wallet cashes the Check
feeResult = xrplClient.fee();
@@ -188,6 +194,8 @@ public void createCheckAndSourceCancels() throws JsonRpcClientErrorException, Js
.filter(findCheck(sourceKeyPair, destinationKeyPair, invoiceId))
.findFirst().get();
+ assertEntryEqualsObjectFromAccountObjects(checkObject);
+
//////////////////////
// Source account cancels the Check
feeResult = xrplClient.fee();
@@ -265,6 +273,8 @@ public void createCheckAndDestinationCancels() throws JsonRpcClientErrorExceptio
.filter(findCheck(sourceKeyPair, destinationKeyPair, invoiceId))
.findFirst().get();
+ assertEntryEqualsObjectFromAccountObjects(checkObject);
+
//////////////////////
// Destination account cancels the Check
feeResult = xrplClient.fee();
@@ -304,4 +314,23 @@ private Predicate findCheck(KeyPair sourceKeyPair, KeyPair destina
((CheckObject) object).destination().equals(destinationKeyPair.publicKey().deriveAddress());
}
+
+ private void assertEntryEqualsObjectFromAccountObjects(CheckObject checkObject) throws JsonRpcClientErrorException {
+ LedgerEntryResult checkEntry = xrplClient.ledgerEntry(
+ LedgerEntryRequestParams.check(checkObject.index(), LedgerSpecifier.CURRENT));
+
+ assertThat(checkEntry.node()).isEqualTo(checkObject);
+
+ LedgerEntryResult entryByIndex = xrplClient.ledgerEntry(
+ LedgerEntryRequestParams.index(checkObject.index(), CheckObject.class, LedgerSpecifier.VALIDATED)
+ );
+
+ assertThat(entryByIndex.node()).isEqualTo(checkEntry.node());
+
+ LedgerEntryResult entryByIndexUnTyped = xrplClient.ledgerEntry(
+ LedgerEntryRequestParams.index(checkObject.index(), LedgerSpecifier.VALIDATED)
+ );
+
+ assertThat(entryByIndex.node()).isEqualTo(entryByIndexUnTyped.node());
+ }
}
diff --git a/xrpl4j-integration-tests/src/test/java/org/xrpl/xrpl4j/tests/ClawbackIT.java b/xrpl4j-integration-tests/src/test/java/org/xrpl/xrpl4j/tests/ClawbackIT.java
new file mode 100644
index 000000000..ace2e32f1
--- /dev/null
+++ b/xrpl4j-integration-tests/src/test/java/org/xrpl/xrpl4j/tests/ClawbackIT.java
@@ -0,0 +1,173 @@
+package org.xrpl.xrpl4j.tests;
+
+import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.google.common.primitives.UnsignedInteger;
+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.signing.SingleSignedTransaction;
+import org.xrpl.xrpl4j.model.client.accounts.AccountInfoRequestParams;
+import org.xrpl.xrpl4j.model.client.accounts.AccountInfoResult;
+import org.xrpl.xrpl4j.model.client.accounts.TrustLine;
+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.transactions.AccountSet;
+import org.xrpl.xrpl4j.model.transactions.AccountSet.AccountSetFlag;
+import org.xrpl.xrpl4j.model.transactions.Address;
+import org.xrpl.xrpl4j.model.transactions.Clawback;
+import org.xrpl.xrpl4j.model.transactions.IssuedCurrencyAmount;
+import org.xrpl.xrpl4j.model.transactions.TransactionResultCodes;
+import org.xrpl.xrpl4j.model.transactions.XrpCurrencyAmount;
+
+@DisabledIf(value = "shouldNotRun", disabledReason = "ClawbackIT only runs on local rippled node or devnet.")
+public class ClawbackIT extends AbstractIT {
+
+ static boolean shouldNotRun() {
+ return System.getProperty("useTestnet") != null ||
+ System.getProperty("useClioTestnet") != null;
+ }
+
+ @Test
+ void issueBalanceAndClawback() throws JsonRpcClientErrorException, JsonProcessingException {
+ KeyPair issuerKeyPair = createRandomAccountEd25519();
+ KeyPair holderKeyPair = createRandomAccountEd25519();
+
+ FeeResult feeResult = xrplClient.fee();
+ AccountInfoResult issuerAccount = this.scanForResult(
+ () -> this.getValidatedAccountInfo(issuerKeyPair.publicKey().deriveAddress())
+ );
+
+ XrpCurrencyAmount fee = FeeUtils.computeNetworkFees(feeResult).recommendedFee();
+ setAllowClawback(issuerKeyPair, issuerAccount, fee);
+
+ createTrustLine(
+ "USD",
+ "10000",
+ issuerKeyPair,
+ holderKeyPair,
+ fee
+ );
+
+ sendIssuedCurrency(
+ "USD",
+ "100",
+ issuerKeyPair,
+ holderKeyPair,
+ fee
+ );
+
+ issuerAccount = this.getValidatedAccountInfo(issuerAccount.accountData().account());
+ clawback(
+ "USD",
+ "10",
+ holderKeyPair.publicKey().deriveAddress(),
+ issuerKeyPair,
+ issuerAccount,
+ fee
+ );
+
+ TrustLine trustline = this.getValidatedAccountLines(
+ issuerAccount.accountData().account(),
+ holderKeyPair.publicKey().deriveAddress()
+ ).lines().stream()
+ .filter(line -> line.currency().equals("USD"))
+ .findFirst()
+ .orElseThrow(() -> new RuntimeException("No trustline found."));
+
+ assertThat(trustline.balance()).isEqualTo("-90");
+
+ issuerAccount = this.getValidatedAccountInfo(issuerAccount.accountData().account());
+ clawback(
+ "USD",
+ "90",
+ holderKeyPair.publicKey().deriveAddress(),
+ issuerKeyPair,
+ issuerAccount,
+ fee
+ );
+
+ trustline = this.getValidatedAccountLines(
+ issuerAccount.accountData().account(),
+ holderKeyPair.publicKey().deriveAddress()
+ ).lines().stream()
+ .filter(line -> line.currency().equals("USD"))
+ .findFirst()
+ .orElseThrow(() -> new RuntimeException("No trustline found."));
+ assertThat(trustline.balance()).isEqualTo("0");
+ }
+
+ private void clawback(
+ String currencyCode,
+ String amount,
+ Address holderAddress,
+ KeyPair issuerKeyPair,
+ AccountInfoResult issuerAccountInfo,
+ XrpCurrencyAmount fee
+ ) throws JsonRpcClientErrorException, JsonProcessingException {
+ Clawback clawback = Clawback.builder()
+ .account(issuerKeyPair.publicKey().deriveAddress())
+ .fee(fee)
+ .sequence(issuerAccountInfo.accountData().sequence())
+ .signingPublicKey(issuerKeyPair.publicKey())
+ .amount(
+ IssuedCurrencyAmount.builder()
+ .currency(currencyCode)
+ .value(amount)
+ .issuer(holderAddress)
+ .build()
+ )
+ .lastLedgerSequence(issuerAccountInfo.ledgerIndexSafe().unsignedIntegerValue().plus(UnsignedInteger.valueOf(4)))
+ .build();
+
+ SingleSignedTransaction signedClawback = signatureService.sign(issuerKeyPair.privateKey(), clawback);
+ SubmitResult submitResult = xrplClient.submit(signedClawback);
+ assertThat(submitResult.engineResult()).isEqualTo(TransactionResultCodes.TES_SUCCESS);
+
+ scanForFinality(
+ signedClawback.hash(),
+ issuerAccountInfo.ledgerIndexSafe(),
+ clawback.lastLedgerSequence().get(),
+ clawback.sequence(),
+ clawback.account()
+ );
+ }
+
+ private void setAllowClawback(
+ KeyPair issuerKeyPair,
+ AccountInfoResult issuerAccount,
+ XrpCurrencyAmount fee
+ ) throws JsonRpcClientErrorException, JsonProcessingException {
+ AccountSet accountSet = AccountSet.builder()
+ .account(issuerAccount.accountData().account())
+ .fee(fee)
+ .sequence(issuerAccount.accountData().sequence())
+ .signingPublicKey(issuerKeyPair.publicKey())
+ .lastLedgerSequence(issuerAccount.ledgerIndexSafe().unsignedIntegerValue().plus(UnsignedInteger.valueOf(4)))
+ .setFlag(AccountSetFlag.ALLOW_TRUSTLINE_CLAWBACK)
+ .build();
+
+ SingleSignedTransaction signedAccountSet = signatureService.sign(
+ issuerKeyPair.privateKey(), accountSet
+ );
+ SubmitResult submitResult = xrplClient.submit(signedAccountSet);
+ assertThat(submitResult.engineResult()).isEqualTo(TransactionResultCodes.TES_SUCCESS);
+
+ scanForFinality(
+ signedAccountSet.hash(),
+ issuerAccount.ledgerIndexSafe(),
+ accountSet.lastLedgerSequence().get(),
+ accountSet.sequence(),
+ accountSet.account()
+ );
+
+ AccountInfoResult accountInfoAfterSet = xrplClient.accountInfo(
+ AccountInfoRequestParams.of(issuerAccount.accountData().account())
+ );
+
+ assertThat(accountInfoAfterSet.accountData().flags().lsfAllowTrustLineClawback()).isTrue();
+ }
+}
diff --git a/xrpl4j-integration-tests/src/test/java/org/xrpl/xrpl4j/tests/DepositPreAuthIT.java b/xrpl4j-integration-tests/src/test/java/org/xrpl/xrpl4j/tests/DepositPreAuthIT.java
index 28c86519e..5b9c2d618 100644
--- a/xrpl4j-integration-tests/src/test/java/org/xrpl/xrpl4j/tests/DepositPreAuthIT.java
+++ b/xrpl4j-integration-tests/src/test/java/org/xrpl/xrpl4j/tests/DepositPreAuthIT.java
@@ -33,10 +33,16 @@
import org.xrpl.xrpl4j.model.client.common.LedgerSpecifier;
import org.xrpl.xrpl4j.model.client.fees.FeeResult;
import org.xrpl.xrpl4j.model.client.fees.FeeUtils;
+import org.xrpl.xrpl4j.model.client.ledger.DepositPreAuthLedgerEntryParams;
+import org.xrpl.xrpl4j.model.client.ledger.LedgerEntryRequestParams;
+import org.xrpl.xrpl4j.model.client.ledger.LedgerEntryResult;
import org.xrpl.xrpl4j.model.client.path.DepositAuthorizedRequestParams;
import org.xrpl.xrpl4j.model.client.transactions.SubmitResult;
import org.xrpl.xrpl4j.model.client.transactions.TransactionResult;
+import org.xrpl.xrpl4j.model.ledger.CheckObject;
import org.xrpl.xrpl4j.model.ledger.DepositPreAuthObject;
+import org.xrpl.xrpl4j.model.ledger.EscrowObject;
+import org.xrpl.xrpl4j.model.ledger.LedgerObject;
import org.xrpl.xrpl4j.model.transactions.AccountSet;
import org.xrpl.xrpl4j.model.transactions.DepositPreAuth;
import org.xrpl.xrpl4j.model.transactions.Hash256;
@@ -85,14 +91,20 @@ public void preauthorizeAccountAndReceivePayment() throws JsonRpcClientErrorExce
/////////////////////////
// Validate that the DepositPreAuthObject was added to the receiver's account objects
- this.scanForResult(
+ DepositPreAuthObject preAuthObject = (DepositPreAuthObject) this.scanForResult(
() -> this.getValidatedAccountObjects(receiverKeyPair.publicKey().deriveAddress()),
accountObjects ->
accountObjects.accountObjects().stream().anyMatch(object ->
DepositPreAuthObject.class.isAssignableFrom(object.getClass()) &&
((DepositPreAuthObject) object).authorize().equals(senderKeyPair.publicKey().deriveAddress())
)
- );
+ ).accountObjects().stream()
+ .filter(object -> DepositPreAuthObject.class.isAssignableFrom(object.getClass()) &&
+ ((DepositPreAuthObject) object).authorize().equals(senderKeyPair.publicKey().deriveAddress()))
+ .findFirst()
+ .get();
+
+ assertEntryEqualsObjectFromAccountObjects(depositPreAuth, preAuthObject);
/////////////////////////
// Validate that the `deposit_authorized` client call is implemented properly by ensuring it aligns with the
@@ -266,4 +278,34 @@ private AccountInfoResult enableDepositPreauth(
accountInfo -> accountInfo.accountData().flags().lsfDepositAuth()
);
}
+
+ private void assertEntryEqualsObjectFromAccountObjects(
+ DepositPreAuth depositPreAuth,
+ DepositPreAuthObject preAuthObject
+ )
+ throws JsonRpcClientErrorException {
+ LedgerEntryResult preAuthEntry = xrplClient.ledgerEntry(
+ LedgerEntryRequestParams.depositPreAuth(
+ DepositPreAuthLedgerEntryParams.builder()
+ .owner(depositPreAuth.account())
+ .authorized(depositPreAuth.authorize().get())
+ .build(),
+ LedgerSpecifier.CURRENT
+ )
+ );
+
+ assertThat(preAuthEntry.node()).isEqualTo(preAuthObject);
+
+ LedgerEntryResult entryByIndex = xrplClient.ledgerEntry(
+ LedgerEntryRequestParams.index(preAuthObject.index(), DepositPreAuthObject.class, LedgerSpecifier.VALIDATED)
+ );
+
+ assertThat(entryByIndex.node()).isEqualTo(preAuthEntry.node());
+
+ LedgerEntryResult entryByIndexUnTyped = xrplClient.ledgerEntry(
+ LedgerEntryRequestParams.index(preAuthObject.index(), LedgerSpecifier.VALIDATED)
+ );
+
+ assertThat(entryByIndex.node()).isEqualTo(entryByIndexUnTyped.node());
+ }
}
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 fed3ba8a9..5d34919b7 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
@@ -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.
@@ -27,16 +27,25 @@
import com.google.common.primitives.UnsignedLong;
import com.ripple.cryptoconditions.PreimageSha256Fulfillment;
import org.junit.jupiter.api.Test;
+import org.testcontainers.shaded.com.github.dockerjava.core.dockerfile.DockerfileStatement.Add;
import org.xrpl.xrpl4j.client.JsonRpcClientErrorException;
import org.xrpl.xrpl4j.crypto.keys.KeyPair;
import org.xrpl.xrpl4j.crypto.signing.SingleSignedTransaction;
import org.xrpl.xrpl4j.model.client.accounts.AccountInfoResult;
+import org.xrpl.xrpl4j.model.client.accounts.AccountObjectsRequestParams;
+import org.xrpl.xrpl4j.model.client.accounts.AccountObjectsRequestParams.AccountObjectType;
+import org.xrpl.xrpl4j.model.client.common.LedgerSpecifier;
import org.xrpl.xrpl4j.model.client.fees.FeeResult;
+import org.xrpl.xrpl4j.model.client.ledger.EscrowLedgerEntryParams;
+import org.xrpl.xrpl4j.model.client.ledger.LedgerEntryRequestParams;
+import org.xrpl.xrpl4j.model.client.ledger.LedgerEntryResult;
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.immutables.FluentCompareTo;
import org.xrpl.xrpl4j.model.ledger.EscrowObject;
+import org.xrpl.xrpl4j.model.ledger.LedgerObject;
+import org.xrpl.xrpl4j.model.transactions.Address;
import org.xrpl.xrpl4j.model.transactions.EscrowCancel;
import org.xrpl.xrpl4j.model.transactions.EscrowCreate;
import org.xrpl.xrpl4j.model.transactions.EscrowFinish;
@@ -92,6 +101,11 @@ public void createAndFinishTimeBasedEscrow() throws JsonRpcClientErrorException,
() -> this.getValidatedTransaction(createResult.transactionResult().hash(), EscrowCreate.class)
);
+ assertEntryEqualsObjectFromAccountObjects(
+ senderKeyPair.publicKey().deriveAddress(),
+ escrowCreate.sequence()
+ );
+
//////////////////////
// Wait until the close time on the current validated ledger is after the finishAfter time on the Escrow
this.scanForResult(
@@ -188,7 +202,7 @@ public void createAndCancelTimeBasedEscrow() throws JsonRpcClientErrorException,
//////////////////////
// Then wait until the transaction gets committed to a validated ledger
- TransactionResult result = this.scanForResult(
+ final TransactionResult result = this.scanForResult(
() -> this.getValidatedTransaction(createResult.transactionResult().hash(), EscrowCreate.class)
);
@@ -201,6 +215,11 @@ public void createAndCancelTimeBasedEscrow() throws JsonRpcClientErrorException,
)
);
+ assertEntryEqualsObjectFromAccountObjects(
+ senderKeyPair.publicKey().deriveAddress(),
+ escrowCreate.sequence()
+ );
+
//////////////////////
// Wait until the close time on the current validated ledger is after the cancelAfter time on the Escrow
this.scanForResult(
@@ -298,6 +317,11 @@ public void createAndFinishCryptoConditionBasedEscrow() throws JsonRpcClientErro
() -> this.getValidatedTransaction(createResult.transactionResult().hash(), EscrowCreate.class)
);
+ assertEntryEqualsObjectFromAccountObjects(
+ senderKeyPair.publicKey().deriveAddress(),
+ escrowCreate.sequence()
+ );
+
//////////////////////
// Wait until the close time on the current validated ledger is after the finishAfter time on the Escrow
this.scanForResult(
@@ -405,6 +429,11 @@ public void createAndCancelCryptoConditionBasedEscrow() throws JsonRpcClientErro
() -> this.getValidatedTransaction(createResult.transactionResult().hash(), EscrowCreate.class)
);
+ assertEntryEqualsObjectFromAccountObjects(
+ senderKeyPair.publicKey().deriveAddress(),
+ escrowCreate.sequence()
+ );
+
//////////////////////
// Wait until the close time on the current validated ledger is after the cancelAfter time on the Escrow
this.scanForResult(
@@ -475,4 +504,41 @@ private Instant getMinExpirationTime() {
return closeTime.isBefore(now) ? now : closeTime;
}
+
+ private void assertEntryEqualsObjectFromAccountObjects(
+ Address escrowOwner,
+ UnsignedInteger createSequence
+ ) throws JsonRpcClientErrorException {
+
+ EscrowObject escrowObject = (EscrowObject) xrplClient.accountObjects(AccountObjectsRequestParams.builder()
+ .type(AccountObjectType.ESCROW)
+ .account(escrowOwner)
+ .ledgerSpecifier(LedgerSpecifier.VALIDATED)
+ .build()
+ ).accountObjects().get(0);
+
+ LedgerEntryResult escrowEntry = xrplClient.ledgerEntry(
+ LedgerEntryRequestParams.escrow(
+ EscrowLedgerEntryParams.builder()
+ .owner(escrowOwner)
+ .seq(createSequence)
+ .build(),
+ LedgerSpecifier.VALIDATED
+ )
+ );
+
+ assertThat(escrowEntry.node()).isEqualTo(escrowObject);
+
+ LedgerEntryResult entryByIndex = xrplClient.ledgerEntry(
+ LedgerEntryRequestParams.index(escrowObject.index(), EscrowObject.class, LedgerSpecifier.VALIDATED)
+ );
+
+ assertThat(entryByIndex.node()).isEqualTo(escrowEntry.node());
+
+ LedgerEntryResult entryByIndexUnTyped = xrplClient.ledgerEntry(
+ LedgerEntryRequestParams.index(escrowObject.index(), LedgerSpecifier.VALIDATED)
+ );
+
+ assertThat(entryByIndex.node()).isEqualTo(entryByIndexUnTyped.node());
+ }
}
diff --git a/xrpl4j-integration-tests/src/test/java/org/xrpl/xrpl4j/tests/IssuedCurrencyIT.java b/xrpl4j-integration-tests/src/test/java/org/xrpl/xrpl4j/tests/IssuedCurrencyIT.java
index 5ab7f776e..670135a9a 100644
--- a/xrpl4j-integration-tests/src/test/java/org/xrpl/xrpl4j/tests/IssuedCurrencyIT.java
+++ b/xrpl4j-integration-tests/src/test/java/org/xrpl/xrpl4j/tests/IssuedCurrencyIT.java
@@ -25,6 +25,7 @@
import com.fasterxml.jackson.core.JsonProcessingException;
import com.google.common.base.Strings;
import com.google.common.io.BaseEncoding;
+import com.google.common.primitives.UnsignedInteger;
import org.junit.jupiter.api.Test;
import org.xrpl.xrpl4j.client.JsonRpcClientErrorException;
import org.xrpl.xrpl4j.crypto.keys.KeyPair;
@@ -32,12 +33,21 @@
import org.xrpl.xrpl4j.model.client.accounts.AccountCurrenciesRequestParams;
import org.xrpl.xrpl4j.model.client.accounts.AccountCurrenciesResult;
import org.xrpl.xrpl4j.model.client.accounts.AccountInfoResult;
+import org.xrpl.xrpl4j.model.client.accounts.AccountObjectsRequestParams;
import org.xrpl.xrpl4j.model.client.accounts.TrustLine;
import org.xrpl.xrpl4j.model.client.common.LedgerSpecifier;
import org.xrpl.xrpl4j.model.client.fees.FeeResult;
import org.xrpl.xrpl4j.model.client.fees.FeeUtils;
+import org.xrpl.xrpl4j.model.client.ledger.LedgerEntryRequestParams;
+import org.xrpl.xrpl4j.model.client.ledger.LedgerEntryResult;
+import org.xrpl.xrpl4j.model.client.ledger.RippleStateLedgerEntryParams;
+import org.xrpl.xrpl4j.model.client.ledger.RippleStateLedgerEntryParams.RippleStateAccounts;
import org.xrpl.xrpl4j.model.client.transactions.SubmitResult;
+import org.xrpl.xrpl4j.model.ledger.EscrowObject;
+import org.xrpl.xrpl4j.model.ledger.LedgerObject;
+import org.xrpl.xrpl4j.model.ledger.RippleStateObject;
import org.xrpl.xrpl4j.model.transactions.AccountSet;
+import org.xrpl.xrpl4j.model.transactions.Address;
import org.xrpl.xrpl4j.model.transactions.IssuedCurrencyAmount;
import org.xrpl.xrpl4j.model.transactions.PathStep;
import org.xrpl.xrpl4j.model.transactions.Payment;
@@ -77,6 +87,12 @@ void createTrustlineWithMaxLimit() throws JsonRpcClientErrorException, JsonProce
FeeUtils.computeNetworkFees(feeResult).recommendedFee()
);
+ assertThatEntryEqualsObjectFromAccountObjects(
+ trustLine,
+ issuerKeyPair.publicKey().deriveAddress(),
+ xrpl4jCoin
+ );
+
assertThat(trustLine.limitPeer()).isEqualTo("9999999999999999e80");
}
@@ -102,6 +118,12 @@ void createTrustlineWithMaxLimitMinusOneExponent() throws JsonRpcClientErrorExce
FeeUtils.computeNetworkFees(feeResult).recommendedFee()
);
+ assertThatEntryEqualsObjectFromAccountObjects(
+ trustLine,
+ issuerKeyPair.publicKey().deriveAddress(),
+ xrpl4jCoin
+ );
+
assertThat(trustLine.limitPeer()).isEqualTo("9999999999999999e79");
}
@@ -127,6 +149,12 @@ void createTrustlineWithSmallestPositiveLimit() throws JsonRpcClientErrorExcepti
FeeUtils.computeNetworkFees(feeResult).recommendedFee()
);
+ assertThatEntryEqualsObjectFromAccountObjects(
+ trustLine,
+ issuerKeyPair.publicKey().deriveAddress(),
+ xrpl4jCoin
+ );
+
assertThat(trustLine.limitPeer()).isEqualTo("1000000000000000e-96");
}
@@ -154,6 +182,12 @@ void createTrustlineWithSmalletPositiveLimitPlusOne() throws JsonRpcClientErrorE
FeeUtils.computeNetworkFees(feeResult).recommendedFee()
);
+ assertThatEntryEqualsObjectFromAccountObjects(
+ trustLine,
+ issuerKeyPair.publicKey().deriveAddress(),
+ xrpl4jCoin
+ );
+
assertThat(trustLine.limitPeer()).isEqualTo("1100000000000000e-96");
}
@@ -179,9 +213,15 @@ public void issueIssuedCurrencyBalance() throws JsonRpcClientErrorException, Jso
FeeUtils.computeNetworkFees(feeResult).recommendedFee()
);
+ assertThatEntryEqualsObjectFromAccountObjects(
+ trustLine,
+ issuerKeyPair.publicKey().deriveAddress(),
+ xrpl4jCoin
+ );
+
///////////////////////////
// Send some xrpl4jCoin to the counterparty account.
- issueBalance(
+ sendIssuedCurrency(
xrpl4jCoin, trustLine.limitPeer(), issuerKeyPair, counterpartyKeyPair,
FeeUtils.computeNetworkFees(feeResult).recommendedFee()
);
@@ -189,9 +229,19 @@ public void issueIssuedCurrencyBalance() throws JsonRpcClientErrorException, Jso
///////////////////////////
// Validate that the TrustLine balance was updated as a result of the Payment.
// The trust line returned is from the perspective of the issuer, so the balance should be negative.
- this.scanForResult(() -> getValidatedAccountLines(issuerKeyPair.publicKey().deriveAddress(),
- counterpartyKeyPair.publicKey().deriveAddress()),
+ TrustLine trustLineAfterPayment = this.scanForResult(
+ () -> getValidatedAccountLines(issuerKeyPair.publicKey().deriveAddress(),
+ counterpartyKeyPair.publicKey().deriveAddress()),
linesResult -> linesResult.lines().stream().anyMatch(line -> line.balance().equals("-" + trustLine.limitPeer()))
+ ).lines().stream()
+ .filter(line -> line.balance().equals("-" + trustLine.limitPeer()))
+ .findFirst()
+ .get();
+
+ assertThatEntryEqualsObjectFromAccountObjects(
+ trustLineAfterPayment,
+ issuerKeyPair.publicKey().deriveAddress(),
+ xrpl4jCoin
);
///////////////////////////
@@ -245,11 +295,23 @@ public void sendSimpleRipplingIssuedCurrencyPayment() throws JsonRpcClientErrorE
///////////////////////////
// Issuer issues 50 USD to alice
- issueBalance("USD", "50", issuerKeyPair, aliceKeyPair, FeeUtils.computeNetworkFees(feeResult).recommendedFee());
+ sendIssuedCurrency(
+ "USD",
+ "50",
+ issuerKeyPair,
+ aliceKeyPair,
+ FeeUtils.computeNetworkFees(feeResult).recommendedFee()
+ );
///////////////////////////
// Issuer issues 50 USD to bob
- issueBalance("USD", "50", issuerKeyPair, bobKeyPair, FeeUtils.computeNetworkFees(feeResult).recommendedFee());
+ sendIssuedCurrency(
+ "USD",
+ "50",
+ issuerKeyPair,
+ bobKeyPair,
+ FeeUtils.computeNetworkFees(feeResult).recommendedFee()
+ );
///////////////////////////
// Try to find a path for this Payment.
@@ -386,22 +448,46 @@ public void sendMultiHopSameCurrencyPayment() throws JsonRpcClientErrorException
///////////////////////////
// Issue 10 USD from issuerA to charlie.
// IssuerA now owes Charlie 10 USD.
- issueBalance("USD", "10", issuerAKeyPair, charlieKeyPair, FeeUtils.computeNetworkFees(feeResult).recommendedFee());
+ sendIssuedCurrency(
+ "USD",
+ "10",
+ issuerAKeyPair,
+ charlieKeyPair,
+ FeeUtils.computeNetworkFees(feeResult).recommendedFee()
+ );
///////////////////////////
// Issue 1 USD from issuerA to emily.
// IssuerA now owes Emily 1 USD
- issueBalance("USD", "1", issuerAKeyPair, emilyKeyPair, FeeUtils.computeNetworkFees(feeResult).recommendedFee());
+ sendIssuedCurrency(
+ "USD",
+ "1",
+ issuerAKeyPair,
+ emilyKeyPair,
+ FeeUtils.computeNetworkFees(feeResult).recommendedFee()
+ );
///////////////////////////
// Issue 100 USD from issuerB to emily.
// IssuerB now owes Emily 100 USD
- issueBalance("USD", "100", issuerBKeyPair, emilyKeyPair, FeeUtils.computeNetworkFees(feeResult).recommendedFee());
+ sendIssuedCurrency(
+ "USD",
+ "100",
+ issuerBKeyPair,
+ emilyKeyPair,
+ FeeUtils.computeNetworkFees(feeResult).recommendedFee()
+ );
///////////////////////////
// Issue 2 USD from issuerB to daniel.
// IssuerB now owes Daniel 2 USD
- issueBalance("USD", "2", issuerBKeyPair, danielKeyPair, FeeUtils.computeNetworkFees(feeResult).recommendedFee());
+ sendIssuedCurrency(
+ "USD",
+ "2",
+ issuerBKeyPair,
+ danielKeyPair,
+ FeeUtils.computeNetworkFees(feeResult).recommendedFee()
+ );
///////////////////////////
// Look for a payment path from charlie to daniel.
@@ -523,106 +609,42 @@ public void setDefaultRipple(KeyPair issuerKeyPair, FeeResult feeResult)
);
}
- /**
- * Send issued currency funds from an issuer to a counterparty.
- *
- * @param currency The currency code to send.
- * @param value The amount of currency to send.
- * @param issuerKeyPair The {@link KeyPair} of the issuer account.
- * @param counterpartyKeyPair The {@link KeyPair} of the counterparty account.
- * @param fee The current network fee, as an {@link XrpCurrencyAmount}.
- * @throws JsonRpcClientErrorException If anything goes wrong while communicating with rippled.
- */
- public void issueBalance(
- String currency,
- String value,
- KeyPair issuerKeyPair,
- KeyPair counterpartyKeyPair,
- XrpCurrencyAmount fee
- ) throws JsonRpcClientErrorException, JsonProcessingException {
- ///////////////////////////
- // Issuer sends a payment with the issued currency to the counterparty
- AccountInfoResult issuerAccountInfo = this.scanForResult(
- () -> getValidatedAccountInfo(issuerKeyPair.publicKey().deriveAddress())
+
+ private void assertThatEntryEqualsObjectFromAccountObjects(
+ TrustLine trustLine,
+ Address peerAddress,
+ String currency
+ ) throws JsonRpcClientErrorException {
+ RippleStateObject rippleStateObject = (RippleStateObject) xrplClient.accountObjects(
+ AccountObjectsRequestParams.of(trustLine.account())
+ ).accountObjects().stream()
+ .filter(object -> RippleStateObject.class.isAssignableFrom(object.getClass()) /*&&*/
+ /*((RippleStateObject) object).previousTransactionLedgerSequence().equals(lastSequence)*/)
+ .findFirst()
+ .get();
+
+ LedgerEntryResult entry = xrplClient.ledgerEntry(
+ LedgerEntryRequestParams.rippleState(
+ RippleStateLedgerEntryParams.builder()
+ .accounts(RippleStateAccounts.of(trustLine.account(), peerAddress))
+ .currency(currency)
+ .build(),
+ LedgerSpecifier.VALIDATED
+ )
);
- Payment fundCounterparty = Payment.builder()
- .account(issuerKeyPair.publicKey().deriveAddress())
- .fee(fee)
- .sequence(issuerAccountInfo.accountData().sequence())
- .destination(counterpartyKeyPair.publicKey().deriveAddress())
- .amount(IssuedCurrencyAmount.builder()
- .issuer(issuerKeyPair.publicKey().deriveAddress())
- .currency(currency)
- .value(value)
- .build())
- .signingPublicKey(issuerKeyPair.publicKey())
- .build();
+ assertThat(entry.node()).isEqualTo(rippleStateObject);
- SingleSignedTransaction signedFundCounterparty = signatureService.sign(
- issuerKeyPair.privateKey(), fundCounterparty
- );
- SubmitResult paymentResult = xrplClient.submit(signedFundCounterparty);
- assertThat(paymentResult.engineResult()).isEqualTo("tesSUCCESS");
- logger.info(
- "Payment transaction successful: https://testnet.xrpl.org/transactions/{}",
- paymentResult.transactionResult().hash()
+ LedgerEntryResult entryByIndex = xrplClient.ledgerEntry(
+ LedgerEntryRequestParams.index(rippleStateObject.index(), RippleStateObject.class, LedgerSpecifier.VALIDATED)
);
- this.scanForResult(() -> getValidatedTransaction(paymentResult.transactionResult().hash(), Payment.class));
- }
-
- /**
- * Create a trustline between the given issuer and counterparty accounts for the given currency code and with the
- * given limit.
- *
- * @param currency The currency code of the trustline to create.
- * @param value The trustline limit of the trustline to create.
- * @param issuerKeyPair The {@link KeyPair} of the issuer account.
- * @param counterpartyKeyPair The {@link KeyPair} of the counterparty account.
- * @param fee The current network fee, as an {@link XrpCurrencyAmount}.
- * @return The {@link TrustLine} that gets created.
- * @throws JsonRpcClientErrorException If anything goes wrong while communicating with rippled.
- */
- public TrustLine createTrustLine(
- String currency,
- String value,
- KeyPair issuerKeyPair,
- KeyPair counterpartyKeyPair,
- XrpCurrencyAmount fee
- ) throws JsonRpcClientErrorException, JsonProcessingException {
- AccountInfoResult counterpartyAccountInfo = this.scanForResult(
- () -> this.getValidatedAccountInfo(counterpartyKeyPair.publicKey().deriveAddress())
- );
-
- TrustSet trustSet = TrustSet.builder()
- .account(counterpartyKeyPair.publicKey().deriveAddress())
- .fee(fee)
- .sequence(counterpartyAccountInfo.accountData().sequence())
- .limitAmount(IssuedCurrencyAmount.builder()
- .currency(currency)
- .issuer(issuerKeyPair.publicKey().deriveAddress())
- .value(value)
- .build())
- .signingPublicKey(counterpartyKeyPair.publicKey())
- .build();
+ assertThat(entryByIndex.node()).isEqualTo(entry.node());
- SingleSignedTransaction signedTrustSet = signatureService.sign(counterpartyKeyPair.privateKey(),
- trustSet);
- SubmitResult trustSetSubmitResult = xrplClient.submit(signedTrustSet);
- assertThat(trustSetSubmitResult.engineResult()).isEqualTo("tesSUCCESS");
- logger.info(
- "TrustSet transaction successful: https://testnet.xrpl.org/transactions/{}",
- trustSetSubmitResult.transactionResult().hash()
+ LedgerEntryResult entryByIndexUnTyped = xrplClient.ledgerEntry(
+ LedgerEntryRequestParams.index(rippleStateObject.index(), LedgerSpecifier.VALIDATED)
);
- return scanForResult(
- () ->
- getValidatedAccountLines(issuerKeyPair.publicKey().deriveAddress(),
- counterpartyKeyPair.publicKey().deriveAddress()),
- linesResult -> !linesResult.lines().isEmpty()
- )
- .lines().get(0);
+ assertThat(entryByIndex.node()).isEqualTo(entryByIndexUnTyped.node());
}
-
}
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 cc0a143d2..5204f08f8 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
@@ -26,6 +26,7 @@
import com.google.common.collect.Lists;
import com.google.common.primitives.UnsignedInteger;
import com.google.common.primitives.UnsignedLong;
+import org.jetbrains.annotations.NotNull;
import org.junit.jupiter.api.Test;
import org.xrpl.xrpl4j.client.JsonRpcClientErrorException;
import org.xrpl.xrpl4j.crypto.keys.KeyPair;
@@ -34,9 +35,11 @@
import org.xrpl.xrpl4j.model.client.accounts.AccountInfoResult;
import org.xrpl.xrpl4j.model.client.accounts.AccountNftsResult;
import org.xrpl.xrpl4j.model.client.accounts.AccountObjectsRequestParams;
-import org.xrpl.xrpl4j.model.client.accounts.AccountObjectsResult;
import org.xrpl.xrpl4j.model.client.accounts.NfTokenObject;
+import org.xrpl.xrpl4j.model.client.common.LedgerSpecifier;
import org.xrpl.xrpl4j.model.client.fees.FeeUtils;
+import org.xrpl.xrpl4j.model.client.ledger.LedgerEntryRequestParams;
+import org.xrpl.xrpl4j.model.client.ledger.LedgerEntryResult;
import org.xrpl.xrpl4j.model.client.nft.NftBuyOffersRequestParams;
import org.xrpl.xrpl4j.model.client.nft.NftBuyOffersResult;
import org.xrpl.xrpl4j.model.client.nft.NftSellOffersRequestParams;
@@ -112,7 +115,7 @@ void mint() throws JsonRpcClientErrorException, JsonProcessingException {
},
result -> result.accountNfts().stream()
.anyMatch(nft -> nft.uri().get().equals(uri))
- )
+ )
.accountNfts()
.stream().filter(nft -> nft.uri().get().equals(uri))
.findFirst()
@@ -121,22 +124,7 @@ void mint() throws JsonRpcClientErrorException, JsonProcessingException {
assertThat(validatedMint.metadata().flatMap(TransactionMetadata::nfTokenId))
.isNotEmpty().get().isEqualTo(nfToken.nfTokenId());
- Optional maybeNfTokenPage = xrplClient.accountObjects(
- AccountObjectsRequestParams.of(nfTokenMint.account())
- ).accountObjects().stream()
- .filter(object -> NfTokenPageObject.class.isAssignableFrom(object.getClass()))
- .map(object -> (NfTokenPageObject) object)
- .findFirst();
-
- assertThat(maybeNfTokenPage).isNotEmpty();
- assertThat(maybeNfTokenPage.get().nfTokens()).contains(
- NfTokenWrapper.of(
- NfToken.builder()
- .nfTokenId(nfToken.nfTokenId())
- .uri(nfToken.uri())
- .build()
- )
- );
+ assertEntryEqualsObjectFromAccountObjects(keyPair.publicKey().deriveAddress(), nfToken);
AccountInfoResult minterAccountInfo = xrplClient.accountInfo(
AccountInfoRequestParams.of(keyPair.publicKey().deriveAddress())
@@ -198,18 +186,9 @@ void mintFromOtherMinterAccount() throws JsonRpcClientErrorException, JsonProces
assertThat(mintSubmitResult.engineResult()).isEqualTo(TransactionResultCodes.TES_SUCCESS);
assertThat(signedMint.hash()).isEqualTo(mintSubmitResult.transactionResult().hash());
- this.scanForResult(
- () -> {
- try {
- return xrplClient.accountNfts(minterKeyPair.publicKey().deriveAddress());
- } catch (JsonRpcClientErrorException e) {
- logger.error("Exception occurred while getting account nfts: {}", e.getMessage(), e);
- throw new RuntimeException(e);
- }
- },
- result -> result.accountNfts().stream()
- .anyMatch(nft -> nft.uri().get().equals(uri))
- );
+ NfTokenObject nfToken = scanForNfToken(minterKeyPair, uri);
+
+ assertEntryEqualsObjectFromAccountObjects(minterKeyPair.publicKey().deriveAddress(), nfToken);
AccountInfoResult sourceAccountInfoAfterMint = xrplClient.accountInfo(
AccountInfoRequestParams.of(keyPair.publicKey().deriveAddress())
@@ -247,19 +226,11 @@ void mintAndBurn() throws JsonRpcClientErrorException, JsonProcessingException {
assertThat(mintSubmitResult.engineResult()).isEqualTo(TransactionResultCodes.TES_SUCCESS);
assertThat(signedMint.hash()).isEqualTo(mintSubmitResult.transactionResult().hash());
- this.scanForResult(
- () -> {
- try {
- return xrplClient.accountNfts(keyPair.publicKey().deriveAddress());
- } catch (JsonRpcClientErrorException e) {
- throw new RuntimeException(e);
- }
- },
- result -> result.accountNfts().stream()
- .anyMatch(nft -> nft.uri().get().equals(uri))
- );
+ NfTokenObject nfToken = scanForNfToken(keyPair, uri);
logger.info("NFT was minted successfully.");
+ assertEntryEqualsObjectFromAccountObjects(keyPair.publicKey().deriveAddress(), nfToken);
+
// nft burn
AccountNftsResult accountNftsResult = xrplClient.accountNfts(keyPair.publicKey().deriveAddress());
@@ -335,19 +306,11 @@ void mintAndCreateOffer() throws JsonRpcClientErrorException, JsonProcessingExce
mintSubmitResult.transactionResult().hash()
);
- this.scanForResult(
- () -> {
- try {
- return xrplClient.accountNfts(keyPair.publicKey().deriveAddress());
- } catch (JsonRpcClientErrorException e) {
- throw new RuntimeException(e);
- }
- },
- result -> result.accountNfts().stream()
- .anyMatch(nft -> nft.uri().get().equals(uri))
- );
+ NfTokenObject nfToken = scanForNfToken(keyPair, uri);
logger.info("NFT was minted successfully.");
+ assertEntryEqualsObjectFromAccountObjects(keyPair.publicKey().deriveAddress(), nfToken);
+
//create a sell offer for the NFT that was created above
NfTokenId tokenId = xrplClient.accountNfts(keyPair.publicKey().deriveAddress()).accountNfts().get(0).nfTokenId();
@@ -394,6 +357,12 @@ void mintAndCreateOffer() throws JsonRpcClientErrorException, JsonProcessingExce
.findFirst()
.get();
+ LedgerEntryResult entry = xrplClient.ledgerEntry(
+ LedgerEntryRequestParams.index(nfTokenOffer.index(), NfTokenOfferObject.class, LedgerSpecifier.VALIDATED)
+ );
+
+ assertThat(entry.node()).isEqualTo(nfTokenOffer);
+
assertThat(validatedOfferCreate.metadata().flatMap(TransactionMetadata::offerId)).isNotEmpty().get()
.isEqualTo(nfTokenOffer.index());
logger.info("NFTokenOffer object was found in account's objects.");
@@ -401,20 +370,20 @@ void mintAndCreateOffer() throws JsonRpcClientErrorException, JsonProcessingExce
@Test
void mintAndCreateThenAcceptOffer() throws JsonRpcClientErrorException, JsonProcessingException {
- KeyPair wallet = createRandomAccountEd25519();
+ KeyPair keypair = createRandomAccountEd25519();
//mint NFT from one account
AccountInfoResult accountInfoResult = this.scanForResult(
- () -> this.getValidatedAccountInfo(wallet.publicKey().deriveAddress())
+ () -> this.getValidatedAccountInfo(keypair.publicKey().deriveAddress())
);
NfTokenUri uri = NfTokenUri.ofPlainText("ipfs://bafybeigdyrzt5sfp7udm7hu76uh7y26nf4dfuylqabf3oclgtqy55fbzdi");
//Nft mint transaction
NfTokenMint nfTokenMint = NfTokenMint.builder()
.tokenTaxon(UnsignedLong.ONE)
- .account(wallet.publicKey().deriveAddress())
+ .account(keypair.publicKey().deriveAddress())
.fee(XrpCurrencyAmount.ofDrops(50))
- .signingPublicKey(wallet.publicKey())
+ .signingPublicKey(keypair.publicKey())
.sequence(accountInfoResult.accountData().sequence())
.flags(NfTokenMintFlags.builder()
.tfTransferable(true)
@@ -422,27 +391,19 @@ void mintAndCreateThenAcceptOffer() throws JsonRpcClientErrorException, JsonProc
.uri(uri)
.build();
- SingleSignedTransaction signedMint = signatureService.sign(wallet.privateKey(), nfTokenMint);
+ SingleSignedTransaction signedMint = signatureService.sign(keypair.privateKey(), nfTokenMint);
SubmitResult mintSubmitResult = xrplClient.submit(signedMint);
assertThat(mintSubmitResult.engineResult()).isEqualTo(TransactionResultCodes.TES_SUCCESS);
assertThat(signedMint.hash()).isEqualTo(mintSubmitResult.transactionResult().hash());
- this.scanForResult(
- () -> {
- try {
- return xrplClient.accountNfts(wallet.publicKey().deriveAddress());
- } catch (JsonRpcClientErrorException e) {
- throw new RuntimeException(e);
- }
- },
- result -> result.accountNfts().stream()
- .anyMatch(nft -> nft.uri().get().equals(uri))
- );
+ NfTokenObject nfToken = scanForNfToken(keypair, uri);
logger.info("NFT was minted successfully.");
+ assertEntryEqualsObjectFromAccountObjects(keypair.publicKey().deriveAddress(), nfToken);
+
//create a sell offer for the NFT that was created above
- NfTokenId tokenId = xrplClient.accountNfts(wallet.publicKey().deriveAddress()).accountNfts().get(0).nfTokenId();
+ NfTokenId tokenId = xrplClient.accountNfts(keypair.publicKey().deriveAddress()).accountNfts().get(0).nfTokenId();
// create buy offer from another account
KeyPair wallet2 = createRandomAccountEd25519();
@@ -453,7 +414,7 @@ void mintAndCreateThenAcceptOffer() throws JsonRpcClientErrorException, JsonProc
NfTokenCreateOffer nfTokenCreateOffer = NfTokenCreateOffer.builder()
.account(wallet2.publicKey().deriveAddress())
- .owner(wallet.publicKey().deriveAddress())
+ .owner(keypair.publicKey().deriveAddress())
.nfTokenId(tokenId)
.fee(XrpCurrencyAmount.ofDrops(50))
.sequence(accountInfoResult2.accountData().sequence())
@@ -478,14 +439,26 @@ void mintAndCreateThenAcceptOffer() throws JsonRpcClientErrorException, JsonProc
);
logger.info("NFT Create Offer (Buy) transaction was validated successfully.");
- this.scanForResult(
+ NfTokenOfferObject nfTokenOffer = (NfTokenOfferObject) this.scanForResult(
() -> this.getValidatedAccountObjects(wallet2.publicKey().deriveAddress()),
objectsResult -> objectsResult.accountObjects().stream()
.anyMatch(object ->
NfTokenOfferObject.class.isAssignableFrom(object.getClass()) &&
((NfTokenOfferObject) object).owner().equals(wallet2.publicKey().deriveAddress())
)
+ ).accountObjects()
+ .stream()
+ .filter(object -> NfTokenOfferObject.class.isAssignableFrom(object.getClass()) &&
+ ((NfTokenOfferObject) object).owner().equals(wallet2.publicKey().deriveAddress()))
+ .findFirst()
+ .get();
+
+ LedgerEntryResult entry = xrplClient.ledgerEntry(
+ LedgerEntryRequestParams.index(nfTokenOffer.index(), NfTokenOfferObject.class, LedgerSpecifier.VALIDATED)
);
+
+ assertThat(entry.node()).isEqualTo(nfTokenOffer);
+
logger.info("NFTokenOffer object was found in account's objects.");
NftBuyOffersResult nftBuyOffersResult = xrplClient.nftBuyOffers(NftBuyOffersRequestParams.builder()
@@ -495,15 +468,15 @@ void mintAndCreateThenAcceptOffer() throws JsonRpcClientErrorException, JsonProc
// accept offer from a different account
NfTokenAcceptOffer nfTokenAcceptOffer = NfTokenAcceptOffer.builder()
- .account(wallet.publicKey().deriveAddress())
+ .account(keypair.publicKey().deriveAddress())
.buyOffer(nftBuyOffersResult.offers().get(0).nftOfferIndex())
.fee(XrpCurrencyAmount.ofDrops(50))
.sequence(accountInfoResult.accountData().sequence().plus(UnsignedInteger.ONE))
- .signingPublicKey(wallet.publicKey())
+ .signingPublicKey(keypair.publicKey())
.build();
SingleSignedTransaction signedAccept = signatureService.sign(
- wallet.privateKey(),
+ keypair.privateKey(),
nfTokenAcceptOffer
);
SubmitResult nfTokenAcceptOfferSubmitResult = xrplClient.submit(signedAccept);
@@ -518,7 +491,7 @@ void mintAndCreateThenAcceptOffer() throws JsonRpcClientErrorException, JsonProc
);
logger.info("NFT Accept Offer transaction was validated successfully.");
- assertThat(xrplClient.accountNfts(wallet.publicKey().deriveAddress()).accountNfts().size()).isEqualTo(0);
+ assertThat(xrplClient.accountNfts(keypair.publicKey().deriveAddress()).accountNfts().size()).isEqualTo(0);
assertThat(xrplClient.accountNfts(wallet2.publicKey().deriveAddress()).accountNfts().size()).isEqualTo(1);
logger.info("The NFT ownership was transferred.");
@@ -526,58 +499,49 @@ void mintAndCreateThenAcceptOffer() throws JsonRpcClientErrorException, JsonProc
@Test
void mintAndCreateOfferThenCancelOffer() throws JsonRpcClientErrorException, JsonProcessingException {
- KeyPair wallet = createRandomAccountEd25519();
+ KeyPair keypair = createRandomAccountEd25519();
//mint NFT from one account
AccountInfoResult accountInfoResult = this.scanForResult(
- () -> this.getValidatedAccountInfo(wallet.publicKey().deriveAddress())
+ () -> this.getValidatedAccountInfo(keypair.publicKey().deriveAddress())
);
NfTokenUri uri = NfTokenUri.ofPlainText("ipfs://bafybeigdyrzt5sfp7udm7hu76uh7y26nf4dfuylqabf3oclgtqy55fbzdi");
//Nft mint transaction
NfTokenMint nfTokenMint = NfTokenMint.builder()
.tokenTaxon(UnsignedLong.ONE)
- .account(wallet.publicKey().deriveAddress())
+ .account(keypair.publicKey().deriveAddress())
.fee(XrpCurrencyAmount.ofDrops(50))
- .signingPublicKey(wallet.publicKey())
+ .signingPublicKey(keypair.publicKey())
.sequence(accountInfoResult.accountData().sequence())
.uri(uri)
.build();
- SingleSignedTransaction signedMint = signatureService.sign(wallet.privateKey(), nfTokenMint);
+ SingleSignedTransaction signedMint = signatureService.sign(keypair.privateKey(), nfTokenMint);
SubmitResult mintSubmitResult = xrplClient.submit(signedMint);
assertThat(mintSubmitResult.engineResult()).isEqualTo(TransactionResultCodes.TES_SUCCESS);
assertThat(signedMint.hash()).isEqualTo(mintSubmitResult.transactionResult().hash());
- this.scanForResult(
- () -> {
- try {
- return xrplClient.accountNfts(wallet.publicKey().deriveAddress());
- } catch (JsonRpcClientErrorException e) {
- throw new RuntimeException(e);
- }
- },
- result -> result.accountNfts().stream()
- .anyMatch(nft -> nft.uri().get().equals(uri))
- );
+ scanForNfToken(keypair, uri);
+
logger.info("NFT was minted successfully.");
//create a sell offer for the NFT that was created above
- NfTokenId tokenId = xrplClient.accountNfts(wallet.publicKey().deriveAddress()).accountNfts().get(0).nfTokenId();
+ NfTokenId tokenId = xrplClient.accountNfts(keypair.publicKey().deriveAddress()).accountNfts().get(0).nfTokenId();
NfTokenCreateOffer nfTokenCreateOffer = NfTokenCreateOffer.builder()
- .account(wallet.publicKey().deriveAddress())
+ .account(keypair.publicKey().deriveAddress())
.nfTokenId(tokenId)
.fee(XrpCurrencyAmount.ofDrops(50))
.sequence(accountInfoResult.accountData().sequence().plus(UnsignedInteger.ONE))
.amount(XrpCurrencyAmount.ofDrops(1000))
.flags(NfTokenCreateOfferFlags.SELL_NFTOKEN)
- .signingPublicKey(wallet.publicKey())
+ .signingPublicKey(keypair.publicKey())
.build();
SingleSignedTransaction signedOffer = signatureService.sign(
- wallet.privateKey(),
+ keypair.privateKey(),
nfTokenCreateOffer
);
SubmitResult nfTokenCreateOfferSubmitResult = xrplClient.submit(signedOffer);
@@ -594,11 +558,11 @@ void mintAndCreateOfferThenCancelOffer() throws JsonRpcClientErrorException, Jso
logger.info("NFT Create Offer (Sell) transaction was validated successfully.");
this.scanForResult(
- () -> this.getValidatedAccountObjects(wallet.publicKey().deriveAddress()),
+ () -> this.getValidatedAccountObjects(keypair.publicKey().deriveAddress()),
objectsResult -> objectsResult.accountObjects().stream()
.anyMatch(object ->
NfTokenOfferObject.class.isAssignableFrom(object.getClass()) &&
- ((NfTokenOfferObject) object).owner().equals(wallet.publicKey().deriveAddress())
+ ((NfTokenOfferObject) object).owner().equals(keypair.publicKey().deriveAddress())
)
);
logger.info("NFTokenOffer object was found in account's objects.");
@@ -610,14 +574,14 @@ void mintAndCreateOfferThenCancelOffer() throws JsonRpcClientErrorException, Jso
// cancel the created offer
NfTokenCancelOffer nfTokenCancelOffer = NfTokenCancelOffer.builder()
.addTokenOffers(nftSellOffersResult.offers().get(0).nftOfferIndex())
- .account(wallet.publicKey().deriveAddress())
+ .account(keypair.publicKey().deriveAddress())
.fee(XrpCurrencyAmount.ofDrops(50))
.sequence(accountInfoResult.accountData().sequence().plus(UnsignedInteger.valueOf(2)))
- .signingPublicKey(wallet.publicKey())
+ .signingPublicKey(keypair.publicKey())
.build();
SingleSignedTransaction signedCancel = signatureService.sign(
- wallet.privateKey(),
+ keypair.privateKey(),
nfTokenCancelOffer
);
SubmitResult nfTokenCancelOfferSubmitResult = xrplClient.submit(signedCancel);
@@ -637,7 +601,7 @@ void mintAndCreateOfferThenCancelOffer() throws JsonRpcClientErrorException, Jso
.isEqualTo(Lists.newArrayList(tokenId));
this.scanForResult(
- () -> this.getValidatedAccountObjects(wallet.publicKey().deriveAddress()),
+ () -> this.getValidatedAccountObjects(keypair.publicKey().deriveAddress()),
objectsResult -> objectsResult.accountObjects().stream()
.noneMatch(object ->
NfTokenOfferObject.class.isAssignableFrom(object.getClass())
@@ -648,20 +612,20 @@ void mintAndCreateOfferThenCancelOffer() throws JsonRpcClientErrorException, Jso
@Test
void acceptOfferDirectModeWithBrokerFee() throws JsonRpcClientErrorException, JsonProcessingException {
- KeyPair wallet = createRandomAccountEd25519();
+ KeyPair keypair = createRandomAccountEd25519();
//mint NFT from one account
AccountInfoResult accountInfoResult = this.scanForResult(
- () -> this.getValidatedAccountInfo(wallet.publicKey().deriveAddress())
+ () -> this.getValidatedAccountInfo(keypair.publicKey().deriveAddress())
);
NfTokenUri uri = NfTokenUri.ofPlainText("ipfs://bafybeigdyrzt5sfp7udm7hu76uh7y26nf4dfuylqabf3oclgtqy55fbzdi");
//Nft mint transaction
NfTokenMint nfTokenMint = NfTokenMint.builder()
.tokenTaxon(UnsignedLong.ONE)
- .account(wallet.publicKey().deriveAddress())
+ .account(keypair.publicKey().deriveAddress())
.fee(XrpCurrencyAmount.ofDrops(50))
- .signingPublicKey(wallet.publicKey())
+ .signingPublicKey(keypair.publicKey())
.sequence(accountInfoResult.accountData().sequence())
.flags(NfTokenMintFlags.builder()
.tfTransferable(true)
@@ -669,41 +633,32 @@ void acceptOfferDirectModeWithBrokerFee() throws JsonRpcClientErrorException, Js
.uri(uri)
.build();
- SingleSignedTransaction signedMint = signatureService.sign(wallet.privateKey(), nfTokenMint);
+ SingleSignedTransaction signedMint = signatureService.sign(keypair.privateKey(), nfTokenMint);
SubmitResult mintSubmitResult = xrplClient.submit(signedMint);
assertThat(mintSubmitResult.engineResult()).isEqualTo(TransactionResultCodes.TES_SUCCESS);
assertThat(signedMint.hash()).isEqualTo(mintSubmitResult.transactionResult().hash());
- this.scanForResult(
- () -> {
- try {
- return xrplClient.accountNfts(wallet.publicKey().deriveAddress());
- } catch (JsonRpcClientErrorException e) {
- throw new RuntimeException(e);
- }
- },
- result -> result.accountNfts().stream()
- .anyMatch(nft -> nft.uri().get().equals(uri))
- );
+ scanForNfToken(keypair, uri);
+
logger.info("NFT was minted successfully.");
//create a sell offer for the NFT that was created above
- NfTokenId tokenId = xrplClient.accountNfts(wallet.publicKey().deriveAddress()).accountNfts().get(0).nfTokenId();
+ NfTokenId tokenId = xrplClient.accountNfts(keypair.publicKey().deriveAddress()).accountNfts().get(0).nfTokenId();
// the owner creates the sell offer
NfTokenCreateOffer nfTokenCreateSellOffer = NfTokenCreateOffer.builder()
- .account(wallet.publicKey().deriveAddress())
+ .account(keypair.publicKey().deriveAddress())
.nfTokenId(tokenId)
.fee(XrpCurrencyAmount.ofDrops(50))
.sequence(accountInfoResult.accountData().sequence().plus(UnsignedInteger.ONE))
.amount(XrpCurrencyAmount.ofDrops(1000))
- .signingPublicKey(wallet.publicKey())
+ .signingPublicKey(keypair.publicKey())
.flags(NfTokenCreateOfferFlags.SELL_NFTOKEN)
.build();
SingleSignedTransaction signedOffer = signatureService.sign(
- wallet.privateKey(),
+ keypair.privateKey(),
nfTokenCreateSellOffer
);
SubmitResult