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 c44d7db2c..9a48d01e1 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 @@ -34,6 +34,7 @@ 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.AmmClawback; import org.xrpl.xrpl4j.model.transactions.AmmCreate; import org.xrpl.xrpl4j.model.transactions.AmmDelete; import org.xrpl.xrpl4j.model.transactions.AmmDeposit; @@ -395,6 +396,10 @@ public SingleSignedTransaction addSignatureToTransact transactionWithSignature = OracleDelete.builder().from((OracleDelete) transaction) .transactionSignature(signature) .build(); + } else if (AmmClawback.class.isAssignableFrom(transaction.getClass())) { + transactionWithSignature = AmmClawback.builder().from((AmmClawback) transaction) + .transactionSignature(signature) + .build(); } else if (MpTokenAuthorize.class.isAssignableFrom(transaction.getClass())) { transactionWithSignature = MpTokenAuthorize.builder().from((MpTokenAuthorize) transaction) .transactionSignature(signature) @@ -622,6 +627,10 @@ public T addMultiSignaturesToTransaction(T transaction, transactionWithSignatures = OracleDelete.builder().from((OracleDelete) transaction) .signers(signers) .build(); + } else if (AmmClawback.class.isAssignableFrom(transaction.getClass())) { + transactionWithSignatures = AmmClawback.builder().from((AmmClawback) transaction) + .signers(signers) + .build(); } else if (MpTokenAuthorize.class.isAssignableFrom(transaction.getClass())) { transactionWithSignatures = MpTokenAuthorize.builder().from((MpTokenAuthorize) transaction) .signers(signers) diff --git a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/flags/AmmClawbackFlags.java b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/flags/AmmClawbackFlags.java new file mode 100644 index 000000000..818467273 --- /dev/null +++ b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/flags/AmmClawbackFlags.java @@ -0,0 +1,64 @@ +package org.xrpl.xrpl4j.model.flags; + +/*- + * ========================LICENSE_START================================= + * xrpl4j :: core + * %% + * Copyright (C) 2020 - 2023 XRPL Foundation and its contributors + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * =========================LICENSE_END================================== + */ + +import org.xrpl.xrpl4j.model.transactions.AmmClawback; + +/** + * {@link TransactionFlags} for {@link AmmClawback} transactions. + */ +public class AmmClawbackFlags extends TransactionFlags { + /** + * Constant {@link AmmDepositFlags} for the {@code tfClawTwoAssets} flag. + */ + public static final AmmClawbackFlags CLAW_TWO_ASSETS = new AmmClawbackFlags(0x00000001); + + /** + * Constant {@link AmmDepositFlags} for an unset value for "flags". + */ + public static final AmmClawbackFlags UNSET = new AmmClawbackFlags(0L); + + private AmmClawbackFlags(long value) { + super(value); + } + + private AmmClawbackFlags() { + } + + /** + * Construct an empty instance of {@link AmmClawbackFlags}. Transactions with empty flags will + * not be serialized with a {@code Flags} field. + * + * @return An empty {@link AmmClawbackFlags}. + */ + public static AmmClawbackFlags empty() { + return new AmmClawbackFlags(); + } + + /** + * Whether the {@code tfClawTwoAssets} flag is set. + * + * @return {@code true} if {@code tfLPToken} is set, otherwise {@code false}. + */ + public boolean tfClawTwoAssets() { + return this.isSet(CLAW_TWO_ASSETS); + } +} diff --git a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/AmmClawback.java b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/AmmClawback.java new file mode 100644 index 000000000..868c033d9 --- /dev/null +++ b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/AmmClawback.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.AmmClawbackFlags; +import org.xrpl.xrpl4j.model.ledger.Issue; + +import java.util.Optional; + +/** + * An {@link AmmClawback} transaction claws back tokens from a holder that has funds in an AMM pool. + */ +@Value.Immutable +@JsonSerialize(as = ImmutableAmmClawback.class) +@JsonDeserialize(as = ImmutableAmmClawback.class) +public interface AmmClawback extends Transaction { + + /** + * Construct a builder for this class. + * + * @return An {@link ImmutableAmmClawback.Builder}. + */ + static ImmutableAmmClawback.Builder builder() { + return ImmutableAmmClawback.builder(); + } + + /** + * The address of the holder that has funds deposited in the AMM pool. + * + * @return An {@link Address}. + */ + @JsonProperty("Holder") + Address holder(); + + /** + * The asset in the AMM pool that the issuer is looking to claw back. + * + * @return An {@link Issue}. + */ + @JsonProperty("Asset") + Issue asset(); + + /** + * Other asset in the AMM pool that the issuer is looking to claw back. + * + * @return An {@link Issue}. + */ + @JsonProperty("Asset2") + Issue asset2(); + + /** + * Optional field that specifies the maximum amount to clawback from the AMM pool. + * + * @return An {@link CurrencyAmount}. + */ + @JsonProperty("Amount") + Optional amount(); + + /** + * Transaction Flags for {@link AmmClawback}, with the only option being tfClawTwoAssets. + * + * @return {@link AmmClawbackFlags#UNSET} if field was not set, otherwise returns with the set flag. + */ + @JsonProperty("Flags") + @Value.Default + default AmmClawbackFlags flags() { + return AmmClawbackFlags.empty(); + } +} 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 a70c66bee..9c2779127 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 @@ -99,6 +99,7 @@ public interface Transaction { .put(ImmutableMpTokenIssuanceDestroy.class, TransactionType.MPT_ISSUANCE_DESTROY) .put(ImmutableMpTokenIssuanceSet.class, TransactionType.MPT_ISSUANCE_SET) .put(ImmutableUnknownTransaction.class, TransactionType.UNKNOWN) + .put(ImmutableAmmClawback.class, TransactionType.AMM_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 b3129bec5..a4cf0ba1f 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 @@ -338,6 +338,10 @@ public enum TransactionType { @Beta ORACLE_DELETE("OracleDelete"), + /** + * The {@link TransactionType} for the {@link AmmClawback} transaction. + */ + AMM_CLAWBACK("AMMClawback"), @Beta MPT_ISSUANCE_CREATE("MPTokenIssuanceCreate"), @Beta 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 50041b6c5..2719df344 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 @@ -63,6 +63,7 @@ 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.AmmClawback; import org.xrpl.xrpl4j.model.transactions.AmmCreate; import org.xrpl.xrpl4j.model.transactions.AmmDelete; import org.xrpl.xrpl4j.model.transactions.AmmDeposit; @@ -850,6 +851,59 @@ void addSignatureToAmmCreate() { addSignatureToTransactionHelper(ammCreate); } + @Test + void addSignatureToAmmClawback() { + AmmClawback ammClawback = AmmClawback.builder() + .account(sourcePublicKey.deriveAddress()) + .holder(sourcePublicKey.deriveAddress()) + .amount( + IssuedCurrencyAmount.builder() + .currency("TST") + .issuer(Address.of("rP9jPyP5kyvFRb6ZiRghAGw5u8SGAmU4bd")) + .value("25") + .build() + ) + .asset(Issue.XRP) + .asset2( + Issue.builder() + .issuer(Address.of("rP9jPyP5kyvFRb6ZiRghAGw5u8SGAmU4bd")) + .currency("TST") + .build() + ) + .fee(XrpCurrencyAmount.ofDrops(10)) + .sequence(UnsignedInteger.valueOf(6)) + .signingPublicKey(sourcePublicKey) + .build(); + + addSignatureToTransactionHelper(ammClawback); + } + + @Test + void addMultiSignaturesToAmmClawback() { + AmmClawback ammClawback = AmmClawback.builder() + .account(sourcePublicKey.deriveAddress()) + .holder(sourcePublicKey.deriveAddress()) + .amount( + IssuedCurrencyAmount.builder() + .currency("TST") + .issuer(Address.of("rP9jPyP5kyvFRb6ZiRghAGw5u8SGAmU4bd")) + .value("25") + .build() + ) + .asset(Issue.XRP) + .asset2( + Issue.builder() + .issuer(Address.of("rP9jPyP5kyvFRb6ZiRghAGw5u8SGAmU4bd")) + .currency("TST") + .build() + ) + .fee(XrpCurrencyAmount.ofDrops(10)) + .sequence(UnsignedInteger.valueOf(6)) + .build(); + + addMultiSignatureToTransactionHelper(ammClawback); + } + @Test void addSignatureToAmmDeposit() { AmmDeposit deposit = AmmDeposit.builder() diff --git a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/flags/AmmClawbackFlagsTest.java b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/flags/AmmClawbackFlagsTest.java new file mode 100644 index 000000000..79271f8c3 --- /dev/null +++ b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/flags/AmmClawbackFlagsTest.java @@ -0,0 +1,50 @@ +package org.xrpl.xrpl4j.model.flags; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.fasterxml.jackson.core.JsonProcessingException; +import org.json.JSONException; +import org.junit.jupiter.api.Test; + +class AmmClawbackFlagsTest extends AbstractFlagsTest { + + @Test + void testFlagWithValue() { + AmmClawbackFlags flags = AmmClawbackFlags.CLAW_TWO_ASSETS; + assertThat(flags.isEmpty()).isFalse(); + + assertThat(flags.tfClawTwoAssets()).isTrue(); + assertThat(flags.tfFullyCanonicalSig()).isFalse(); + assertThat(flags.getValue()).isEqualTo(1L); + } + + @Test + void testEmptyFlags() { + AmmClawbackFlags flags = AmmClawbackFlags.empty(); + assertThat(flags.isEmpty()).isTrue(); + + assertThat(flags.tfClawTwoAssets()).isFalse(); + assertThat(flags.tfFullyCanonicalSig()).isFalse(); + assertThat(flags.getValue()).isZero(); + } + + @Test + void testJson() throws JSONException, JsonProcessingException { + TransactionFlagsWrapper wrapper = TransactionFlagsWrapper.of(AmmClawbackFlags.CLAW_TWO_ASSETS); + String json = String.format("{\n" + + " \"flags\": %s\n" + + "}", AmmClawbackFlags.CLAW_TWO_ASSETS.getValue()); + + assertCanSerializeAndDeserialize(wrapper, json); + } + + @Test + void testEmptyJson() throws JSONException, JsonProcessingException { + AmmClawbackFlags flags = AmmClawbackFlags.empty(); + AbstractFlagsTest.TransactionFlagsWrapper wrapper = AbstractFlagsTest.TransactionFlagsWrapper.of(flags); + String json = "{\n" + + "}"; + + assertCanSerializeAndDeserialize(wrapper, json); + } +} diff --git a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/AmmClawbackTest.java b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/AmmClawbackTest.java new file mode 100644 index 000000000..c5306c509 --- /dev/null +++ b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/AmmClawbackTest.java @@ -0,0 +1,163 @@ +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.AmmClawbackFlags; +import org.xrpl.xrpl4j.model.ledger.Issue; + +/** + * Unit tests for {@link AmmClawback}. + **/ +public class AmmClawbackTest extends AbstractJsonTest { + + String usd = "USD"; + + @Test + void testJson() throws JSONException, JsonProcessingException { + AmmClawback ammClawback = AmmClawback.builder() + .account(Address.of("rJVUeRqDFNs2xqA7ncVE6ZoAhPUoaJJSQm")) + .amount( + IssuedCurrencyAmount.builder() + .currency(usd) + .issuer(Address.of("rP9jPyP5kyvFRb6ZiRghAGw5u8SGAmU4bd")) + .value("25") + .build() + ) + .asset(Issue.builder().currency(usd).issuer(Address.of("rP9jPyP5kyvFRb6ZiRghAGw5u8SGAmU4bd")).build()) + .asset2(Issue.builder().currency(usd).issuer(Address.of("rP9jPyP5kyvFRb6ZiRghAGw5u8SGAmU4bd")).build()) + .holder(Address.of("rJVUeRqDFNs2xqA7ncVE6ZoAhPUoaJJSQm")) + .fee(XrpCurrencyAmount.ofDrops(10)) + .sequence(UnsignedInteger.valueOf(6)) + .signingPublicKey( + PublicKey.fromBase16EncodedPublicKey("02356E89059A75438887F9FEE2056A2890DB82A68353BE9C0C0C8F89C0018B37FC") + ) + .build(); + + String json = + "{\n" + + " \"Account\" : \"rJVUeRqDFNs2xqA7ncVE6ZoAhPUoaJJSQm\",\n" + + " \"Amount\" : {\n" + + " \"currency\" : \"USD\",\n" + + " \"issuer\" : \"rP9jPyP5kyvFRb6ZiRghAGw5u8SGAmU4bd\",\n" + + " \"value\" : \"25\"\n" + + " },\n" + + " \"Asset\" : {\n" + + " \"currency\" : \"USD\",\n" + + " \"issuer\" : \"rP9jPyP5kyvFRb6ZiRghAGw5u8SGAmU4bd\"\n" + + " },\n" + + " \"Asset2\" : {\n" + + " \"currency\" : \"USD\",\n" + + " \"issuer\" : \"rP9jPyP5kyvFRb6ZiRghAGw5u8SGAmU4bd\"\n" + + " },\n" + + " \"Holder\" : \"rJVUeRqDFNs2xqA7ncVE6ZoAhPUoaJJSQm\",\n" + + " \"Fee\" : \"10\",\n" + + " \"Sequence\" : 6,\n" + + " \"SigningPubKey\" : \"02356E89059A75438887F9FEE2056A2890DB82A68353BE9C0C0C8F89C0018B37FC\",\n" + + " \"TransactionType\" : \"AMMClawback\"\n" + + "}"; + + assertCanSerializeAndDeserialize(ammClawback, json); + } + + @Test + void testJsonWithUnsetFlags() throws JSONException, JsonProcessingException { + AmmClawback ammClawback = AmmClawback.builder() + .account(Address.of("rJVUeRqDFNs2xqA7ncVE6ZoAhPUoaJJSQm")) + .amount( + IssuedCurrencyAmount.builder() + .currency(usd) + .issuer(Address.of("rP9jPyP5kyvFRb6ZiRghAGw5u8SGAmU4bd")) + .value("25") + .build() + ) + .holder(Address.of("rJVUeRqDFNs2xqA7ncVE6ZoAhPUoaJJSQm")) + .asset(Issue.builder().currency(usd).issuer(Address.of("rP9jPyP5kyvFRb6ZiRghAGw5u8SGAmU4bd")).build()) + .asset2(Issue.builder().currency(usd).issuer(Address.of("rP9jPyP5kyvFRb6ZiRghAGw5u8SGAmU4bd")).build()) + .fee(XrpCurrencyAmount.ofDrops(10)) + .sequence(UnsignedInteger.valueOf(6)) + .signingPublicKey( + PublicKey.fromBase16EncodedPublicKey("02356E89059A75438887F9FEE2056A2890DB82A68353BE9C0C0C8F89C0018B37FC") + ) + .flags(AmmClawbackFlags.UNSET) + .build(); + + String json = + "{\n" + + " \"Account\" : \"rJVUeRqDFNs2xqA7ncVE6ZoAhPUoaJJSQm\",\n" + + " \"Amount\" : {\n" + + " \"currency\" : \"USD\",\n" + + " \"issuer\" : \"rP9jPyP5kyvFRb6ZiRghAGw5u8SGAmU4bd\",\n" + + " \"value\" : \"25\"\n" + + " },\n" + + " \"Asset\" : {\n" + + " \"currency\" : \"USD\",\n" + + " \"issuer\" : \"rP9jPyP5kyvFRb6ZiRghAGw5u8SGAmU4bd\"\n" + + " },\n" + + " \"Asset2\" : {\n" + + " \"currency\" : \"USD\",\n" + + " \"issuer\" : \"rP9jPyP5kyvFRb6ZiRghAGw5u8SGAmU4bd\"\n" + + " },\n" + + " \"Holder\" : \"rJVUeRqDFNs2xqA7ncVE6ZoAhPUoaJJSQm\",\n" + + " \"Flags\" : 0,\n" + + " \"Fee\" : \"10\",\n" + + " \"Sequence\" : 6,\n" + + " \"SigningPubKey\" : \"02356E89059A75438887F9FEE2056A2890DB82A68353BE9C0C0C8F89C0018B37FC\",\n" + + " \"TransactionType\" : \"AMMClawback\"\n" + + "}"; + + assertCanSerializeAndDeserialize(ammClawback, json); + } + + @Test + void testJsonWithNonZeroFlags() throws JSONException, JsonProcessingException { + AmmClawback ammClawback = AmmClawback.builder() + .account(Address.of("rJVUeRqDFNs2xqA7ncVE6ZoAhPUoaJJSQm")) + .amount( + IssuedCurrencyAmount.builder() + .currency(usd) + .issuer(Address.of("rP9jPyP5kyvFRb6ZiRghAGw5u8SGAmU4bd")) + .value("25") + .build() + ) + .holder(Address.of("rJVUeRqDFNs2xqA7ncVE6ZoAhPUoaJJSQm")) + .asset(Issue.builder().currency(usd).issuer(Address.of("rP9jPyP5kyvFRb6ZiRghAGw5u8SGAmU4bd")).build()) + .asset2(Issue.builder().currency(usd).issuer(Address.of("rP9jPyP5kyvFRb6ZiRghAGw5u8SGAmU4bd")).build()) + .fee(XrpCurrencyAmount.ofDrops(10)) + .sequence(UnsignedInteger.valueOf(6)) + .signingPublicKey( + PublicKey.fromBase16EncodedPublicKey("02356E89059A75438887F9FEE2056A2890DB82A68353BE9C0C0C8F89C0018B37FC") + ) + .flags(AmmClawbackFlags.CLAW_TWO_ASSETS) + .build(); + + String json = + "{\n" + + " \"Account\" : \"rJVUeRqDFNs2xqA7ncVE6ZoAhPUoaJJSQm\",\n" + + " \"Amount\" : {\n" + + " \"currency\" : \"USD\",\n" + + " \"issuer\" : \"rP9jPyP5kyvFRb6ZiRghAGw5u8SGAmU4bd\",\n" + + " \"value\" : \"25\"\n" + + " },\n" + + " \"Asset\" : {\n" + + " \"currency\" : \"USD\",\n" + + " \"issuer\" : \"rP9jPyP5kyvFRb6ZiRghAGw5u8SGAmU4bd\"\n" + + " },\n" + + " \"Asset2\" : {\n" + + " \"currency\" : \"USD\",\n" + + " \"issuer\" : \"rP9jPyP5kyvFRb6ZiRghAGw5u8SGAmU4bd\"\n" + + " },\n" + + " \"Holder\" : \"rJVUeRqDFNs2xqA7ncVE6ZoAhPUoaJJSQm\",\n" + + " \"Flags\" : 1,\n" + + " \"Fee\" : \"10\",\n" + + " \"Sequence\" : 6,\n" + + " \"SigningPubKey\" : \"02356E89059A75438887F9FEE2056A2890DB82A68353BE9C0C0C8F89C0018B37FC\",\n" + + " \"TransactionType\" : \"AMMClawback\"\n" + + "}"; + + assertCanSerializeAndDeserialize(ammClawback, json); + } +} diff --git a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/TransactionTypeTests.java b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/TransactionTypeTests.java index ed5fd8377..3acc6476c 100644 --- a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/TransactionTypeTests.java +++ b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/TransactionTypeTests.java @@ -80,6 +80,7 @@ public void testTxTypeCapitalization() { assertThat(TransactionType.CLAWBACK.value()).isEqualTo("Clawback"); assertThat(TransactionType.AMM_BID.value()).isEqualTo("AMMBid"); assertThat(TransactionType.AMM_CREATE.value()).isEqualTo("AMMCreate"); + assertThat(TransactionType.AMM_CLAWBACK.value()).isEqualTo("AMMClawback"); assertThat(TransactionType.AMM_DEPOSIT.value()).isEqualTo("AMMDeposit"); assertThat(TransactionType.AMM_VOTE.value()).isEqualTo("AMMVote"); assertThat(TransactionType.AMM_WITHDRAW.value()).isEqualTo("AMMWithdraw"); 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 index 2c436e658..e3bd1a632 100644 --- 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 @@ -31,8 +31,10 @@ 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.AmmClawbackFlags; import org.xrpl.xrpl4j.model.flags.AmmDepositFlags; import org.xrpl.xrpl4j.model.flags.AmmWithdrawFlags; +import org.xrpl.xrpl4j.model.flags.TrustSetFlags; import org.xrpl.xrpl4j.model.ledger.AmmObject; import org.xrpl.xrpl4j.model.ledger.AuctionSlot; import org.xrpl.xrpl4j.model.ledger.AuthAccount; @@ -42,13 +44,16 @@ 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.AmmClawback; 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.Payment; import org.xrpl.xrpl4j.model.transactions.TradingFee; import org.xrpl.xrpl4j.model.transactions.TransactionResultCodes; +import org.xrpl.xrpl4j.model.transactions.TrustSet; import org.xrpl.xrpl4j.model.transactions.XrpCurrencyAmount; import java.math.BigDecimal; @@ -64,6 +69,7 @@ static boolean shouldNotRun() { } String xrpl4jCoin = Strings.padEnd(BaseEncoding.base16().encode("xrpl4jCoin".getBytes()), 40, '0'); + String usd = "USD"; @Test void depositAndVoteOnTradingFee() throws JsonRpcClientErrorException, JsonProcessingException { @@ -245,7 +251,9 @@ void depositAndWithdraw() throws JsonRpcClientErrorException, JsonProcessingExce .fee(FeeUtils.computeNetworkFees(feeResult).recommendedFee()) .sequence(traderAccountAfterDeposit.accountData().sequence()) .lastLedgerSequence( - traderAccountAfterDeposit.ledgerCurrentIndexSafe().plus(UnsignedInteger.valueOf(4000)).unsignedIntegerValue() + traderAccountAfterDeposit.ledgerCurrentIndexSafe() + .plus(UnsignedInteger.valueOf(4000)) + .unsignedIntegerValue() ) .signingPublicKey(traderKeyPair.publicKey()) .asset2( @@ -288,6 +296,565 @@ void depositAndWithdraw() throws JsonRpcClientErrorException, JsonProcessingExce ); } + @Test + void depositAndClawbackTwoAssets() throws JsonRpcClientErrorException, JsonProcessingException { + // create issuer and trader wallets + KeyPair issuerKeyPair = createRandomAccountEd25519(); + KeyPair traderKeyPair = createRandomAccountEd25519(); + FeeResult feeResult = xrplClient.fee(); + + AccountInfoResult issuerAccount = scanForResult( + () -> this.getValidatedAccountInfo(issuerKeyPair.publicKey().deriveAddress()) + ); + AccountInfoResult traderAccount = scanForResult( + () -> this.getValidatedAccountInfo(traderKeyPair.publicKey().deriveAddress()) + ); + + // enable rippling + enableFlag(issuerKeyPair, issuerAccount.accountData().sequence(), feeResult, AccountSetFlag.DEFAULT_RIPPLE); + + // enable Allow Trustline Clawback + enableFlag(issuerKeyPair, issuerAccount.accountData().sequence().plus(UnsignedInteger.ONE), + feeResult, AccountSetFlag.ALLOW_TRUSTLINE_CLAWBACK); + + // create Trustline from issuer to trader for currency USD and define two currencies + TrustSet trustSet = TrustSet.builder() + .account(traderKeyPair.publicKey().deriveAddress()) + .fee(FeeUtils.computeNetworkFees(feeResult).recommendedFee()) + .sequence(traderAccount.accountData().sequence()) + .signingPublicKey(traderKeyPair.publicKey()) + .limitAmount(IssuedCurrencyAmount.builder() + .currency(usd) + .issuer(issuerKeyPair.publicKey().deriveAddress()) + .value("1000") + .build() + ) + .flags(TrustSetFlags.builder().tfClearNoRipple().build()) + .build(); + + SingleSignedTransaction signedTrustline = + signatureService.sign(traderKeyPair.privateKey(), trustSet); + SubmitResult trustlineSubmitResult = xrplClient.submit(signedTrustline); + assertThat(trustlineSubmitResult.engineResult()).isEqualTo(TransactionResultCodes.TES_SUCCESS); + + // create Trustline from issuer to trader for xrpl4j coin + TrustSet trustSet2 = TrustSet.builder() + .account(traderKeyPair.publicKey().deriveAddress()) + .fee(FeeUtils.computeNetworkFees(feeResult).recommendedFee()) + .sequence(traderAccount.accountData().sequence().plus(UnsignedInteger.ONE)) + .signingPublicKey(traderKeyPair.publicKey()) + .limitAmount( + IssuedCurrencyAmount.builder() + .currency(xrpl4jCoin) + .issuer(issuerKeyPair.publicKey().deriveAddress()) + .value("1000000") + .build() + ) + .flags(TrustSetFlags.builder().tfClearNoRipple().build()) + .build(); + + SingleSignedTransaction signedTrustline2 = + signatureService.sign(traderKeyPair.privateKey(), trustSet2); + SubmitResult trustlineSubmitResult2 = xrplClient.submit(signedTrustline2); + assertThat(trustlineSubmitResult2.engineResult()).isEqualTo(TransactionResultCodes.TES_SUCCESS); + + // send payment of xrpl4jcoin to trader wallet from issuer + Payment paymentXrpl4jCoin = Payment.builder() + .account(issuerKeyPair.publicKey().deriveAddress()) + .destination(traderKeyPair.publicKey().deriveAddress()) + .amount(IssuedCurrencyAmount.builder() + .currency(xrpl4jCoin) + .issuer(issuerKeyPair.publicKey().deriveAddress()) + .value("500") + .build()) + .fee(FeeUtils.computeNetworkFees(feeResult).recommendedFee()) + .sequence(issuerAccount.accountData().sequence().plus(UnsignedInteger.valueOf(2))) + .signingPublicKey(issuerKeyPair.publicKey()) + .build(); + + SingleSignedTransaction signedPaymentXrpl4jCoin = + signatureService.sign(issuerKeyPair.privateKey(), paymentXrpl4jCoin); + SubmitResult paymentXrpl4jCoinSubmitResult = xrplClient.submit(signedPaymentXrpl4jCoin); + assertThat(paymentXrpl4jCoinSubmitResult.engineResult()).isEqualTo(TransactionResultCodes.TES_SUCCESS); + + // send payment of USD to trader wallet from issuer + Payment paymentUsd = Payment.builder() + .account(issuerKeyPair.publicKey().deriveAddress()) + .destination(traderKeyPair.publicKey().deriveAddress()) + .amount(IssuedCurrencyAmount.builder() + .currency(usd) + .issuer(issuerKeyPair.publicKey().deriveAddress()) + .value("1000") + .build()) + .fee(FeeUtils.computeNetworkFees(feeResult).recommendedFee()) + .sequence(issuerAccount.accountData().sequence().plus(UnsignedInteger.valueOf(3))) + .signingPublicKey(issuerKeyPair.publicKey()) + .build(); + + SingleSignedTransaction signedPaymentUsd = signatureService.sign(issuerKeyPair.privateKey(), paymentUsd); + SubmitResult paymentUsdSubmitResult = xrplClient.submit(signedPaymentUsd); + assertThat(paymentUsdSubmitResult.engineResult()).isEqualTo(TransactionResultCodes.TES_SUCCESS); + + // create AMM from issuer account + 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(traderKeyPair.publicKey().deriveAddress()) + .sequence(traderAccount.accountData().sequence().plus(UnsignedInteger.valueOf(2))) + .fee(reserveAmount) + .amount( + IssuedCurrencyAmount.builder() + .issuer(issuerKeyPair.publicKey().deriveAddress()) + .currency(xrpl4jCoin) + .value("100") + .build() + ) + .amount2( + IssuedCurrencyAmount.builder() + .issuer(issuerKeyPair.publicKey().deriveAddress()) + .currency(usd) + .value("250") + .build() + ) + .tradingFee(TradingFee.ofPercent(BigDecimal.ONE)) + .lastLedgerSequence(issuerAccount.ledgerIndexSafe().plus(UnsignedInteger.valueOf(100)).unsignedIntegerValue()) + .signingPublicKey(traderKeyPair.publicKey()) + .build(); + + SingleSignedTransaction signedCreate = signatureService.sign(traderKeyPair.privateKey(), ammCreate); + SubmitResult submitResult = xrplClient.submit(signedCreate); + assertThat(submitResult.engineResult()).isEqualTo(TransactionResultCodes.TES_SUCCESS); + + // create variables for issued currencies + Issue testCurrencyIssue = Issue.builder() + .issuer(issuerKeyPair.publicKey().deriveAddress()) + .currency(usd) + .build(); + Issue xrpl4jCoinIssue = Issue.builder() + .issuer(issuerKeyPair.publicKey().deriveAddress()) + .currency(xrpl4jCoin) + .build(); + + // deposit assets into AMM pool + AmmDeposit ammDeposit = AmmDeposit.builder() + .account(traderKeyPair.publicKey().deriveAddress()) + .fee(XrpCurrencyAmount.ofDrops(10)) + .sequence(traderAccount.accountData().sequence().plus(UnsignedInteger.valueOf(3))) + .asset(testCurrencyIssue) + .asset2(xrpl4jCoinIssue) + .amount( + IssuedCurrencyAmount.builder() + .issuer(issuerKeyPair.publicKey().deriveAddress()) + .currency(usd) + .value("10") + .build() + ) + .flags(AmmDepositFlags.SINGLE_ASSET) + .signingPublicKey(traderKeyPair.publicKey()) + .build(); + + SingleSignedTransaction signedAmmDeposit = + signatureService.sign(traderKeyPair.privateKey(), ammDeposit); + SubmitResult ammDepositResult = xrplClient.submit(signedAmmDeposit); + assertThat(ammDepositResult.engineResult()).isEqualTo(TransactionResultCodes.TES_SUCCESS); + + // create AMMClawback + AmmClawback ammClawback = AmmClawback.builder() + .account(issuerKeyPair.publicKey().deriveAddress()) + .holder(traderKeyPair.publicKey().deriveAddress()) + .fee(XrpCurrencyAmount.ofDrops(20)) + .sequence(issuerAccount.accountData().sequence().plus(UnsignedInteger.valueOf(4))) + .asset(testCurrencyIssue) + .asset2(xrpl4jCoinIssue) + .flags(AmmClawbackFlags.CLAW_TWO_ASSETS) + .signingPublicKey(issuerKeyPair.publicKey()) + .build(); + + SingleSignedTransaction signedAmmClawback = + signatureService.sign(issuerKeyPair.privateKey(), ammClawback); + SubmitResult ammClawbackResult = xrplClient.submit(signedAmmClawback); + assertThat(ammClawbackResult.engineResult()).isEqualTo(TransactionResultCodes.TES_SUCCESS); + } + + @Test + void depositAndClawbackWithAmount() throws JsonRpcClientErrorException, JsonProcessingException { + // create issuer and trader wallets + KeyPair issuerKeyPair = createRandomAccountEd25519(); + KeyPair traderKeyPair = createRandomAccountEd25519(); + FeeResult feeResult = xrplClient.fee(); + + AccountInfoResult issuerAccount = scanForResult( + () -> this.getValidatedAccountInfo(issuerKeyPair.publicKey().deriveAddress()) + ); + AccountInfoResult traderAccount = scanForResult( + () -> this.getValidatedAccountInfo(traderKeyPair.publicKey().deriveAddress()) + ); + + // enable rippling + enableFlag(issuerKeyPair, issuerAccount.accountData().sequence(), feeResult, AccountSetFlag.DEFAULT_RIPPLE); + + // enable Allow Trustline Clawback + enableFlag(issuerKeyPair, issuerAccount.accountData().sequence().plus(UnsignedInteger.ONE), + feeResult, AccountSetFlag.ALLOW_TRUSTLINE_CLAWBACK); + + // create Trustline from issuer to trader for currency USD and define two currencies + TrustSet trustSet = TrustSet.builder() + .account(traderKeyPair.publicKey().deriveAddress()) + .fee(FeeUtils.computeNetworkFees(feeResult).recommendedFee()) + .sequence(traderAccount.accountData().sequence()) + .signingPublicKey(traderKeyPair.publicKey()) + .limitAmount(IssuedCurrencyAmount.builder() + .currency(usd) + .issuer(issuerKeyPair.publicKey().deriveAddress()) + .value("1000") + .build() + ) + .flags(TrustSetFlags.builder().tfClearNoRipple().build()) + .build(); + + SingleSignedTransaction signedTrustline = + signatureService.sign(traderKeyPair.privateKey(), trustSet); + SubmitResult trustlineSubmitResult = xrplClient.submit(signedTrustline); + assertThat(trustlineSubmitResult.engineResult()).isEqualTo(TransactionResultCodes.TES_SUCCESS); + + // create Trustline from issuer to trader for xrpl4jcoin + TrustSet trustSet2 = TrustSet.builder() + .account(traderKeyPair.publicKey().deriveAddress()) + .fee(FeeUtils.computeNetworkFees(feeResult).recommendedFee()) + .sequence(traderAccount.accountData().sequence().plus(UnsignedInteger.ONE)) + .signingPublicKey(traderKeyPair.publicKey()) + .limitAmount( + IssuedCurrencyAmount.builder() + .currency(xrpl4jCoin) + .issuer(issuerKeyPair.publicKey().deriveAddress()) + .value("1000000") + .build() + ) + .flags(TrustSetFlags.builder().tfClearNoRipple().build()) + .build(); + + SingleSignedTransaction signedTrustline2 = + signatureService.sign(traderKeyPair.privateKey(), trustSet2); + SubmitResult trustlineSubmitResult2 = xrplClient.submit(signedTrustline2); + assertThat(trustlineSubmitResult2.engineResult()).isEqualTo(TransactionResultCodes.TES_SUCCESS); + + // send payment of xrpl4jcoin to trader wallet from issuer + Payment paymentXrpl4jCoin = Payment.builder() + .account(issuerKeyPair.publicKey().deriveAddress()) + .destination(traderKeyPair.publicKey().deriveAddress()) + .amount(IssuedCurrencyAmount.builder() + .currency(xrpl4jCoin) + .issuer(issuerKeyPair.publicKey().deriveAddress()) + .value("500") + .build()) + .fee(FeeUtils.computeNetworkFees(feeResult).recommendedFee()) + .sequence(issuerAccount.accountData().sequence().plus(UnsignedInteger.valueOf(2))) + .signingPublicKey(issuerKeyPair.publicKey()) + .build(); + + SingleSignedTransaction signedPaymentXrpl4jCoin = + signatureService.sign(issuerKeyPair.privateKey(), paymentXrpl4jCoin); + SubmitResult paymentXrpl4jCoinSubmitResult = xrplClient.submit(signedPaymentXrpl4jCoin); + assertThat(paymentXrpl4jCoinSubmitResult.engineResult()).isEqualTo(TransactionResultCodes.TES_SUCCESS); + + // send payment of USD to trader wallet from issuer + Payment paymentUsd = Payment.builder() + .account(issuerKeyPair.publicKey().deriveAddress()) + .destination(traderKeyPair.publicKey().deriveAddress()) + .amount(IssuedCurrencyAmount.builder() + .currency(usd) + .issuer(issuerKeyPair.publicKey().deriveAddress()) + .value("1000") + .build()) + .fee(FeeUtils.computeNetworkFees(feeResult).recommendedFee()) + .sequence(issuerAccount.accountData().sequence().plus(UnsignedInteger.valueOf(3))) + .signingPublicKey(issuerKeyPair.publicKey()) + .build(); + + SingleSignedTransaction signedPaymentUsd = signatureService.sign(issuerKeyPair.privateKey(), paymentUsd); + SubmitResult paymentUsdSubmitResult = xrplClient.submit(signedPaymentUsd); + assertThat(paymentUsdSubmitResult.engineResult()).isEqualTo(TransactionResultCodes.TES_SUCCESS); + + // create AMM from issuer account + 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(traderKeyPair.publicKey().deriveAddress()) + .sequence(traderAccount.accountData().sequence().plus(UnsignedInteger.valueOf(2))) + .fee(reserveAmount) + .amount( + IssuedCurrencyAmount.builder() + .issuer(issuerKeyPair.publicKey().deriveAddress()) + .currency(xrpl4jCoin) + .value("100") + .build() + ) + .amount2( + IssuedCurrencyAmount.builder() + .issuer(issuerKeyPair.publicKey().deriveAddress()) + .currency(usd) + .value("250") + .build() + ) + .tradingFee(TradingFee.ofPercent(BigDecimal.ONE)) + .lastLedgerSequence(issuerAccount.ledgerIndexSafe().plus(UnsignedInteger.valueOf(100)).unsignedIntegerValue()) + .signingPublicKey(traderKeyPair.publicKey()) + .build(); + + SingleSignedTransaction signedCreate = signatureService.sign(traderKeyPair.privateKey(), ammCreate); + SubmitResult submitResult = xrplClient.submit(signedCreate); + assertThat(submitResult.engineResult()).isEqualTo(TransactionResultCodes.TES_SUCCESS); + + // create variables for issued currencies + Issue testCurrencyIssue = Issue.builder() + .issuer(issuerKeyPair.publicKey().deriveAddress()) + .currency(usd) + .build(); + Issue xrpl4jCoinIssue = Issue.builder() + .issuer(issuerKeyPair.publicKey().deriveAddress()) + .currency(xrpl4jCoin) + .build(); + + // deposit assets into AMM pool + AmmDeposit ammDeposit = AmmDeposit.builder() + .account(traderKeyPair.publicKey().deriveAddress()) + .fee(XrpCurrencyAmount.ofDrops(10)) + .sequence(traderAccount.accountData().sequence().plus(UnsignedInteger.valueOf(3))) + .asset(testCurrencyIssue) + .asset2(xrpl4jCoinIssue) + .amount( + IssuedCurrencyAmount.builder() + .issuer(issuerKeyPair.publicKey().deriveAddress()) + .currency(usd) + .value("10") + .build() + ) + .flags(AmmDepositFlags.SINGLE_ASSET) + .signingPublicKey(traderKeyPair.publicKey()) + .build(); + + SingleSignedTransaction signedAmmDeposit = + signatureService.sign(traderKeyPair.privateKey(), ammDeposit); + SubmitResult ammDepositResult = xrplClient.submit(signedAmmDeposit); + assertThat(ammDepositResult.engineResult()).isEqualTo(TransactionResultCodes.TES_SUCCESS); + + // create AMMClawback + AmmClawback ammClawback = AmmClawback.builder() + .account(issuerKeyPair.publicKey().deriveAddress()) + .holder(traderKeyPair.publicKey().deriveAddress()) + .fee(XrpCurrencyAmount.ofDrops(20)) + .sequence(issuerAccount.accountData().sequence().plus(UnsignedInteger.valueOf(4))) + .asset(testCurrencyIssue) + .asset2(xrpl4jCoinIssue) + .amount( + IssuedCurrencyAmount.builder() + .issuer(issuerKeyPair.publicKey().deriveAddress()) + .currency(usd) + .value("5") + .build() + ) + .flags(AmmClawbackFlags.UNSET) + .signingPublicKey(issuerKeyPair.publicKey()) + .build(); + + SingleSignedTransaction signedAmmClawback = + signatureService.sign(issuerKeyPair.privateKey(), ammClawback); + SubmitResult ammClawbackResult = xrplClient.submit(signedAmmClawback); + assertThat(ammClawbackResult.engineResult()).isEqualTo(TransactionResultCodes.TES_SUCCESS); + } + + @Test + void depositAndClawbackSingleAsset() throws JsonRpcClientErrorException, JsonProcessingException { + // create issuer and trader wallets + KeyPair issuerKeyPair = createRandomAccountEd25519(); + KeyPair traderKeyPair = createRandomAccountEd25519(); + FeeResult feeResult = xrplClient.fee(); + + AccountInfoResult issuerAccount = scanForResult( + () -> this.getValidatedAccountInfo(issuerKeyPair.publicKey().deriveAddress()) + ); + AccountInfoResult traderAccount = scanForResult( + () -> this.getValidatedAccountInfo(traderKeyPair.publicKey().deriveAddress()) + ); + + // enable rippling + enableFlag(issuerKeyPair, issuerAccount.accountData().sequence(), feeResult, AccountSetFlag.DEFAULT_RIPPLE); + + // enable Allow Trustline Clawback + enableFlag(issuerKeyPair, issuerAccount.accountData().sequence().plus(UnsignedInteger.ONE), + feeResult, AccountSetFlag.ALLOW_TRUSTLINE_CLAWBACK); + + // create Trustline from issuer to trader for currency USD + TrustSet trustSet = TrustSet.builder() + .account(traderKeyPair.publicKey().deriveAddress()) + .fee(FeeUtils.computeNetworkFees(feeResult).recommendedFee()) + .sequence(traderAccount.accountData().sequence()) + .signingPublicKey(traderKeyPair.publicKey()) + .limitAmount(IssuedCurrencyAmount.builder() + .currency(usd) + .issuer(issuerKeyPair.publicKey().deriveAddress()) + .value("1000") + .build() + ) + .flags(TrustSetFlags.builder().tfClearNoRipple().build()) + .build(); + + SingleSignedTransaction signedTrustline = + signatureService.sign(traderKeyPair.privateKey(), trustSet); + SubmitResult trustlineSubmitResult = xrplClient.submit(signedTrustline); + assertThat(trustlineSubmitResult.engineResult()).isEqualTo(TransactionResultCodes.TES_SUCCESS); + + // create Trustline from issuer to trader for xrpl4jcoin + TrustSet trustSet2 = TrustSet.builder() + .account(traderKeyPair.publicKey().deriveAddress()) + .fee(FeeUtils.computeNetworkFees(feeResult).recommendedFee()) + .sequence(traderAccount.accountData().sequence().plus(UnsignedInteger.ONE)) + .signingPublicKey(traderKeyPair.publicKey()) + .limitAmount( + IssuedCurrencyAmount.builder() + .currency(xrpl4jCoin) + .issuer(issuerKeyPair.publicKey().deriveAddress()) + .value("1000000") + .build() + ) + .flags(TrustSetFlags.builder().tfClearNoRipple().build()) + .build(); + + SingleSignedTransaction signedTrustline2 = + signatureService.sign(traderKeyPair.privateKey(), trustSet2); + SubmitResult trustlineSubmitResult2 = xrplClient.submit(signedTrustline2); + assertThat(trustlineSubmitResult2.engineResult()).isEqualTo(TransactionResultCodes.TES_SUCCESS); + + // send payment of xrpl4jCoin to trader wallet from issuer + Payment paymentXrpl4jCoin = Payment.builder() + .account(issuerKeyPair.publicKey().deriveAddress()) + .destination(traderKeyPair.publicKey().deriveAddress()) + .amount(IssuedCurrencyAmount.builder() + .currency(xrpl4jCoin) + .issuer(issuerKeyPair.publicKey().deriveAddress()) + .value("500") + .build()) + .fee(FeeUtils.computeNetworkFees(feeResult).recommendedFee()) + .sequence(issuerAccount.accountData().sequence().plus(UnsignedInteger.valueOf(2))) + .signingPublicKey(issuerKeyPair.publicKey()) + .build(); + + SingleSignedTransaction signedPaymentXrpl4jCoin = + signatureService.sign(issuerKeyPair.privateKey(), paymentXrpl4jCoin); + SubmitResult paymentXrpl4jCoinSubmitResult = xrplClient.submit(signedPaymentXrpl4jCoin); + assertThat(paymentXrpl4jCoinSubmitResult.engineResult()).isEqualTo(TransactionResultCodes.TES_SUCCESS); + + // send payment of USD to trader wallet from issuer + Payment paymentUsd = Payment.builder() + .account(issuerKeyPair.publicKey().deriveAddress()) + .destination(traderKeyPair.publicKey().deriveAddress()) + .amount(IssuedCurrencyAmount.builder() + .currency(usd) + .issuer(issuerKeyPair.publicKey().deriveAddress()) + .value("1000") + .build()) + .fee(FeeUtils.computeNetworkFees(feeResult).recommendedFee()) + .sequence(issuerAccount.accountData().sequence().plus(UnsignedInteger.valueOf(3))) + .signingPublicKey(issuerKeyPair.publicKey()) + .build(); + + SingleSignedTransaction signedPaymentUsd = signatureService.sign(issuerKeyPair.privateKey(), paymentUsd); + SubmitResult paymentUsdSubmitResult = xrplClient.submit(signedPaymentUsd); + assertThat(paymentUsdSubmitResult.engineResult()).isEqualTo(TransactionResultCodes.TES_SUCCESS); + + // create AMM from issuer account + 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(traderKeyPair.publicKey().deriveAddress()) + .sequence(traderAccount.accountData().sequence().plus(UnsignedInteger.valueOf(2))) + .fee(reserveAmount) + .amount( + IssuedCurrencyAmount.builder() + .issuer(issuerKeyPair.publicKey().deriveAddress()) + .currency(xrpl4jCoin) + .value("100") + .build() + ) + .amount2( + IssuedCurrencyAmount.builder() + .issuer(issuerKeyPair.publicKey().deriveAddress()) + .currency(usd) + .value("250") + .build() + ) + .tradingFee(TradingFee.ofPercent(BigDecimal.ONE)) + .lastLedgerSequence(issuerAccount.ledgerIndexSafe().plus(UnsignedInteger.valueOf(100)).unsignedIntegerValue()) + .signingPublicKey(traderKeyPair.publicKey()) + .build(); + + SingleSignedTransaction signedCreate = signatureService.sign(traderKeyPair.privateKey(), ammCreate); + SubmitResult submitResult = xrplClient.submit(signedCreate); + assertThat(submitResult.engineResult()).isEqualTo(TransactionResultCodes.TES_SUCCESS); + + // create variables for issued currencies + Issue testCurrencyIssue = Issue.builder() + .issuer(issuerKeyPair.publicKey().deriveAddress()) + .currency(usd) + .build(); + Issue xrpl4jCoinIssue = Issue.builder() + .issuer(issuerKeyPair.publicKey().deriveAddress()) + .currency(xrpl4jCoin) + .build(); + + // deposit assets into AMM pool + AmmDeposit ammDeposit = AmmDeposit.builder() + .account(traderKeyPair.publicKey().deriveAddress()) + .fee(XrpCurrencyAmount.ofDrops(10)) + .sequence(traderAccount.accountData().sequence().plus(UnsignedInteger.valueOf(3))) + .asset(testCurrencyIssue) + .asset2(xrpl4jCoinIssue) + .amount( + IssuedCurrencyAmount.builder() + .issuer(issuerKeyPair.publicKey().deriveAddress()) + .currency(usd) + .value("10") + .build() + ) + .flags(AmmDepositFlags.SINGLE_ASSET) + .signingPublicKey(traderKeyPair.publicKey()) + .build(); + + SingleSignedTransaction signedAmmDeposit = + signatureService.sign(traderKeyPair.privateKey(), ammDeposit); + SubmitResult ammDepositResult = xrplClient.submit(signedAmmDeposit); + assertThat(ammDepositResult.engineResult()).isEqualTo(TransactionResultCodes.TES_SUCCESS); + + // create AMMClawback + AmmClawback ammClawback = AmmClawback.builder() + .account(issuerKeyPair.publicKey().deriveAddress()) + .holder(traderKeyPair.publicKey().deriveAddress()) + .fee(XrpCurrencyAmount.ofDrops(20)) + .sequence(issuerAccount.accountData().sequence().plus(UnsignedInteger.valueOf(4))) + .asset(testCurrencyIssue) + .asset2(xrpl4jCoinIssue) + .signingPublicKey(issuerKeyPair.publicKey()) + .flags(AmmClawbackFlags.UNSET) + .build(); + + SingleSignedTransaction signedAmmClawback = + signatureService.sign(issuerKeyPair.privateKey(), ammClawback); + SubmitResult ammClawbackResult = xrplClient.submit(signedAmmClawback); + assertThat(ammClawbackResult.engineResult()).isEqualTo(TransactionResultCodes.TES_SUCCESS); + } + private AccountInfoResult depositXrp( KeyPair issuerKeyPair, KeyPair traderKeyPair, @@ -357,7 +924,7 @@ private AmmInfoResult createAmm( () -> this.getValidatedAccountInfo(issuerKeyPair.publicKey().deriveAddress()) ); - enableRippling(issuerKeyPair, issuerAccount, feeResult); + enableFlag(issuerKeyPair, issuerAccount.accountData().sequence(), feeResult, AccountSetFlag.DEFAULT_RIPPLE); XrpCurrencyAmount reserveAmount = xrplClient.serverInformation().info() .map( @@ -478,14 +1045,15 @@ private AmmInfoResult getAmmInfo(KeyPair issuerKeyPair) throws JsonRpcClientErro return ammInfoResult; } - private void enableRippling(KeyPair issuerKeyPair, AccountInfoResult issuerAccount, FeeResult feeResult) + private void enableFlag(KeyPair issuerKeyPair, UnsignedInteger sequence, FeeResult feeResult, + AccountSetFlag accountSetFlag) 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) + .sequence(sequence) + .setFlag(accountSetFlag) .build(); SingleSignedTransaction signed = signatureService.sign(issuerKeyPair.privateKey(), accountSet); diff --git a/xrpl4j-integration-tests/src/test/resources/rippled/rippled.cfg b/xrpl4j-integration-tests/src/test/resources/rippled/rippled.cfg index 21eac6b2d..f4bb59030 100644 --- a/xrpl4j-integration-tests/src/test/resources/rippled/rippled.cfg +++ b/xrpl4j-integration-tests/src/test/resources/rippled/rippled.cfg @@ -211,6 +211,8 @@ fixNFTokenReserve fixInnerObjTemplate # 2.1.1 Amendments fixAMMOverflowOffer +# 2.3.0 Amendments +AMMClawback # TBD Version fixPreviousTxnID fixAMMv1_1