From 73d78480386f08c89d22d150f5c3bea613a91306 Mon Sep 17 00:00:00 2001 From: nkramer44 Date: Fri, 1 Dec 2023 13:07:12 -0500 Subject: [PATCH 1/4] XChain support (#497) * Add XChainAccountCreateCommit transaction, as well as new XChainBridge serialized type * Add XChainAddAccountCreateAttestation * Add XChainAddClaimAttestation * Fix bug in binary codec's UInt64Type where it expected JSON values to be base 10, not base 16. Also serialize XChainClaimIds as hex in JSON, and add a new XChainCount type that also serializes to hex in JSON * add XChainClaim * add XChainClaim * add XChainCommit * Add XChainCreateBridge * Add XChainCreateClaimId * add XChainModifyBridge * Add Bridge object * Add XChainOwnedCreateAccountClaimIdObject * rename XChainCreateAccountProofSigWrapper to XChainCreateAccountAttestation * add XChainOwnedClaimIdObject * fix checkstyle and javadoc * migrate to rippleci/rippled from xrpllabsofficial/xrpld. Add new transactions to SignatureUtils switches * write ITs for xchain stuff. Add attestations that can be signed. Add ability to sign attestations to signature services. * fix checkstyle * fix tests * add bridge ledger entry lookup * PR feedback * uncomment test --- .../xrpl4j/codec/binary/types/AmountType.java | 11 +- .../codec/binary/types/SerializedType.java | 1 + .../xrpl4j/codec/binary/types/UInt64Type.java | 8 +- .../codec/binary/types/XChainBridge.java | 63 + .../codec/binary/types/XChainBridgeType.java | 104 ++ .../signing/AbstractSignatureService.java | 6 + .../signing/AbstractTransactionSigner.java | 10 + .../xrpl4j/crypto/signing/SignatureUtils.java | 118 +- .../crypto/signing/TransactionSigner.java | 16 + .../bc/BcDerivedKeySignatureService.java | 11 + .../ledger/LedgerEntryRequestParams.java | 44 + .../model/flags/XChainModifyBridgeFlags.java | 97 ++ .../modules/AffectedNodeDeserializer.java | 51 +- .../modules/XChainClaimIdDeserializer.java | 31 + .../modules/XChainClaimIdSerializer.java | 28 + .../modules/XChainCountDeserializer.java | 31 + .../modules/XChainCountSerializer.java | 30 + .../xrpl/xrpl4j/model/ledger/Attestation.java | 65 + .../xrpl4j/model/ledger/AttestationClaim.java | 51 + .../ledger/AttestationCreateAccount.java | 60 + .../xrpl4j/model/ledger/BridgeObject.java | 169 +++ .../xrpl4j/model/ledger/LedgerObject.java | 41 +- .../model/ledger/XChainClaimAttestation.java | 43 + .../model/ledger/XChainClaimProofSig.java | 86 ++ .../XChainCreateAccountAttestation.java | 44 + .../ledger/XChainCreateAccountProofSig.java | 95 ++ .../ledger/XChainOwnedClaimIdObject.java | 165 +++ ...XChainOwnedCreateAccountClaimIdObject.java | 142 ++ .../model/transactions/Transaction.java | 8 + .../model/transactions/TransactionType.java | 80 +- .../xrpl4j/model/transactions/Wrappers.java | 46 + .../XChainAccountCreateCommit.java | 81 + .../XChainAddAccountCreateAttestation.java | 145 ++ .../XChainAddClaimAttestation.java | 138 ++ .../model/transactions/XChainBridge.java | 65 + .../model/transactions/XChainClaim.java | 93 ++ .../model/transactions/XChainCommit.java | 86 ++ .../transactions/XChainCreateBridge.java | 72 + .../transactions/XChainCreateClaimId.java | 70 + .../transactions/XChainModifyBridge.java | 76 + .../metadata/MetaBridgeObject.java | 145 ++ .../metadata/MetaLedgerEntryType.java | 59 + .../metadata/MetaXChainClaimAttestation.java | 31 + .../metadata/MetaXChainClaimProofSig.java | 79 + .../MetaXChainCreateAccountAttestation.java | 30 + .../MetaXChainCreateAccountProofSig.java | 90 ++ .../MetaXChainOwnedClaimIdObject.java | 142 ++ ...XChainOwnedCreateAccountClaimIdObject.java | 117 ++ .../binary/BinarySerializationTests.java | 353 ++++- .../binary/types/UInt64TypeUnitTest.java | 10 +- .../signing/AbstractSignatureServiceTest.java | 29 + .../AbstractTransactionSignerTest.java | 43 + .../crypto/signing/SignatureUtilsTest.java | 478 ++++++ .../bc/BcDerivedKeySignatureServiceTest.java | 201 +++ .../ledger/LedgerEntryRequestParamsTest.java | 66 + .../flags/XChainModifyBridgeFlagsTest.java | 89 ++ .../xrpl4j/model/ledger/BridgeObjectTest.java | 119 ++ .../ledger/XChainOwnedClaimIdObjectTest.java | 122 ++ ...inOwnedCreateAccountClaimIdObjectTest.java | 123 ++ .../transactions/TransactionMetadataTest.java | 39 +- .../transactions/TransactionTypeTests.java | 16 + .../XChainAccountCreateCommitTest.java | 216 +++ ...XChainAddAccountCreateAttestationTest.java | 233 +++ .../XChainAddClaimAttestationTest.java | 239 +++ .../model/transactions/XChainClaimIdTest.java | 76 + .../model/transactions/XChainClaimTest.java | 155 ++ .../model/transactions/XChainCommitTest.java | 159 ++ .../model/transactions/XChainCountTest.java | 73 + .../transactions/XChainCreateBridgeTest.java | 123 ++ .../transactions/XChainCreateClaimIdTest.java | 123 ++ .../transactions/XChainModifyBridgeTest.java | 146 ++ .../metadata/MetaLedgerEntryTypeTest.java | 32 + .../src/test/resources/codec-fixtures.json | 545 +++++-- xrpl4j-integration-tests/pom.xml | 10 + .../org/xrpl/xrpl4j/tests/AbstractIT.java | 31 + .../java/org/xrpl/xrpl4j/tests/XChainIT.java | 1040 +++++++++++++ .../tests/environment/RippledContainer.java | 2 +- .../src/test/resources/rippled/rippled.cfg | 1298 ++--------------- 78 files changed, 8073 insertions(+), 1390 deletions(-) create mode 100644 xrpl4j-core/src/main/java/org/xrpl/xrpl4j/codec/binary/types/XChainBridge.java create mode 100644 xrpl4j-core/src/main/java/org/xrpl/xrpl4j/codec/binary/types/XChainBridgeType.java create mode 100644 xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/flags/XChainModifyBridgeFlags.java create mode 100644 xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/jackson/modules/XChainClaimIdDeserializer.java create mode 100644 xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/jackson/modules/XChainClaimIdSerializer.java create mode 100644 xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/jackson/modules/XChainCountDeserializer.java create mode 100644 xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/jackson/modules/XChainCountSerializer.java create mode 100644 xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/ledger/Attestation.java create mode 100644 xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/ledger/AttestationClaim.java create mode 100644 xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/ledger/AttestationCreateAccount.java create mode 100644 xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/ledger/BridgeObject.java create mode 100644 xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/ledger/XChainClaimAttestation.java create mode 100644 xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/ledger/XChainClaimProofSig.java create mode 100644 xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/ledger/XChainCreateAccountAttestation.java create mode 100644 xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/ledger/XChainCreateAccountProofSig.java create mode 100644 xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/ledger/XChainOwnedClaimIdObject.java create mode 100644 xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/ledger/XChainOwnedCreateAccountClaimIdObject.java create mode 100644 xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/XChainAccountCreateCommit.java create mode 100644 xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/XChainAddAccountCreateAttestation.java create mode 100644 xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/XChainAddClaimAttestation.java create mode 100644 xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/XChainBridge.java create mode 100644 xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/XChainClaim.java create mode 100644 xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/XChainCommit.java create mode 100644 xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/XChainCreateBridge.java create mode 100644 xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/XChainCreateClaimId.java create mode 100644 xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/XChainModifyBridge.java create mode 100644 xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/metadata/MetaBridgeObject.java create mode 100644 xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/metadata/MetaXChainClaimAttestation.java create mode 100644 xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/metadata/MetaXChainClaimProofSig.java create mode 100644 xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/metadata/MetaXChainCreateAccountAttestation.java create mode 100644 xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/metadata/MetaXChainCreateAccountProofSig.java create mode 100644 xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/metadata/MetaXChainOwnedClaimIdObject.java create mode 100644 xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/metadata/MetaXChainOwnedCreateAccountClaimIdObject.java create mode 100644 xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/flags/XChainModifyBridgeFlagsTest.java create mode 100644 xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/ledger/BridgeObjectTest.java create mode 100644 xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/ledger/XChainOwnedClaimIdObjectTest.java create mode 100644 xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/ledger/XChainOwnedCreateAccountClaimIdObjectTest.java create mode 100644 xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/XChainAccountCreateCommitTest.java create mode 100644 xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/XChainAddAccountCreateAttestationTest.java create mode 100644 xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/XChainAddClaimAttestationTest.java create mode 100644 xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/XChainClaimIdTest.java create mode 100644 xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/XChainClaimTest.java create mode 100644 xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/XChainCommitTest.java create mode 100644 xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/XChainCountTest.java create mode 100644 xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/XChainCreateBridgeTest.java create mode 100644 xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/XChainCreateClaimIdTest.java create mode 100644 xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/XChainModifyBridgeTest.java create mode 100644 xrpl4j-integration-tests/src/test/java/org/xrpl/xrpl4j/tests/XChainIT.java diff --git a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/codec/binary/types/AmountType.java b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/codec/binary/types/AmountType.java index bb2304dae..e2b66c71f 100644 --- a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/codec/binary/types/AmountType.java +++ b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/codec/binary/types/AmountType.java @@ -24,6 +24,7 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.TextNode; +import com.google.common.primitives.UnsignedLong; import org.xrpl.xrpl4j.codec.addresses.ByteUtils; import org.xrpl.xrpl4j.codec.addresses.UnsignedByte; import org.xrpl.xrpl4j.codec.addresses.UnsignedByteArray; @@ -140,8 +141,14 @@ public AmountType fromParser(BinaryParser parser) { public AmountType fromJson(JsonNode value) throws JsonProcessingException { if (value.isValueNode()) { assertXrpIsValid(value.asText()); - UInt64Type number = new UInt64Type().fromJson(value.asText()); - byte[] rawBytes = number.toBytes(); + + UnsignedByteArray number = UnsignedByteArray.fromHex( + ByteUtils.padded( + UnsignedLong.valueOf(value.asText()).toString(16), + 64 / 4 + ) + ); + byte[] rawBytes = number.toByteArray(); rawBytes[0] |= 0x40; return new AmountType(UnsignedByteArray.of(rawBytes)); } diff --git a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/codec/binary/types/SerializedType.java b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/codec/binary/types/SerializedType.java index cc67a3ff9..42ad767d9 100644 --- a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/codec/binary/types/SerializedType.java +++ b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/codec/binary/types/SerializedType.java @@ -58,6 +58,7 @@ public abstract class SerializedType> { .put("UInt64", () -> new UInt64Type()) .put("Vector256", () -> new Vector256Type()) .put("Issue", () -> new IssueType()) + .put("XChainBridge", () -> new XChainBridgeType()) .build(); private final UnsignedByteArray bytes; diff --git a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/codec/binary/types/UInt64Type.java b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/codec/binary/types/UInt64Type.java index 08b407da7..a08dfc05f 100644 --- a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/codec/binary/types/UInt64Type.java +++ b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/codec/binary/types/UInt64Type.java @@ -21,6 +21,7 @@ */ import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.TextNode; import com.google.common.primitives.UnsignedLong; import org.xrpl.xrpl4j.codec.binary.serdes.BinaryParser; @@ -44,7 +45,12 @@ public UInt64Type fromParser(BinaryParser parser) { @Override public UInt64Type fromJson(JsonNode value) { - return new UInt64Type(UnsignedLong.valueOf(value.asText())); + // STUInt64s are represented as hex-encoded Strings in JSON. + return new UInt64Type(UnsignedLong.valueOf(value.asText(), 16)); } + @Override + public JsonNode toJson() { + return new TextNode(UnsignedLong.valueOf(toHex(), 16).toString(16)); + } } diff --git a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/codec/binary/types/XChainBridge.java b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/codec/binary/types/XChainBridge.java new file mode 100644 index 000000000..65e917fc9 --- /dev/null +++ b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/codec/binary/types/XChainBridge.java @@ -0,0 +1,63 @@ +package org.xrpl.xrpl4j.codec.binary.types; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.JsonNode; +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.Immutable; + +/** + * JSON mapping object for the XChainBridge serialized type. + */ +@Beta +@Immutable +@JsonSerialize(as = ImmutableXChainBridge.class) +@JsonDeserialize(as = ImmutableXChainBridge.class) +public interface XChainBridge { + + /** + * Construct a {@code XChainBridge} builder. + * + * @return An {@link ImmutableXChainBridge.Builder}. + */ + static ImmutableXChainBridge.Builder builder() { + return ImmutableXChainBridge.builder(); + } + + /** + * The door account on the issuing chain. For an XRP-XRP bridge, this must be the genesis account (the account that is + * created when the network is first started, which contains all of the XRP). + * + * @return The address of the door account as a {@link JsonNode}. + */ + @JsonProperty("IssuingChainDoor") + JsonNode issuingChainDoor(); + + /** + * The asset that is minted and burned on the issuing chain. For an IOU-IOU bridge, the issuer of the asset must be + * the door account on the issuing chain, to avoid supply issues. + * + * @return An {@link Issue}. + */ + @JsonProperty("IssuingChainIssue") + JsonNode issuingChainIssue(); + + /** + * The door account on the locking chain. + * + * @return The address of the door account as a {@link JsonNode}. + */ + @JsonProperty("LockingChainDoor") + JsonNode lockingChainDoor(); + + /** + * The asset that is locked and unlocked on the locking chain. + * + * @return An {@link Issue}. + */ + @JsonProperty("LockingChainIssue") + JsonNode lockingChainIssue(); + + +} diff --git a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/codec/binary/types/XChainBridgeType.java b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/codec/binary/types/XChainBridgeType.java new file mode 100644 index 000000000..8faf04afb --- /dev/null +++ b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/codec/binary/types/XChainBridgeType.java @@ -0,0 +1,104 @@ +package org.xrpl.xrpl4j.codec.binary.types; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.xrpl.xrpl4j.codec.addresses.UnsignedByte; +import org.xrpl.xrpl4j.codec.addresses.UnsignedByteArray; +import org.xrpl.xrpl4j.codec.binary.BinaryCodecObjectMapperFactory; +import org.xrpl.xrpl4j.codec.binary.serdes.BinaryParser; + +public class XChainBridgeType extends SerializedType { + + private static final ObjectMapper objectMapper = BinaryCodecObjectMapperFactory.getObjectMapper(); + + /** + * XChainBridge typed fields are serialized by serializing the LockingChainDoor, LockingChainIssue, IssuingChainDoor + * IssuingChainIssue fields in order without type or field codes. LockingChainDoor and IssuingChainDoor are an + * {@link AccountIdType}, and LockingChainIssue and IssuingChainIssue are {@link IssueType}s. + * + *

The "empty" or "zero" + * XChainBridge type has LockingChainDoor and IssuingChainDoor = ACCOUNT_ZERO, and LockingChainIssue and + * IssuingChainIssue = XRP. AccountIDs are serialized with a length prefix, which is always {@code 0x14}. The XRP + * issue is serialized as 160 zero bits. Therefore, the "zero" XChainBridge is {@code 0x14} followed by 20 zero bytes + * for the LockingChainDoor, followed by 20 zero bytes for LockingChainIssue, followed by {@code 0x14} and 20 zero + * bytes for the IssuingChainDoor and 20 zero bytes for IssuingChainIssue.

+ */ + private static final byte[] ZERO_XCHAIN_BRIDGE = new byte[82]; + + static { + ZERO_XCHAIN_BRIDGE[0] = 0x14; + ZERO_XCHAIN_BRIDGE[41] = 0x14; + } + + public XChainBridgeType() { + this(UnsignedByteArray.of(ZERO_XCHAIN_BRIDGE)); + } + + public XChainBridgeType(UnsignedByteArray bytes) { + super(bytes); + } + + + @Override + public XChainBridgeType fromJson(JsonNode node) throws JsonProcessingException { + if (!node.isObject()) { + throw new IllegalArgumentException("node is not an object"); + } + XChainBridge bridge = objectMapper.treeToValue(node, XChainBridge.class); + + // AccountIDs have a VL prefix that is always 20 (except for ACCOUNT_ZERO). Usually this length prefix is + // added in BinarySerializer.writeFieldAndValue because STAccounts are VL encoded according to definitions.json. + // However, because STXChainBridges are not VL encoded, we need to manually add the length prefix to the two + // STAccount types here. + UnsignedByteArray byteArray = UnsignedByteArray.of(UnsignedByte.of(20)); + byteArray.append(new AccountIdType().fromJson(bridge.lockingChainDoor()).value()); + byteArray.append(new IssueType().fromJson(bridge.lockingChainIssue()).value()); + + // Need to add length prefix for issuing chain door account. + byteArray.append(UnsignedByte.of(20)); + byteArray.append(new AccountIdType().fromJson(bridge.issuingChainDoor()).value()); + byteArray.append(new IssueType().fromJson(bridge.issuingChainIssue()).value()); + + return new XChainBridgeType(byteArray); + } + + @Override + public XChainBridgeType fromParser(BinaryParser parser) { + parser.skip(1); + AccountIdType lockingChainDoor = new AccountIdType().fromParser(parser); + IssueType lockingChainIssue = new IssueType().fromParser(parser); + parser.skip(1); + AccountIdType issuingChainDoor = new AccountIdType().fromParser(parser); + IssueType issuingChainIssue = new IssueType().fromParser(parser); + + return new XChainBridgeType( + UnsignedByteArray.of(UnsignedByte.of(20)) + .append(lockingChainDoor.value()) + .append(lockingChainIssue.value()) + .append(UnsignedByte.of(20)) + .append(issuingChainDoor.value()) + .append(issuingChainIssue.value()) + ); + } + + @Override + public JsonNode toJson() { + BinaryParser parser = new BinaryParser(this.toHex()); + parser.skip(1); + AccountIdType lockingChainDoor = new AccountIdType().fromParser(parser); + IssueType lockingChainIssue = new IssueType().fromParser(parser); + parser.skip(1); + AccountIdType issuingChainDoor = new AccountIdType().fromParser(parser); + IssueType issuingChainIssue = new IssueType().fromParser(parser); + + XChainBridge bridge = XChainBridge.builder() + .lockingChainDoor(lockingChainDoor.toJson()) + .lockingChainIssue(lockingChainIssue.toJson()) + .issuingChainDoor(issuingChainDoor.toJson()) + .issuingChainIssue(issuingChainIssue.toJson()) + .build(); + + return objectMapper.valueToTree(bridge); + } +} diff --git a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/crypto/signing/AbstractSignatureService.java b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/crypto/signing/AbstractSignatureService.java index dd76487ac..2e707e3e1 100644 --- a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/crypto/signing/AbstractSignatureService.java +++ b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/crypto/signing/AbstractSignatureService.java @@ -26,6 +26,7 @@ import org.xrpl.xrpl4j.crypto.keys.PrivateKeyable; import org.xrpl.xrpl4j.crypto.keys.PublicKey; import org.xrpl.xrpl4j.model.client.channels.UnsignedClaim; +import org.xrpl.xrpl4j.model.ledger.Attestation; import org.xrpl.xrpl4j.model.transactions.Signer; import org.xrpl.xrpl4j.model.transactions.Transaction; @@ -101,6 +102,11 @@ public Signature sign(final P privateKeyable, final UnsignedClaim unsignedClaim) return this.abstractTransactionSigner.sign(privateKeyable, unsignedClaim); } + @Override + public Signature sign(P privateKeyable, Attestation attestation) { + return this.abstractTransactionSigner.sign(privateKeyable, attestation); + } + @Override public Signature multiSign(final P privateKeyable, final T transaction) { return abstractTransactionSigner.multiSign(privateKeyable, transaction); 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 f30cdbe49..d2aa00961 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 @@ -25,6 +25,7 @@ import org.xrpl.xrpl4j.crypto.keys.PrivateKeyable; import org.xrpl.xrpl4j.crypto.keys.PublicKey; import org.xrpl.xrpl4j.model.client.channels.UnsignedClaim; +import org.xrpl.xrpl4j.model.ledger.Attestation; import org.xrpl.xrpl4j.model.transactions.Address; import org.xrpl.xrpl4j.model.transactions.Transaction; @@ -67,6 +68,15 @@ public Signature sign(final P privateKeyable, final UnsignedClaim unsignedClaim) return this.signingHelper(privateKeyable, signableBytes); } + @Override + public Signature sign(P privateKeyable, Attestation attestation) { + Objects.requireNonNull(privateKeyable); + Objects.requireNonNull(attestation); + + final UnsignedByteArray signableBytes = this.signatureUtils.toSignableBytes(attestation); + return this.signingHelper(privateKeyable, signableBytes); + } + @Override public Signature multiSign(final P privateKeyable, final T transaction) { Objects.requireNonNull(privateKeyable); 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 7f8622555..9d4fb28c2 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 @@ -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. @@ -22,12 +22,14 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.common.annotations.Beta; import com.google.common.base.Preconditions; import org.xrpl.xrpl4j.codec.addresses.UnsignedByteArray; 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.jackson.ObjectMapperFactory; +import org.xrpl.xrpl4j.model.ledger.Attestation; import org.xrpl.xrpl4j.model.transactions.AccountDelete; import org.xrpl.xrpl4j.model.transactions.AccountSet; import org.xrpl.xrpl4j.model.transactions.Address; @@ -62,6 +64,14 @@ import org.xrpl.xrpl4j.model.transactions.TicketCreate; import org.xrpl.xrpl4j.model.transactions.Transaction; import org.xrpl.xrpl4j.model.transactions.TrustSet; +import org.xrpl.xrpl4j.model.transactions.XChainAccountCreateCommit; +import org.xrpl.xrpl4j.model.transactions.XChainAddAccountCreateAttestation; +import org.xrpl.xrpl4j.model.transactions.XChainAddClaimAttestation; +import org.xrpl.xrpl4j.model.transactions.XChainClaim; +import org.xrpl.xrpl4j.model.transactions.XChainCommit; +import org.xrpl.xrpl4j.model.transactions.XChainCreateBridge; +import org.xrpl.xrpl4j.model.transactions.XChainCreateClaimId; +import org.xrpl.xrpl4j.model.transactions.XChainModifyBridge; import java.util.List; import java.util.Objects; @@ -135,6 +145,28 @@ public UnsignedByteArray toSignableBytes(final UnsignedClaim unsignedClaim) { } } + /** + * Helper method to convert an {@link Attestation} into bytes that can be used directly for signing. + * + *

This method will be marked {@link Beta} until the featureXChainBridge amendment is enabled on mainnet. Its API + * is subject to change.

+ * + * @param attestation An {@link Attestation} to be signed. + * + * @return An {@link UnsignedByteArray}. + */ + @Beta + public UnsignedByteArray toSignableBytes(final Attestation attestation) { + Objects.requireNonNull(attestation); + try { + final String unsignedJson = objectMapper.writeValueAsString(attestation); + final String unsignedBinaryHex = binaryCodec.encode(unsignedJson); + return UnsignedByteArray.fromHex(unsignedBinaryHex); + } catch (JsonProcessingException e) { + throw new RuntimeException(e.getMessage(), e); + } + } + /** * Helper method to convert a {@link Transaction} into bytes that can be signed by multiple signers, as is the case * when the source account has set a SignerList. @@ -162,9 +194,9 @@ public UnsignedByteArray toMultiSignableBytes(final Transaction transaction, fin * Immutable object, it does not have a generated builder like its subclasses do. Thus, this method needs to rebuild * transactions based on their runtime type. * - * @param transaction An unsigned {@link Transaction} to add a signature to. Note that {@link - * Transaction#transactionSignature()} must not be provided, and {@link - * Transaction#signingPublicKey()} must be provided. + * @param transaction An unsigned {@link Transaction} to add a signature to. Note that + * {@link Transaction#transactionSignature()} must not be provided, and + * {@link Transaction#signingPublicKey()} must be provided. * @param signature A {@link Signature} containing the transaction signature. * @param extends {@link Transaction}. * @@ -306,6 +338,39 @@ public SingleSignedTransaction addSignatureToTransact transactionWithSignature = AmmDelete.builder().from((AmmDelete) transaction) .transactionSignature(signature) .build(); + } else if (XChainAccountCreateCommit.class.isAssignableFrom(transaction.getClass())) { + transactionWithSignature = XChainAccountCreateCommit.builder().from((XChainAccountCreateCommit) transaction) + .transactionSignature(signature) + .build(); + } else if (XChainAddAccountCreateAttestation.class.isAssignableFrom(transaction.getClass())) { + transactionWithSignature = XChainAddAccountCreateAttestation.builder() + .from((XChainAddAccountCreateAttestation) transaction) + .transactionSignature(signature) + .build(); + } else if (XChainAddClaimAttestation.class.isAssignableFrom(transaction.getClass())) { + transactionWithSignature = XChainAddClaimAttestation.builder().from((XChainAddClaimAttestation) transaction) + .transactionSignature(signature) + .build(); + } else if (XChainClaim.class.isAssignableFrom(transaction.getClass())) { + transactionWithSignature = XChainClaim.builder().from((XChainClaim) transaction) + .transactionSignature(signature) + .build(); + } else if (XChainCommit.class.isAssignableFrom(transaction.getClass())) { + transactionWithSignature = XChainCommit.builder().from((XChainCommit) transaction) + .transactionSignature(signature) + .build(); + } else if (XChainCreateBridge.class.isAssignableFrom(transaction.getClass())) { + transactionWithSignature = XChainCreateBridge.builder().from((XChainCreateBridge) transaction) + .transactionSignature(signature) + .build(); + } else if (XChainCreateClaimId.class.isAssignableFrom(transaction.getClass())) { + transactionWithSignature = XChainCreateClaimId.builder().from((XChainCreateClaimId) transaction) + .transactionSignature(signature) + .build(); + } else if (XChainModifyBridge.class.isAssignableFrom(transaction.getClass())) { + transactionWithSignature = XChainModifyBridge.builder().from((XChainModifyBridge) 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."); @@ -318,13 +383,13 @@ public SingleSignedTransaction addSignatureToTransact } /** - * Add {@link Transaction#signers()}} to the given transaction. Because {@link Transaction} is not an - * Immutable object, it does not have a generated builder like its subclasses do. Thus, this method needs to rebuild + * Add {@link Transaction#signers()}} to the given transaction. Because {@link Transaction} is not an Immutable + * object, it does not have a generated builder like its subclasses do. Thus, this method needs to rebuild * transactions based on their runtime type. * - * @param transaction An unsigned {@link Transaction} to add a signature to. Note that {@link - * Transaction#transactionSignature()} must not be provided, and {@link - * Transaction#signingPublicKey()} must be an empty string. + * @param transaction An unsigned {@link Transaction} to add a signature to. Note that + * {@link Transaction#transactionSignature()} must not be provided, and + * {@link Transaction#signingPublicKey()} must be an empty string. * @param signers A {@link List} of {@link SignerWrapper}s containing the transaction signatures. * @param extends {@link Transaction}. * @@ -468,6 +533,39 @@ public T addMultiSignaturesToTransaction(T transaction, transactionWithSignatures = AmmDelete.builder().from((AmmDelete) transaction) .signers(signers) .build(); + } else if (XChainAccountCreateCommit.class.isAssignableFrom(transaction.getClass())) { + transactionWithSignatures = XChainAccountCreateCommit.builder().from((XChainAccountCreateCommit) transaction) + .signers(signers) + .build(); + } else if (XChainAddAccountCreateAttestation.class.isAssignableFrom(transaction.getClass())) { + transactionWithSignatures = XChainAddAccountCreateAttestation.builder() + .from((XChainAddAccountCreateAttestation) transaction) + .signers(signers) + .build(); + } else if (XChainAddClaimAttestation.class.isAssignableFrom(transaction.getClass())) { + transactionWithSignatures = XChainAddClaimAttestation.builder().from((XChainAddClaimAttestation) transaction) + .signers(signers) + .build(); + } else if (XChainClaim.class.isAssignableFrom(transaction.getClass())) { + transactionWithSignatures = XChainClaim.builder().from((XChainClaim) transaction) + .signers(signers) + .build(); + } else if (XChainCommit.class.isAssignableFrom(transaction.getClass())) { + transactionWithSignatures = XChainCommit.builder().from((XChainCommit) transaction) + .signers(signers) + .build(); + } else if (XChainCreateBridge.class.isAssignableFrom(transaction.getClass())) { + transactionWithSignatures = XChainCreateBridge.builder().from((XChainCreateBridge) transaction) + .signers(signers) + .build(); + } else if (XChainCreateClaimId.class.isAssignableFrom(transaction.getClass())) { + transactionWithSignatures = XChainCreateClaimId.builder().from((XChainCreateClaimId) transaction) + .signers(signers) + .build(); + } else if (XChainModifyBridge.class.isAssignableFrom(transaction.getClass())) { + transactionWithSignatures = XChainModifyBridge.builder().from((XChainModifyBridge) 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/TransactionSigner.java b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/crypto/signing/TransactionSigner.java index f86cd6520..74693f564 100644 --- a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/crypto/signing/TransactionSigner.java +++ b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/crypto/signing/TransactionSigner.java @@ -20,9 +20,11 @@ * =========================LICENSE_END================================== */ +import com.google.common.annotations.Beta; import org.xrpl.xrpl4j.crypto.keys.PrivateKeyable; import org.xrpl.xrpl4j.crypto.keys.PublicKey; import org.xrpl.xrpl4j.model.client.channels.UnsignedClaim; +import org.xrpl.xrpl4j.model.ledger.Attestation; import org.xrpl.xrpl4j.model.transactions.Signer; import org.xrpl.xrpl4j.model.transactions.Transaction; @@ -66,6 +68,20 @@ public interface TransactionSigner

{ */ Signature sign(P privateKeyable, UnsignedClaim unsignedClaim); + /** + * Sign attestations for cross-chain account creation or transfers. + * + *

This method will be marked {@link Beta} until the featureXChainBridge amendment is enabled on mainnet. Its API + * is subject to change.

+ * + * @param privateKeyable The {@link P} used to sign {@code attestation}. + * @param attestation An {@link Attestation} to sign. + * + * @return A {@link Signature}. + */ + @Beta + Signature sign(P privateKeyable, Attestation attestation); + /** * Obtain a signature for the supplied unsigned transaction using the supplied {@link P}. The primary reason this * method's signature diverges from {@link #sign(PrivateKeyable, Transaction)} is that for multi-sign scenarios, the diff --git a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/crypto/signing/bc/BcDerivedKeySignatureService.java b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/crypto/signing/bc/BcDerivedKeySignatureService.java index b5c121aea..d359ebaac 100644 --- a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/crypto/signing/bc/BcDerivedKeySignatureService.java +++ b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/crypto/signing/bc/BcDerivedKeySignatureService.java @@ -46,6 +46,7 @@ import org.xrpl.xrpl4j.crypto.signing.SignatureUtils; import org.xrpl.xrpl4j.crypto.signing.SingleSignedTransaction; import org.xrpl.xrpl4j.model.client.channels.UnsignedClaim; +import org.xrpl.xrpl4j.model.ledger.Attestation; import org.xrpl.xrpl4j.model.transactions.Signer; import org.xrpl.xrpl4j.model.transactions.Transaction; @@ -127,6 +128,11 @@ public Signature sign(final PrivateKeyReference privateKeyReference, final Unsig return getTransactionSigner(privateKeyReference).sign(unsignedClaim); } + @Override + public Signature sign(final PrivateKeyReference privateKeyReference, final Attestation attestation) { + return getTransactionSigner(privateKeyReference).sign(attestation); + } + @Override public Signature multiSign( final PrivateKeyReference privateKeyReference, final T transaction @@ -294,6 +300,11 @@ public Signature sign(final UnsignedClaim unsignedClaim) { return bcSignatureService.sign(this.privateKey, unsignedClaim); } + public Signature sign(final Attestation attestation) { + Objects.requireNonNull(attestation); + return bcSignatureService.sign(this.privateKey, attestation); + } + public Signature multiSign(final T transaction) { return bcSignatureService.multiSign(this.privateKey, transaction); } 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 index 9a74f558c..11e675428 100644 --- 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 @@ -11,6 +11,7 @@ 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.BridgeObject; import org.xrpl.xrpl4j.model.ledger.CheckObject; import org.xrpl.xrpl4j.model.ledger.DepositPreAuthObject; import org.xrpl.xrpl4j.model.ledger.EscrowObject; @@ -22,6 +23,7 @@ import org.xrpl.xrpl4j.model.ledger.TicketObject; import org.xrpl.xrpl4j.model.transactions.Address; import org.xrpl.xrpl4j.model.transactions.Hash256; +import org.xrpl.xrpl4j.model.transactions.XChainBridge; import java.util.Optional; @@ -300,6 +302,27 @@ static LedgerEntryRequestParams nftPage( .build(); } + /** + * Construct a {@link LedgerEntryRequestParams} that requests a {@link BridgeObject} ledger entry. + * + * @param bridgeAccount The address of the account that owned the {@link BridgeObject}. + * @param bridge The bridge spec, as an {@link XChainBridge}. + * @param ledgerSpecifier A {@link LedgerSpecifier} indicating the ledger to query data from. + * + * @return A {@link LedgerEntryRequestParams} for {@link NfTokenPageObject}. + */ + static LedgerEntryRequestParams bridge( + Address bridgeAccount, + XChainBridge bridge, + LedgerSpecifier ledgerSpecifier + ) { + return ImmutableLedgerEntryRequestParams.builder() + .bridgeAccount(bridgeAccount) + .bridge(bridge) + .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. @@ -402,6 +425,23 @@ default boolean binary() { */ Optional ticket(); + /** + * Look up a {@link org.xrpl.xrpl4j.model.ledger.BridgeObject} by {@link Address}. The {@link #bridge()} field must + * also be present. + * + * @return An optionally-present {@link Address}. + */ + @JsonProperty("bridge_account") + Optional
bridgeAccount(); + + /** + * Look up a {@link org.xrpl.xrpl4j.model.ledger.BridgeObject} by {@link XChainBridge}. The {@link #bridgeAccount()} + * field must also be present. + * + * @return An optionally-present {@link XChainBridge}. + */ + Optional bridge(); + /** * The {@link Class} of {@link T}. This field is helpful when telling Jackson how to deserialize rippled's response to * a {@link T}. @@ -451,6 +491,10 @@ default Class ledgerObjectClass() { return (Class) TicketObject.class; } + if (bridgeAccount().isPresent() || bridge().isPresent()) { + return (Class) BridgeObject.class; + } + return (Class) LedgerObject.class; } } diff --git a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/flags/XChainModifyBridgeFlags.java b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/flags/XChainModifyBridgeFlags.java new file mode 100644 index 000000000..c00f65e31 --- /dev/null +++ b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/flags/XChainModifyBridgeFlags.java @@ -0,0 +1,97 @@ +package org.xrpl.xrpl4j.model.flags; + +public class XChainModifyBridgeFlags extends TransactionFlags { + + /** + * Constant {@link XChainModifyBridgeFlags} for an unset flag. + */ + public static final XChainModifyBridgeFlags UNSET = new XChainModifyBridgeFlags(0); + + /** + * Constant {@link XChainModifyBridgeFlags} for the {@code tfClearAccountCreateAmount} flag. + */ + public static final XChainModifyBridgeFlags CLEAR_ACCOUNT_CREATE_AMOUNT = new XChainModifyBridgeFlags(0x00010000); + + private XChainModifyBridgeFlags(long value) { + super(value); + } + + private XChainModifyBridgeFlags() { + + } + + /** + * Create a new {@link Builder}. + * + * @return A new {@link Builder}. + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Construct {@link XChainModifyBridgeFlags} with a given value. + * + * @param value The long-number encoded flags value of this {@link XChainModifyBridgeFlags}. + * + * @return New {@link XChainModifyBridgeFlags}. + */ + public static XChainModifyBridgeFlags of(long value) { + return new XChainModifyBridgeFlags(value); + } + + private static XChainModifyBridgeFlags of(boolean tfFullyCanonicalSig, boolean tfClearAccountCreateAmount) { + return new XChainModifyBridgeFlags(of( + tfFullyCanonicalSig ? TransactionFlags.FULLY_CANONICAL_SIG : UNSET, + tfClearAccountCreateAmount ? CLEAR_ACCOUNT_CREATE_AMOUNT : UNSET + ).getValue()); + } + + /** + * Construct an empty instance of {@link XChainModifyBridgeFlags}. Transactions with empty flags will + * not be serialized with a {@code Flags} field. + * + * @return An empty {@link XChainModifyBridgeFlags}. + */ + public static XChainModifyBridgeFlags empty() { + return new XChainModifyBridgeFlags(); + } + + /** + * Clears the MinAccountCreateAmount of the bridge. + * + * @return {@code true} if {@code tfClearAccountCreateAmount} is set, otherwise {@code false}. + */ + public boolean tfClearAccountCreateAmount() { + return this.isSet(XChainModifyBridgeFlags.CLEAR_ACCOUNT_CREATE_AMOUNT); + } + + /** + * A builder class for {@link XChainModifyBridgeFlags} flags. + */ + public static class Builder { + + private boolean tfClearAccountCreateAmount = false; + + /** + * Set {@code tfClearAccountCreateAmount} to the given value. + * + * @param tfClearAccountCreateAmount A boolean value. + * + * @return The same {@link Builder}. + */ + public Builder tfClearAccountCreateAmount(boolean tfClearAccountCreateAmount) { + this.tfClearAccountCreateAmount = tfClearAccountCreateAmount; + return this; + } + + /** + * Build a new {@link XChainModifyBridgeFlags} from the current boolean values. + * + * @return A new {@link XChainModifyBridgeFlags}. + */ + public XChainModifyBridgeFlags build() { + return XChainModifyBridgeFlags.of(true, tfClearAccountCreateAmount); + } + } +} 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 da326cd40..4ece438f5 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 @@ -30,20 +30,8 @@ import org.xrpl.xrpl4j.model.transactions.metadata.AffectedNode; import org.xrpl.xrpl4j.model.transactions.metadata.CreatedNode; import org.xrpl.xrpl4j.model.transactions.metadata.DeletedNode; -import org.xrpl.xrpl4j.model.transactions.metadata.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; +import org.xrpl.xrpl4j.model.transactions.metadata.MetaLedgerEntryType; import org.xrpl.xrpl4j.model.transactions.metadata.MetaLedgerObject; -import org.xrpl.xrpl4j.model.transactions.metadata.MetaNfTokenOfferObject; -import org.xrpl.xrpl4j.model.transactions.metadata.MetaNfTokenPageObject; -import org.xrpl.xrpl4j.model.transactions.metadata.MetaOfferObject; -import org.xrpl.xrpl4j.model.transactions.metadata.MetaPayChannelObject; -import org.xrpl.xrpl4j.model.transactions.metadata.MetaRippleStateObject; -import org.xrpl.xrpl4j.model.transactions.metadata.MetaSignerListObject; -import org.xrpl.xrpl4j.model.transactions.metadata.MetaTicketObject; -import org.xrpl.xrpl4j.model.transactions.metadata.MetaUnknownObject; import org.xrpl.xrpl4j.model.transactions.metadata.ModifiedNode; import java.io.IOException; @@ -68,8 +56,10 @@ public AffectedNode deserialize(JsonParser jsonParser, DeserializationContext ct Map.Entry nodeFieldAndValue = jsonNode.fields().next(); String affectedNodeType = nodeFieldAndValue.getKey(); - String ledgerEntryType = nodeFieldAndValue.getValue().get("LedgerEntryType").asText(); - Class ledgerObjectClass = determineLedgerObjectType(ledgerEntryType); + MetaLedgerEntryType ledgerEntryType = MetaLedgerEntryType.of( + nodeFieldAndValue.getValue().get("LedgerEntryType").asText() + ); + Class ledgerObjectClass = ledgerEntryType.ledgerObjectType(); switch (affectedNodeType) { case "CreatedNode": @@ -93,35 +83,4 @@ public AffectedNode deserialize(JsonParser jsonParser, DeserializationContext ct ); } } - - private Class determineLedgerObjectType(String ledgerEntryType) { - switch (ledgerEntryType) { - case "AccountRoot": - return MetaAccountRootObject.class; - case "Check": - return MetaCheckObject.class; - case "DepositPreauth": - return MetaDepositPreAuthObject.class; - case "Escrow": - return MetaEscrowObject.class; - case "NFTokenOffer": - return MetaNfTokenOfferObject.class; - case "Offer": - return MetaOfferObject.class; - case "PayChannel": - return MetaPayChannelObject.class; - case "RippleState": - return MetaRippleStateObject.class; - case "SignerList": - return MetaSignerListObject.class; - case "Ticket": - 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/XChainClaimIdDeserializer.java b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/jackson/modules/XChainClaimIdDeserializer.java new file mode 100644 index 000000000..5e0293252 --- /dev/null +++ b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/jackson/modules/XChainClaimIdDeserializer.java @@ -0,0 +1,31 @@ +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 com.google.common.primitives.UnsignedLong; +import org.xrpl.xrpl4j.model.transactions.VoteWeight; +import org.xrpl.xrpl4j.model.transactions.XChainClaimId; + +import java.io.IOException; +import java.math.BigInteger; + +/** + * Custom Jackson deserializer for {@link VoteWeight}s. + */ +public class XChainClaimIdDeserializer extends StdDeserializer { + + /** + * No-args constructor. + */ + public XChainClaimIdDeserializer() { + super(XChainClaimId.class); + } + + @Override + public XChainClaimId deserialize(JsonParser jsonParser, DeserializationContext ctxt) throws IOException { + // sfXChainClaimID is a STUInt64, which in JSON is represented as a hex-encoded String. + return XChainClaimId.of(UnsignedLong.valueOf(jsonParser.getText(), 16)); + } +} diff --git a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/jackson/modules/XChainClaimIdSerializer.java b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/jackson/modules/XChainClaimIdSerializer.java new file mode 100644 index 000000000..b9100460b --- /dev/null +++ b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/jackson/modules/XChainClaimIdSerializer.java @@ -0,0 +1,28 @@ +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 org.xrpl.xrpl4j.model.transactions.XChainClaimId; + +import java.io.IOException; + +/** + * Custom Jackson serializer for {@link VoteWeight}s. + */ +public class XChainClaimIdSerializer extends StdScalarSerializer { + + /** + * No-args constructor. + */ + public XChainClaimIdSerializer() { + super(XChainClaimId.class, false); + } + + @Override + public void serialize(XChainClaimId claimId, JsonGenerator gen, SerializerProvider provider) throws IOException { + // sfXChainClaimID is a STUInt64, which in JSON is represented as a hex-encoded String. + gen.writeString(claimId.value().toString(16)); + } +} diff --git a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/jackson/modules/XChainCountDeserializer.java b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/jackson/modules/XChainCountDeserializer.java new file mode 100644 index 000000000..d7b55f453 --- /dev/null +++ b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/jackson/modules/XChainCountDeserializer.java @@ -0,0 +1,31 @@ +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.UnsignedLong; +import org.xrpl.xrpl4j.model.transactions.VoteWeight; +import org.xrpl.xrpl4j.model.transactions.XChainClaimId; +import org.xrpl.xrpl4j.model.transactions.XChainCount; + +import java.io.IOException; + +/** + * Custom Jackson deserializer for {@link VoteWeight}s. + */ +public class XChainCountDeserializer extends StdDeserializer { + + /** + * No-args constructor. + */ + public XChainCountDeserializer() { + super(XChainCount.class); + } + + @Override + public XChainCount deserialize(JsonParser jsonParser, DeserializationContext ctxt) throws IOException { + // sfXChainAccountCreateCount and sfXChainAccountClaimCount are STUInt64s, which in JSON is represented as a + // hex-encoded String. + return XChainCount.of(UnsignedLong.valueOf(jsonParser.getText(), 16)); + } +} diff --git a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/jackson/modules/XChainCountSerializer.java b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/jackson/modules/XChainCountSerializer.java new file mode 100644 index 000000000..16de4cb1d --- /dev/null +++ b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/jackson/modules/XChainCountSerializer.java @@ -0,0 +1,30 @@ +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 org.xrpl.xrpl4j.model.transactions.XChainClaimId; +import org.xrpl.xrpl4j.model.transactions.XChainCount; + +import java.io.IOException; + +/** + * Custom Jackson serializer for {@link VoteWeight}s. + */ +public class XChainCountSerializer extends StdScalarSerializer { + + /** + * No-args constructor. + */ + public XChainCountSerializer() { + super(XChainCount.class, false); + } + + @Override + public void serialize(XChainCount count, JsonGenerator gen, SerializerProvider provider) throws IOException { + // sfXChainAccountCreateCount and sfXChainAccountClaimCount are STUInt64s, which in JSON is represented as a + // hex-encoded String. + gen.writeString(count.value().toString(16)); + } +} diff --git a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/ledger/Attestation.java b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/ledger/Attestation.java new file mode 100644 index 000000000..f93a6d7b4 --- /dev/null +++ b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/ledger/Attestation.java @@ -0,0 +1,65 @@ +package org.xrpl.xrpl4j.model.ledger; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonFormat.Shape; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.google.common.annotations.Beta; +import org.xrpl.xrpl4j.model.transactions.Address; +import org.xrpl.xrpl4j.model.transactions.CurrencyAmount; +import org.xrpl.xrpl4j.model.transactions.XChainBridge; + +/** + * Represents a generic cross-chain attestation. + * + *

This class will be marked {@link Beta} until the featureXChainBridge amendment is enabled on mainnet. Its API is + * subject to change.

+ */ +@Beta +public interface Attestation { + + /** + * The bridge spec. + * + * @return An {@link XChainBridge}. + */ + @JsonProperty("XChainBridge") + @SuppressWarnings("MethodName") + XChainBridge xChainBridge(); + + /** + * The account on the source chain that submitted the + * {@link org.xrpl.xrpl4j.model.transactions.XChainAccountCreateCommit} transaction or + * {@link org.xrpl.xrpl4j.model.transactions.XChainCommit} transaction that triggered the event associated with the + * attestation. + * + * @return An {@link Address}. + */ + @JsonProperty("OtherChainSource") + Address otherChainSource(); + + /** + * The account that should receive this signer's share of the {@code SignatureReward}. + * + * @return An {@link Address}. + */ + @JsonProperty("AttestationRewardAccount") + Address attestationRewardAccount(); + + /** + * The amount committed by the {@link org.xrpl.xrpl4j.model.transactions.XChainAccountCreateCommit} or + * {@link org.xrpl.xrpl4j.model.transactions.XChainCommit} transaction on the source chain. + * + * @return A {@link CurrencyAmount}. + */ + @JsonProperty("Amount") + CurrencyAmount amount(); + + /** + * A boolean representing the chain where the event occurred. + * + * @return {@code true} if the event occurred on the locking chain, otherwise {@code false}. + */ + @JsonProperty("WasLockingChainSend") + @JsonFormat(shape = Shape.NUMBER) + boolean wasLockingChainSend(); +} diff --git a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/ledger/AttestationClaim.java b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/ledger/AttestationClaim.java new file mode 100644 index 000000000..b40b8294b --- /dev/null +++ b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/ledger/AttestationClaim.java @@ -0,0 +1,51 @@ +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.Immutable; +import org.xrpl.xrpl4j.model.transactions.Address; +import org.xrpl.xrpl4j.model.transactions.XChainClaimId; + +import java.util.Optional; + +/** + * An attestation that an {@link org.xrpl.xrpl4j.model.transactions.XChainCommit} transaction occurred on another chain. + * + *

This class will be marked {@link Beta} until the featureXChainBridge amendment is enabled on mainnet. Its API is + * subject to change.

+ */ +@Beta +@Immutable +@JsonSerialize(as = ImmutableAttestationClaim.class) +@JsonDeserialize(as = ImmutableAttestationClaim.class) +public interface AttestationClaim extends Attestation { + + /** + * Construct a {@code AttestationClaim} builder. + * + * @return An {@link ImmutableAttestationClaim.Builder}. + */ + static ImmutableAttestationClaim.Builder builder() { + return ImmutableAttestationClaim.builder(); + } + + /** + * The destination account for the funds on the destination chain. + * + * @return An {@link Address}. + */ + @JsonProperty("Destination") + Optional
destination(); + + /** + * The ID of the {@link XChainOwnedClaimIdObject}. + * + * @return An {@link XChainClaimId}. + */ + @JsonProperty("XChainClaimID") + @SuppressWarnings("MethodName") + XChainClaimId xChainClaimId(); + +} diff --git a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/ledger/AttestationCreateAccount.java b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/ledger/AttestationCreateAccount.java new file mode 100644 index 000000000..5371bf143 --- /dev/null +++ b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/ledger/AttestationCreateAccount.java @@ -0,0 +1,60 @@ +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.Immutable; +import org.xrpl.xrpl4j.model.transactions.Address; +import org.xrpl.xrpl4j.model.transactions.XChainCount; +import org.xrpl.xrpl4j.model.transactions.XrpCurrencyAmount; + +/** + * The attestation message that witness servers sign to include in a + * {@link org.xrpl.xrpl4j.model.transactions.XChainAddAccountCreateAttestation}. + * + *

This interface will be marked {@link Beta} until the featureXChainBridge amendment is enabled on mainnet. Its API + * is subject to change.

+ */ +@Beta +@Immutable +@JsonSerialize(as = ImmutableAttestationCreateAccount.class) +@JsonDeserialize(as = ImmutableAttestationCreateAccount.class) +public interface AttestationCreateAccount extends Attestation { + + /** + * Construct a {@code AttestationCreateAccount} builder. + * + * @return An {@link ImmutableAttestationCreateAccount.Builder}. + */ + static ImmutableAttestationCreateAccount.Builder builder() { + return ImmutableAttestationCreateAccount.builder(); + } + + /** + * The number of accounts that have been created by the bridge. + * + * @return An {@link XChainCount}. + */ + @JsonProperty("XChainAccountCreateCount") + @SuppressWarnings("MethodName") + XChainCount xChainAccountCreateCount(); + + /** + * The destination account for the funds on the destination chain. + * + * @return An {@link Address}. + */ + @JsonProperty("Destination") + Address destination(); + + /** + * The total amount, in XRP, to be rewarded for providing a signature for cross-chain transfer or for signing for the + * cross-chain reward. This amount will be split among the signers. + * + * @return An {@link XrpCurrencyAmount}. + */ + @JsonProperty("SignatureReward") + XrpCurrencyAmount signatureReward(); + +} diff --git a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/ledger/BridgeObject.java b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/ledger/BridgeObject.java new file mode 100644 index 000000000..ac51cedf0 --- /dev/null +++ b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/ledger/BridgeObject.java @@ -0,0 +1,169 @@ +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 com.google.common.primitives.UnsignedInteger; +import org.immutables.value.Value; +import org.immutables.value.Value.Immutable; +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.XChainAccountCreateCommit; +import org.xrpl.xrpl4j.model.transactions.XChainBridge; +import org.xrpl.xrpl4j.model.transactions.XChainClaimId; +import org.xrpl.xrpl4j.model.transactions.XChainCount; +import org.xrpl.xrpl4j.model.transactions.XrpCurrencyAmount; + +import java.util.Optional; + +/** + * Represents a single cross-chain bridge that connects the XRP Ledger with another blockchain, such as its sidechain, + * and enables value in the form of XRP and other tokens (IOUs) to move efficiently between the two blockchains. + * + *

This interface will be marked {@link Beta} until the featureXChainBridge amendment is enabled on mainnet. Its API + * is subject to change.

+ */ +@Beta +@Immutable +@JsonSerialize(as = ImmutableBridgeObject.class) +@JsonDeserialize(as = ImmutableBridgeObject.class) +public interface BridgeObject extends LedgerObject { + + /** + * Construct a {@code BridgeObject} builder. + * + * @return An {@link ImmutableBridgeObject.Builder}. + */ + static ImmutableBridgeObject.Builder builder() { + return ImmutableBridgeObject.builder(); + } + + /** + * The type of ledger object, which will always be "Bridge" in this case. + * + * @return Always returns {@link org.xrpl.xrpl4j.model.ledger.LedgerObject.LedgerEntryType#BRIDGE}. + */ + @JsonProperty("LedgerEntryType") + @Value.Derived + default LedgerEntryType ledgerEntryType() { + return LedgerEntryType.BRIDGE; + } + + /** + * A bit-map of boolean flags. No flags are defined for {@link BridgeObject}, so this value is always 0. + * + * @return Always {@link Flags#UNSET}. + */ + @JsonProperty("Flags") + @Value.Derived + default Flags flags() { + return Flags.UNSET; + } + + /** + * The account that owns this object. + * + * @return An {@link Address}. + */ + @JsonProperty("Account") + Address account(); + + /** + * The minimum amount, in XRP, required for an XChainAccountCreateCommit transaction. If this isn't present, the + * {@link org.xrpl.xrpl4j.model.transactions.XChainAccountCreateCommit} transaction will fail. This field can only be + * present on XRP-XRP bridges. + * + * @return An optionally-present {@link XrpCurrencyAmount}. + */ + @JsonProperty("MinAccountCreateAmount") + Optional minAccountCreateAmount(); + + /** + * The total amount, in XRP, to be rewarded for providing a signature for cross-chain transfer or for signing for the + * cross-chain reward. This amount will be split among the signers. + * + * @return An {@link XrpCurrencyAmount}. + */ + @JsonProperty("SignatureReward") + XrpCurrencyAmount signatureReward(); + + /** + * The door accounts and assets of the bridge this object correlates to. + * + * @return An {@link XChainBridge}. + */ + @JsonProperty("XChainBridge") + @SuppressWarnings("MethodName") + XChainBridge xChainBridge(); + + /** + * The value of the next XChainClaimID to be created. + * + * @return An {@link XChainClaimId}. + */ + @JsonProperty("XChainClaimID") + @SuppressWarnings("MethodName") + XChainClaimId xChainClaimId(); + + /** + * A counter used to order the execution of account create transactions. It is incremented every time a successful + * {@link org.xrpl.xrpl4j.model.transactions.XChainAccountCreateCommit} transaction is run for the source chain. + * + * @return An {@link XChainCount}. + */ + @JsonProperty("XChainAccountCreateCount") + @SuppressWarnings("MethodName") + XChainCount xChainAccountCreateCount(); + + /** + * A counter used to order the execution of account create transactions. It is incremented every time a + * {@link org.xrpl.xrpl4j.model.transactions.XChainAccountCreateCommit} transaction is "claimed" on the destination + * chain. When the "claim" transaction is run on the destination chain, the {@link #xChainAccountClaimCount()} must + * match the value that the {@link #xChainAccountCreateCount()} had at the time the {@link #xChainAccountClaimCount()} + * was run on the source chain. This orders the claims so that they run in the same order that the + * {@link org.xrpl.xrpl4j.model.transactions.XChainAccountCreateCommit} transactions ran on the source chain, to + * prevent transaction replay. + * + * @return An {@link XChainCount}. + */ + @JsonProperty("XChainAccountClaimCount") + @SuppressWarnings("MethodName") + XChainCount xChainAccountClaimCount(); + + /** + * 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(); + + /** + * The identifying hash of the transaction that most recently modified this object. + * + * @return A {@link Hash256} containing the previous transaction hash. + */ + @JsonProperty("PreviousTxnID") + Hash256 previousTransactionId(); + + /** + * The index of the ledger that contains the transaction that most recently modified this object. + * + * @return An {@link UnsignedInteger} representing the previous transaction ledger sequence. + */ + @JsonProperty("PreviousTxnLgrSeq") + UnsignedInteger previousTransactionLedgerSequence(); + + /** + * The unique ID of the {@link BridgeObject}. + * + * @return A {@link Hash256} containing the ID. + */ + Hash256 index(); +} 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 b6b7e1b2f..ecbd3d316 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 @@ -55,6 +55,12 @@ @JsonSubTypes.Type(value = ImmutableTicketObject.class, name = "Ticket"), @JsonSubTypes.Type(value = ImmutableAmmObject.class, name = "AMM"), @JsonSubTypes.Type(value = ImmutableNfTokenPageObject.class, name = "NFTokenPage"), + @JsonSubTypes.Type(value = ImmutableBridgeObject.class, name = "Bridge"), + @JsonSubTypes.Type( + value = ImmutableXChainOwnedCreateAccountClaimIdObject.class, + name = "XChainOwnedCreateAccountClaimID" + ), + @JsonSubTypes.Type(value = ImmutableXChainOwnedClaimIdObject.class, name = "XChainOwnedClaimID"), }) // TODO: Uncomment subtypes as we implement public interface LedgerObject { @@ -146,11 +152,39 @@ enum LedgerEntryType { /** * 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.

+ *

This constant will be marked {@link Beta} until the AMM amendment is enabled on mainnet. Its API is subject + * to + * change.

*/ @Beta - AMM("AMM"); + AMM("AMM"), + + /** + * The {@link LedgerEntryType} for {@code Bridge} ledger objects. + * + *

This constant will be marked {@link Beta} until the featureXChainBridge amendment is enabled on mainnet. + * Its API is subject to change.

+ */ + @Beta + BRIDGE("Bridge"), + + /** + * The {@link LedgerEntryType} for {@code XChainOwnedCreateAccountClaimID} ledger objects. + * + *

This constant will be marked {@link Beta} until the featureXChainBridge amendment is enabled on mainnet. + * Its API is subject to change.

+ */ + @Beta + XCHAIN_OWNED_CREATE_ACCOUNT_CLAIM_ID("XChainOwnedCreateAccountClaimID"), + + /** + * The {@link LedgerEntryType} for {@code XChainOwnedClaimID} ledger objects. + * + *

This constant will be marked {@link Beta} until the featureXChainBridge amendment is enabled on mainnet. + * Its API is subject to change.

+ */ + @Beta + XCHAIN_OWNED_CLAIM_ID("XChainOwnedClaimID"); private final String value; @@ -165,6 +199,7 @@ enum LedgerEntryType { *

Mostly used by Jackson for deserialization. * * @param value The {@link String} value of a {@link LedgerEntryType}. + * * @return A {@link LedgerEntryType}. */ @JsonCreator diff --git a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/ledger/XChainClaimAttestation.java b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/ledger/XChainClaimAttestation.java new file mode 100644 index 000000000..ec7ccdb06 --- /dev/null +++ b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/ledger/XChainClaimAttestation.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.Immutable; + +/** + * A wrapper around {@link XChainClaimProofSig}s. + * + *

This class will be marked {@link Beta} until the featureXChainBridge amendment is enabled on mainnet. Its API is + * subject to change.

+ */ +@Beta +@Immutable +@JsonSerialize(as = ImmutableXChainClaimAttestation.class) +@JsonDeserialize(as = ImmutableXChainClaimAttestation.class) +public interface XChainClaimAttestation { + + /** + * Construct an {@link XChainClaimAttestation} containing the specified {@link XChainClaimProofSig}. + * + * @param proofSig An {@link XChainClaimProofSig}. + * + * @return An {@link XChainClaimAttestation}. + */ + static XChainClaimAttestation of(XChainClaimProofSig proofSig) { + return ImmutableXChainClaimAttestation.builder() + .xChainClaimProofSig(proofSig) + .build(); + } + + /** + * An {@link XChainClaimProofSig}. + * + * @return An {@link XChainClaimProofSig}. + */ + @JsonProperty("XChainClaimProofSig") + @SuppressWarnings("MethodName") + XChainClaimProofSig xChainClaimProofSig(); + +} diff --git a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/ledger/XChainClaimProofSig.java b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/ledger/XChainClaimProofSig.java new file mode 100644 index 000000000..eae36365e --- /dev/null +++ b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/ledger/XChainClaimProofSig.java @@ -0,0 +1,86 @@ +package org.xrpl.xrpl4j.model.ledger; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonFormat.Shape; +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.Immutable; +import org.xrpl.xrpl4j.crypto.keys.PublicKey; +import org.xrpl.xrpl4j.model.transactions.Address; +import org.xrpl.xrpl4j.model.transactions.CurrencyAmount; + +import java.util.Optional; + +/** + * Represents an attestation for an {@link XChainOwnedClaimIdObject}. + * + *

This class will be marked {@link Beta} until the featureXChainBridge amendment is enabled on mainnet. Its API is + * subject to change.

+ */ +@Immutable +@JsonSerialize(as = ImmutableXChainClaimProofSig.class) +@JsonDeserialize(as = ImmutableXChainClaimProofSig.class) +public interface XChainClaimProofSig { + + /** + * Construct a {@code XChainClaimProofSig} builder. + * + * @return An {@link ImmutableXChainClaimProofSig.Builder}. + */ + static ImmutableXChainClaimProofSig.Builder builder() { + return ImmutableXChainClaimProofSig.builder(); + } + + /** + * The account on the door account's signer list that is signing the transaction. + * + * @return An {@link Address}. + */ + @JsonProperty("AttestationSignerAccount") + Address attestationSignerAccount(); + + /** + * The public key used to verify the signature. + * + * @return A {@link PublicKey}. + */ + @JsonProperty("PublicKey") + PublicKey publicKey(); + + /** + * The amount to claim in the {@link org.xrpl.xrpl4j.model.transactions.XChainCommit} transaction on the destination + * chain. + * + * @return A {@link CurrencyAmount}. + */ + @JsonProperty("Amount") + CurrencyAmount amount(); + + /** + * The account that should receive this signer's share of the SignatureReward. + * + * @return An {@link Address}. + */ + @JsonProperty("AttestationRewardAccount") + Address attestationRewardAccount(); + + /** + * A boolean representing the chain where the event occurred. + * + * @return {@code true} if the event occurred on the locking chain, otherwise {@code false}. + */ + @JsonProperty("WasLockingChainSend") + @JsonFormat(shape = Shape.NUMBER) + boolean wasLockingChainSend(); + + /** + * The destination account for the funds on the destination chain. + * + * @return An {@link Address}. + */ + @JsonProperty("Destination") + Optional
destination(); + +} diff --git a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/ledger/XChainCreateAccountAttestation.java b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/ledger/XChainCreateAccountAttestation.java new file mode 100644 index 000000000..666eec312 --- /dev/null +++ b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/ledger/XChainCreateAccountAttestation.java @@ -0,0 +1,44 @@ +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.Immutable; + +/** + * A wrapper around {@link XChainCreateAccountProofSig}s. + * + *

This class will be marked {@link Beta} until the featureXChainBridge amendment is enabled on mainnet. Its API is + * subject to + * change.

+ */ +@Beta +@Immutable +@JsonSerialize(as = ImmutableXChainCreateAccountAttestation.class) +@JsonDeserialize(as = ImmutableXChainCreateAccountAttestation.class) +public interface XChainCreateAccountAttestation { + + /** + * Construct an {@link XChainCreateAccountAttestation} containing the specified {@link XChainCreateAccountProofSig}. + * + * @param proofSig An {@link XChainCreateAccountProofSig}. + * + * @return An {@link XChainCreateAccountAttestation}. + */ + static XChainCreateAccountAttestation of(XChainCreateAccountProofSig proofSig) { + return ImmutableXChainCreateAccountAttestation.builder() + .xChainCreateAccountProofSig(proofSig) + .build(); + } + + /** + * An {@link XChainCreateAccountProofSig}. + * + * @return An {@link XChainCreateAccountProofSig}. + */ + @JsonProperty("XChainCreateAccountProofSig") + @SuppressWarnings("MethodName") + XChainCreateAccountProofSig xChainCreateAccountProofSig(); + +} diff --git a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/ledger/XChainCreateAccountProofSig.java b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/ledger/XChainCreateAccountProofSig.java new file mode 100644 index 000000000..87f394365 --- /dev/null +++ b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/ledger/XChainCreateAccountProofSig.java @@ -0,0 +1,95 @@ +package org.xrpl.xrpl4j.model.ledger; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonFormat.Shape; +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.Immutable; +import org.xrpl.xrpl4j.crypto.keys.PublicKey; +import org.xrpl.xrpl4j.model.transactions.Address; +import org.xrpl.xrpl4j.model.transactions.CurrencyAmount; +import org.xrpl.xrpl4j.model.transactions.XrpCurrencyAmount; + +/** + * Represents an attestation for an {@link XChainOwnedCreateAccountClaimIdObject}. + * + *

This interface will be marked {@link Beta} until the featureXChainBridge amendment is enabled on mainnet. Its API + * is subject to change.

+ */ +@Beta +@Immutable +@JsonSerialize(as = ImmutableXChainCreateAccountProofSig.class) +@JsonDeserialize(as = ImmutableXChainCreateAccountProofSig.class) +public interface XChainCreateAccountProofSig { + + /** + * Construct a {@code XChainCreateAccountProofSig} builder. + * + * @return An {@link ImmutableXChainCreateAccountProofSig.Builder}. + */ + static ImmutableXChainCreateAccountProofSig.Builder builder() { + return ImmutableXChainCreateAccountProofSig.builder(); + } + + /** + * The amount committed by the {@link org.xrpl.xrpl4j.model.transactions.XChainAccountCreateCommit} transaction on the + * source chain. + * + * @return A {@link CurrencyAmount}. + */ + @JsonProperty("Amount") + CurrencyAmount amount(); + + /** + * The total amount, in XRP, to be rewarded for providing a signature for cross-chain transfer or for signing for the + * cross-chain reward. This amount will be split among the signers. + * + * @return An {@link XrpCurrencyAmount}. + */ + @JsonProperty("SignatureReward") + XrpCurrencyAmount signatureReward(); + + /** + * The account that should receive this signer's share of the {@link #signatureReward()}. + * + * @return An {@link Address}. + */ + @JsonProperty("AttestationRewardAccount") + Address attestationRewardAccount(); + + /** + * The account on the door account's signer list that is signing the transaction. + * + * @return An {@link Address}. + */ + @JsonProperty("AttestationSignerAccount") + Address attestationSignerAccount(); + + /** + * The destination account for the funds on the destination chain. + * + * @return An {@link Address}. + */ + @JsonProperty("Destination") + Address destination(); + + /** + * The public key used to verify the signature. + * + * @return A {@link PublicKey}. + */ + @JsonProperty("PublicKey") + PublicKey publicKey(); + + /** + * A boolean representing the chain where the event occurred. + * + * @return {@code true} if the event occurred on the locking chain, otherwise {@code false}. + */ + @JsonProperty("WasLockingChainSend") + @JsonFormat(shape = Shape.NUMBER) + boolean wasLockingChainSend(); + +} diff --git a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/ledger/XChainOwnedClaimIdObject.java b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/ledger/XChainOwnedClaimIdObject.java new file mode 100644 index 000000000..0ef9d8e17 --- /dev/null +++ b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/ledger/XChainOwnedClaimIdObject.java @@ -0,0 +1,165 @@ +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 com.google.common.primitives.UnsignedInteger; +import org.immutables.value.Value; +import org.immutables.value.Value.Immutable; +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.XChainBridge; +import org.xrpl.xrpl4j.model.transactions.XChainClaimId; +import org.xrpl.xrpl4j.model.transactions.XChainCommit; +import org.xrpl.xrpl4j.model.transactions.XChainCount; +import org.xrpl.xrpl4j.model.transactions.XChainCreateClaimId; +import org.xrpl.xrpl4j.model.transactions.XrpCurrencyAmount; + +import java.util.List; + +/** + * An {@code XChainOwnedClaimID} object represents one cross-chain transfer of value and includes information of the + * account on the source chain that locks or burns the funds on the source chain. + * + *

The {@code XChainOwnedClaimID} object must be acquired on the destination chain before submitting a + * {@link org.xrpl.xrpl4j.model.transactions.XChainCommit} on the source chain. Its purpose is to prevent transaction + * replay attacks and is also used as a place to collect attestations from witness servers.

+ * + *

An {@link XChainCreateClaimId} transaction is used to create a new {@code XChainOwnedClaimID}. The ledger object + * is destroyed when the funds are successfully claimed on the destination chain.

+ * + *

This interface will be marked {@link Beta} until the featureXChainBridge amendment is enabled on mainnet. Its API + * is subject to change.

+ */ +@Beta +@Immutable +@JsonSerialize(as = ImmutableXChainOwnedClaimIdObject.class) +@JsonDeserialize(as = ImmutableXChainOwnedClaimIdObject.class) +public interface XChainOwnedClaimIdObject extends LedgerObject { + + /** + * Construct a {@code XChainOwnedClaimIdObject} builder. + * + * @return An {@link ImmutableXChainOwnedClaimIdObject.Builder}. + */ + static ImmutableXChainOwnedClaimIdObject.Builder builder() { + return ImmutableXChainOwnedClaimIdObject.builder(); + } + + /** + * The type of ledger object, which will always be "XChainOwnedClaimID" in this case. + * + * @return Always returns {@link LedgerEntryType#XCHAIN_OWNED_CLAIM_ID}. + */ + @JsonProperty("LedgerEntryType") + @Value.Derived + default LedgerEntryType ledgerEntryType() { + return LedgerEntryType.XCHAIN_OWNED_CLAIM_ID; + } + + /** + * A bit-map of boolean flags. No flags are defined for {@link XChainOwnedClaimIdObject}, so this value is always 0. + * + * @return Always {@link Flags#UNSET}. + */ + @JsonProperty("Flags") + @Value.Derived + default Flags flags() { + return Flags.UNSET; + } + + /** + * The account that owns this object. + * + * @return An {@link Address}. + */ + @JsonProperty("Account") + Address account(); + + /** + * The door accounts and assets of the bridge this object correlates to. + * + * @return An {@link XChainBridge}. + */ + @JsonProperty("XChainBridge") + @SuppressWarnings("MethodName") + XChainBridge xChainBridge(); + + /** + * The unique sequence number for a cross-chain transfer. + * + * @return An {@link XChainClaimId}. + */ + @JsonProperty("XChainClaimID") + @SuppressWarnings("MethodName") + XChainClaimId xChainClaimId(); + + /** + * The account that must send the corresponding {@link org.xrpl.xrpl4j.model.transactions.XChainCommit} on the source + * chain. The destination may be specified in the {@link org.xrpl.xrpl4j.model.transactions.XChainCommit} transaction, + * which means that if the OtherChainSource isn't specified, another account can try to specify a different + * destination and steal the funds. This also allows tracking only a single set of signatures, since we know which + * account will send the {@link org.xrpl.xrpl4j.model.transactions.XChainCommit} transaction. + * + * @return An {@link Address}. + */ + @JsonProperty("OtherChainSource") + Address otherChainSource(); + + /** + * Attestations collected from the witness servers. This includes the parameters needed to recreate the message that + * was signed, including the amount, which chain (locking or issuing), optional destination, and reward account for + * that signature. + * + * @return A {@link List} of {@link XChainClaimAttestation}s. + */ + @JsonProperty("XChainClaimAttestations") + @SuppressWarnings("MethodName") + List xChainClaimAttestations(); + + /** + * The total amount to pay the witness servers for their signatures. It must be at least the value of SignatureReward + * in the Bridge ledger object. + * + * @return An {@link XrpCurrencyAmount}. + */ + @JsonProperty("SignatureReward") + XrpCurrencyAmount signatureReward(); + + /** + * 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(); + + /** + * The identifying hash of the transaction that most recently modified this object. + * + * @return A {@link Hash256} containing the previous transaction hash. + */ + @JsonProperty("PreviousTxnID") + Hash256 previousTransactionId(); + + /** + * The index of the ledger that contains the transaction that most recently modified this object. + * + * @return An {@link UnsignedInteger} representing the previous transaction ledger sequence. + */ + @JsonProperty("PreviousTxnLgrSeq") + UnsignedInteger previousTransactionLedgerSequence(); + + /** + * The unique ID of the {@link XChainOwnedClaimIdObject}. + * + * @return A {@link Hash256} containing the ID. + */ + Hash256 index(); +} diff --git a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/ledger/XChainOwnedCreateAccountClaimIdObject.java b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/ledger/XChainOwnedCreateAccountClaimIdObject.java new file mode 100644 index 000000000..690a16d9e --- /dev/null +++ b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/ledger/XChainOwnedCreateAccountClaimIdObject.java @@ -0,0 +1,142 @@ +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 com.google.common.primitives.UnsignedInteger; +import org.immutables.value.Value; +import org.immutables.value.Value.Immutable; +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.XChainBridge; +import org.xrpl.xrpl4j.model.transactions.XChainCount; + +import java.util.List; + +/** + * The {@code XChainOwnedCreateAccountClaimID} ledger object is used to collect attestations for creating an account via + * a cross-chain transfer. + * + *

It is created when an XChainAddAccountCreateAttestation transaction adds a signature attesting to a + * XChainAccountCreateCommit transaction and the XChainAccountCreateCount is greater than or equal to the current + * XChainAccountClaimCount on the Bridge ledger object.

+ * + *

The ledger object is destroyed when all the attestations have been received and the funds have transferred to the + * new account.

+ * + *

This interface will be marked {@link Beta} until the featureXChainBridge amendment is enabled on mainnet. Its API + * is subject to change.

+ */ +@Beta +@Immutable +@JsonSerialize(as = ImmutableXChainOwnedCreateAccountClaimIdObject.class) +@JsonDeserialize(as = ImmutableXChainOwnedCreateAccountClaimIdObject.class) +public interface XChainOwnedCreateAccountClaimIdObject extends LedgerObject { + + /** + * Construct a {@code XChainOwnedCreateAccountClaimIdObject} builder. + * + * @return An {@link ImmutableXChainOwnedCreateAccountClaimIdObject.Builder}. + */ + static ImmutableXChainOwnedCreateAccountClaimIdObject.Builder builder() { + return ImmutableXChainOwnedCreateAccountClaimIdObject.builder(); + } + + /** + * The type of ledger object, which will always be "XChainOwnedCreateAccountClaimIdObject" in this case. + * + * @return Always returns {@link LedgerEntryType#XCHAIN_OWNED_CREATE_ACCOUNT_CLAIM_ID}. + */ + @JsonProperty("LedgerEntryType") + @Value.Derived + default LedgerEntryType ledgerEntryType() { + return LedgerEntryType.XCHAIN_OWNED_CREATE_ACCOUNT_CLAIM_ID; + } + + /** + * A bit-map of boolean flags. No flags are defined for {@link XChainOwnedCreateAccountClaimIdObject}, so this value + * is always 0. + * + * @return Always {@link Flags#UNSET}. + */ + @JsonProperty("Flags") + @Value.Derived + default Flags flags() { + return Flags.UNSET; + } + + /** + * The account that owns this object. + * + * @return An {@link Address}. + */ + @JsonProperty("Account") + Address account(); + + /** + * The door accounts and assets of the bridge this object correlates to. + * + * @return An {@link XChainBridge}. + */ + @JsonProperty("XChainBridge") + @SuppressWarnings("MethodName") + XChainBridge xChainBridge(); + + /** + * Attestations collected from the witness servers. This includes the parameters needed to recreate the message that + * was signed, including the amount, destination, signature reward amount, and reward account for that signature. With + * the exception of the reward account, all signatures must sign the message created with common parameters. + * + * @return A {@link List} of {@link XChainCreateAccountAttestation}s. + */ + @JsonProperty("XChainCreateAccountAttestations") + @SuppressWarnings("MethodName") + List xChainCreateAccountAttestations(); + + /** + * An integer that determines the order that accounts created through cross-chain transfers must be performed. Smaller + * numbers must execute before larger numbers. + * + * @return An {@link XChainCount}. + */ + @JsonProperty("XChainAccountCreateCount") + @SuppressWarnings("MethodName") + XChainCount xChainAccountCreateCount(); + + /** + * 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(); + + /** + * The identifying hash of the transaction that most recently modified this object. + * + * @return A {@link Hash256} containing the previous transaction hash. + */ + @JsonProperty("PreviousTxnID") + Hash256 previousTransactionId(); + + /** + * The index of the ledger that contains the transaction that most recently modified this object. + * + * @return An {@link UnsignedInteger} representing the previous transaction ledger sequence. + */ + @JsonProperty("PreviousTxnLgrSeq") + UnsignedInteger previousTransactionLedgerSequence(); + + /** + * The unique ID of the {@link XChainOwnedCreateAccountClaimIdObject}. + * + * @return A {@link Hash256} containing the ID. + */ + Hash256 index(); +} 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 1d756cf96..4c74c491b 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 @@ -78,6 +78,14 @@ public interface Transaction { .put(ImmutableAmmWithdraw.class, TransactionType.AMM_WITHDRAW) .put(ImmutableAmmDelete.class, TransactionType.AMM_DELETE) .put(ImmutableClawback.class, TransactionType.CLAWBACK) + .put(ImmutableXChainAccountCreateCommit.class, TransactionType.XCHAIN_ACCOUNT_CREATE_COMMIT) + .put(ImmutableXChainAddAccountCreateAttestation.class, TransactionType.XCHAIN_ADD_ACCOUNT_CREATE_ATTESTATION) + .put(ImmutableXChainAddClaimAttestation.class, TransactionType.XCHAIN_ADD_CLAIM_ATTESTATION) + .put(ImmutableXChainClaim.class, TransactionType.XCHAIN_CLAIM) + .put(ImmutableXChainCommit.class, TransactionType.XCHAIN_COMMIT) + .put(ImmutableXChainCreateBridge.class, TransactionType.XCHAIN_CREATE_BRIDGE) + .put(ImmutableXChainCreateClaimId.class, TransactionType.XCHAIN_CREATE_CLAIM_ID) + .put(ImmutableXChainModifyBridge.class, TransactionType.XCHAIN_MODIFY_BRIDGE) .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 87a293c00..91f7d2d59 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 @@ -166,7 +166,8 @@ public enum TransactionType { /** * 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 + *

This constant will be marked {@link Beta} until the Clawback amendment is enabled on mainnet. Its API is + * subject * to change.

*/ @Beta @@ -224,7 +225,82 @@ public enum TransactionType { * change.

*/ @Beta - AMM_DELETE("AMMDelete"); + AMM_DELETE("AMMDelete"), + + /** + * The {@link TransactionType} for the {@link XChainAccountCreateCommit} transaction. + * + *

This constant will be marked {@link Beta} until the featureXChainBridge amendment is enabled on mainnet. Its API + * is subject to + * change.

+ */ + @Beta + XCHAIN_ACCOUNT_CREATE_COMMIT("XChainAccountCreateCommit"), + + /** + * The {@link TransactionType} for the {@link XChainAddAccountCreateAttestation} transaction. + * + *

This constant will be marked {@link Beta} until the featureXChainBridge amendment is enabled on mainnet. Its API + * is subject to change.

+ */ + @Beta + XCHAIN_ADD_ACCOUNT_CREATE_ATTESTATION("XChainAddAccountCreateAttestation"), + + /** + * The {@link TransactionType} for the {@link XChainAddClaimAttestation} transaction. + * + *

This constant will be marked {@link Beta} until the featureXChainBridge amendment is enabled on mainnet. Its API + * is subject to change.

+ */ + @Beta + XCHAIN_ADD_CLAIM_ATTESTATION("XChainAddClaimAttestation"), + + + /** + * The {@link TransactionType} for the {@link XChainClaim} transaction. + * + *

This constant will be marked {@link Beta} until the featureXChainBridge amendment is enabled on mainnet. Its API + * is subject to change.

+ */ + @Beta + XCHAIN_CLAIM("XChainClaim"), + + + /** + * The {@link TransactionType} for the {@link XChainCommit} transaction. + * + *

This constant will be marked {@link Beta} until the featureXChainBridge amendment is enabled on mainnet. Its API + * is subject to change.

+ */ + @Beta + XCHAIN_COMMIT("XChainCommit"), + + /** + * The {@link TransactionType} for the {@link XChainCreateBridge} transaction. + * + *

This constant will be marked {@link Beta} until the featureXChainBridge amendment is enabled on mainnet. Its API + * is subject to change.

+ */ + @Beta + XCHAIN_CREATE_BRIDGE("XChainCreateBridge"), + + /** + * The {@link TransactionType} for the {@link XChainCreateClaimId} transaction. + * + *

This constant will be marked {@link Beta} until the featureXChainBridge amendment is enabled on mainnet. Its API + * is subject to change.

+ */ + @Beta + XCHAIN_CREATE_CLAIM_ID("XChainCreateClaimID"), + + /** + * The {@link TransactionType} for the {@link XChainModifyBridge} transaction. + * + *

This constant will be marked {@link Beta} until the featureXChainBridge amendment is enabled on mainnet. Its API + * is subject to change.

+ */ + @Beta + XCHAIN_MODIFY_BRIDGE("XChainModifyBridge"); 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 d746b27f7..3558d0c9e 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 @@ -49,6 +49,10 @@ 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.XChainClaimIdDeserializer; +import org.xrpl.xrpl4j.model.jackson.modules.XChainClaimIdSerializer; +import org.xrpl.xrpl4j.model.jackson.modules.XChainCountDeserializer; +import org.xrpl.xrpl4j.model.jackson.modules.XChainCountSerializer; import org.xrpl.xrpl4j.model.jackson.modules.XrpCurrencyAmountDeserializer; import org.xrpl.xrpl4j.model.jackson.modules.XrpCurrencyAmountSerializer; @@ -490,4 +494,46 @@ public BigDecimal bigDecimalValue() { } } + + /** + * A wrapped {@link com.google.common.primitives.UnsignedLong} containing an XChainClaimID. + * + *

This class will be marked {@link com.google.common.annotations.Beta} until the featureXChainBridge amendment is + * enabled on mainnet. Its API is subject to change.

+ */ + @Value.Immutable + @Wrapped + @JsonSerialize(as = XChainClaimId.class, using = XChainClaimIdSerializer.class) + @JsonDeserialize(as = XChainClaimId.class, using = XChainClaimIdDeserializer.class) + @Beta + abstract static class _XChainClaimId extends Wrapper implements Serializable { + + @Override + public String toString() { + return this.value().toString(); + } + + } + + /** + * A wrapped {@link com.google.common.primitives.UnsignedLong} representing a counter for XLS-38 sidechains. This + * wrapper mostly exists to ensure we serialize fields of this type as a hex String in JSON, as these fields are + * STUInt64s in rippled, which are hex encoded in JSON. + * + *

This class will be marked {@link com.google.common.annotations.Beta} until the featureXChainBridge amendment is + * enabled on mainnet. Its API is subject to change.

+ */ + @Value.Immutable + @Wrapped + @JsonSerialize(as = XChainCount.class, using = XChainCountSerializer.class) + @JsonDeserialize(as = XChainCount.class, using = XChainCountDeserializer.class) + @Beta + abstract static class _XChainCount extends Wrapper implements Serializable { + + @Override + public String toString() { + return this.value().toString(); + } + + } } diff --git a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/XChainAccountCreateCommit.java b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/XChainAccountCreateCommit.java new file mode 100644 index 000000000..9272ea579 --- /dev/null +++ b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/XChainAccountCreateCommit.java @@ -0,0 +1,81 @@ +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 java.util.Optional; + +/** + * Object mapping for the {@code XChainAccountCreateCommit} transaction. + * + *

This interface will be marked {@link Beta} until the featureXChainBridge amendment is enabled on mainnet. Its API + * is subject to change.

+ */ +@Beta +@Immutable +@JsonSerialize(as = ImmutableXChainAccountCreateCommit.class) +@JsonDeserialize(as = ImmutableXChainAccountCreateCommit.class) +public interface XChainAccountCreateCommit extends Transaction { + + /** + * Construct a {@code XChainAccountCreateCommit} builder. + * + * @return An {@link ImmutableXChainAccountCreateCommit.Builder}. + */ + static ImmutableXChainAccountCreateCommit.Builder builder() { + return ImmutableXChainAccountCreateCommit.builder(); + } + + /** + * Set of {@link TransactionFlags}s for this {@link XChainAccountCreateCommit}, which only allows the + * {@code tfFullyCanonicalSig} flag, which is deprecated. + * + * @return A set of {@link TransactionFlags}, default is {@link TransactionFlags#EMPTY}. + */ + @JsonProperty("Flags") + @Value.Default + default TransactionFlags flags() { + return TransactionFlags.EMPTY; + } + + /** + * The amount, in drops of XRP, to use for account creation. This must be greater than or equal to the + * {@code MinAccountCreateAmount} specified in the {@code Bridge} ledger object. + * + * @return An {@link XrpCurrencyAmount}. + */ + @JsonProperty("Amount") + XrpCurrencyAmount amount(); + + /** + * The destination account on the destination chain. + * + * @return The {@link Address} of the destination account. + */ + @JsonProperty("Destination") + Address destination(); + + /** + * The amount, in XRP, to be used to reward the witness servers for providing signatures. This must match the amount + * on the {@code Bridge} ledger object. + * + * @return An optionally-present {@link XrpCurrencyAmount}. + */ + @JsonProperty("SignatureReward") + Optional signatureReward(); + + /** + * The bridge to create accounts for. + * + * @return An {@link XChainBridge}. + */ + @JsonProperty("XChainBridge") + @SuppressWarnings("MethodName") + XChainBridge xChainBridge(); + +} diff --git a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/XChainAddAccountCreateAttestation.java b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/XChainAddAccountCreateAttestation.java new file mode 100644 index 000000000..cf7be3c94 --- /dev/null +++ b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/XChainAddAccountCreateAttestation.java @@ -0,0 +1,145 @@ +package org.xrpl.xrpl4j.model.transactions; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonFormat.Shape; +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.crypto.keys.PublicKey; +import org.xrpl.xrpl4j.crypto.signing.Signature; +import org.xrpl.xrpl4j.model.flags.TransactionFlags; + +import java.util.Optional; + +/** + * Object mapping for the {@code XChainAddAccountCreateAttestation} transaction. + * + *

This interface will be marked {@link Beta} until the featureXChainBridge amendment is enabled on mainnet. Its API + * is subject to change.

+ */ +@Beta +@Immutable +@JsonSerialize(as = ImmutableXChainAddAccountCreateAttestation.class) +@JsonDeserialize(as = ImmutableXChainAddAccountCreateAttestation.class) +public interface XChainAddAccountCreateAttestation extends Transaction { + + /** + * Construct a {@code XChainAddAccountCreateAttestation} builder. + * + * @return An {@link ImmutableXChainAddAccountCreateAttestation.Builder}. + */ + static ImmutableXChainAddAccountCreateAttestation.Builder builder() { + return ImmutableXChainAddAccountCreateAttestation.builder(); + } + + /** + * Set of {@link TransactionFlags}s for this {@link XChainAddAccountCreateAttestation}, which only allows the + * {@code tfFullyCanonicalSig} flag, which is deprecated. + * + * @return A set of {@link TransactionFlags}, default is {@link TransactionFlags#EMPTY}. + */ + @JsonProperty("Flags") + @Value.Default + default TransactionFlags flags() { + return TransactionFlags.EMPTY; + } + + /** + * The amount committed by the {@code XChainAccountCreateCommit} transaction on the source chain. + * + * @return An {@link XrpCurrencyAmount}. + */ + @JsonProperty("Amount") + XrpCurrencyAmount amount(); + + /** + * The account that should receive this signer's share of the {@link #signatureReward()}. + * + * @return An {@link Address}. + */ + @JsonProperty("AttestationRewardAccount") + Address attestationRewardAccount(); + + /** + * The account on the door account's signer list that is signing the transaction. + * + * @return An {@link Address}. + */ + @JsonProperty("AttestationSignerAccount") + Address attestationSignerAccount(); + + /** + * The destination account for the funds on the destination chain. + * + * @return The {@link Address} of the destination account. + */ + @JsonProperty("Destination") + Address destination(); + + /** + * The account on the source chain that submitted the {@code XChainAccountCreateCommit} transaction that triggered the + * event associated with the attestation. + * + * @return An {@link Address}. + */ + @JsonProperty("OtherChainSource") + Address otherChainSource(); + + /** + * The public key used to verify the signature. + * + * @return A {@link PublicKey}. + */ + @JsonProperty("PublicKey") + PublicKey publicKey(); + + /** + * The signature attesting to the event on the other chain. + * + * @return A {@link Signature}. + */ + @JsonProperty("Signature") + Signature signature(); + + /** + * The signature reward paid in the {@code XChainAccountCreateCommit} transaction. + * + * @return An optionally-present {@link XrpCurrencyAmount}. + */ + @JsonProperty("SignatureReward") + Optional signatureReward(); + + /** + * A boolean representing the chain where the event occurred. + * + *

Note that this field is typed as a {@code boolean} but is represented by an integer (0 or 1) in JSON + * and treated as a UInt8 in XRPL binary format.

+ * + * @return {@code true} if the locking chain was the sender, otherwise {@code false}. + */ + @JsonProperty("WasLockingChainSend") + @JsonFormat(shape = Shape.NUMBER) + boolean wasLockingChainSend(); + + /** + * The counter that represents the order that the claims must be processed in. + * + * @return An {@link XChainCount}. + */ + @JsonProperty("XChainAccountCreateCount") + @SuppressWarnings("MethodName") + XChainCount xChainAccountCreateCount(); + + /** + * The bridge associated with the attestation. + * + * @return An {@link XChainBridge}. + */ + @JsonProperty("XChainBridge") + @SuppressWarnings("MethodName") + XChainBridge xChainBridge(); + +} diff --git a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/XChainAddClaimAttestation.java b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/XChainAddClaimAttestation.java new file mode 100644 index 000000000..68c46c7b4 --- /dev/null +++ b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/XChainAddClaimAttestation.java @@ -0,0 +1,138 @@ +package org.xrpl.xrpl4j.model.transactions; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonFormat.Shape; +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.crypto.keys.PublicKey; +import org.xrpl.xrpl4j.crypto.signing.Signature; +import org.xrpl.xrpl4j.model.flags.TransactionFlags; + +import java.util.Optional; + +/** + * Object mapping for the {@code XChainAddClaimAttestation} transaction. + * + *

This interface will be marked {@link Beta} until the featureXChainBridge amendment is enabled on mainnet. Its API + * is subject to change.

+ */ +@Beta +@Immutable +@JsonSerialize(as = ImmutableXChainAddClaimAttestation.class) +@JsonDeserialize(as = ImmutableXChainAddClaimAttestation.class) +public interface XChainAddClaimAttestation extends Transaction { + + /** + * Construct a {@code XChainAddClaimAttestation} builder. + * + * @return An {@link ImmutableXChainAddClaimAttestation.Builder}. + */ + static ImmutableXChainAddClaimAttestation.Builder builder() { + return ImmutableXChainAddClaimAttestation.builder(); + } + + /** + * Set of {@link TransactionFlags}s for this {@link XChainAddClaimAttestation}, which only allows the + * {@code tfFullyCanonicalSig} flag, which is deprecated. + * + * @return A set of {@link TransactionFlags}, default is {@link TransactionFlags#EMPTY}. + */ + @JsonProperty("Flags") + @Value.Default + default TransactionFlags flags() { + return TransactionFlags.EMPTY; + } + + /** + * The amount committed by the {@code XChainCommit} transaction on the source chain. + * + * @return A {@link CurrencyAmount}. + */ + @JsonProperty("Amount") + CurrencyAmount amount(); + + /** + * The account that should receive this signer's share of the {@code SignatureReward}. + * + * @return An {@link Address}. + */ + @JsonProperty("AttestationRewardAccount") + Address attestationRewardAccount(); + + /** + * The account on the door account's signer list that is signing the transaction. + * + * @return An {@link Address}. + */ + @JsonProperty("AttestationSignerAccount") + Address attestationSignerAccount(); + + /** + * The destination account for the funds on the destination chain (taken from the {@code XChainCommit} transaction). + * + * @return An {@link Address}. + */ + @JsonProperty("Destination") + Optional
destination(); + + /** + * The account on the source chain that submitted the {@code XChainCommit} transaction that triggered the event + * associated with the attestation. + * + * @return An {@link Address}. + */ + @JsonProperty("OtherChainSource") + Address otherChainSource(); + + /** + * The public key used to verify the attestation signature. + * + * @return A {@link PublicKey}. + */ + @JsonProperty("PublicKey") + PublicKey publicKey(); + + /** + * The signature attesting to the event on the other chain. + * + * @return A {@link Signature}. + */ + @JsonProperty("Signature") + Signature signature(); + + /** + * A boolean representing the chain where the event occurred. + * + *

Note that this field is typed as a {@code boolean} but is represented by an integer (0 or 1) in JSON + * and treated as a UInt8 in XRPL binary format.

+ * + * @return {@code true} if the locking chain was the sender, otherwise {@code false}. + */ + @JsonProperty("WasLockingChainSend") + @JsonFormat(shape = Shape.NUMBER) + boolean wasLockingChainSend(); + + /** + * The bridge to use to transfer funds. + * + * @return An {@link XChainBridge}. + */ + @JsonProperty("XChainBridge") + @SuppressWarnings("MethodName") + XChainBridge xChainBridge(); + + /** + * The {@link XChainClaimId} associated with the transfer, which was included in the {@code XChainCommit} + * transaction. + * + * @return An {@link XChainClaimId}. + */ + @JsonProperty("XChainClaimID") + @SuppressWarnings("MethodName") + XChainClaimId xChainClaimId(); + +} diff --git a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/XChainBridge.java b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/XChainBridge.java new file mode 100644 index 000000000..614af52f8 --- /dev/null +++ b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/XChainBridge.java @@ -0,0 +1,65 @@ +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.Immutable; +import org.xrpl.xrpl4j.model.ledger.Issue; + +/** + * Represents a cross-chain bridge. + * + *

This interface will be marked {@link Beta} until the featureXChainBridge amendment is enabled on mainnet. Its API + * is subject to change.

+ */ +@Beta +@Immutable +@JsonSerialize(as = ImmutableXChainBridge.class) +@JsonDeserialize(as = ImmutableXChainBridge.class) +public interface XChainBridge { + + /** + * Construct a {@code XChainBridge} builder. + * + * @return An {@link ImmutableXChainBridge.Builder}. + */ + static ImmutableXChainBridge.Builder builder() { + return ImmutableXChainBridge.builder(); + } + + /** + * The door account on the issuing chain. For an XRP-XRP bridge, this must be the genesis account (the account that is + * created when the network is first started, which contains all of the XRP). + * + * @return The {@link Address} of the door account. + */ + @JsonProperty("IssuingChainDoor") + Address issuingChainDoor(); + + /** + * The asset that is minted and burned on the issuing chain. For an IOU-IOU bridge, the issuer of the asset must be + * the door account on the issuing chain, to avoid supply issues. + * + * @return An {@link Issue}. + */ + @JsonProperty("IssuingChainIssue") + Issue issuingChainIssue(); + + /** + * The door account on the locking chain. + * + * @return The {@link Address} of the door account. + */ + @JsonProperty("LockingChainDoor") + Address lockingChainDoor(); + + /** + * The asset that is locked and unlocked on the locking chain. + * + * @return An {@link Issue}. + */ + @JsonProperty("LockingChainIssue") + Issue lockingChainIssue(); + +} diff --git a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/XChainClaim.java b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/XChainClaim.java new file mode 100644 index 000000000..8aa1d2519 --- /dev/null +++ b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/XChainClaim.java @@ -0,0 +1,93 @@ +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.primitives.UnsignedInteger; +import org.immutables.value.Value; +import org.immutables.value.Value.Immutable; +import org.xrpl.xrpl4j.model.flags.TransactionFlags; + +import java.util.Optional; + +/** + * Object mapping for the {@code XChainClaim} transaction. + * + *

This interface will be marked {@link Beta} until the featureXChainBridge amendment is enabled on mainnet. Its API + * is subject to change.

+ */ +@Beta +@Immutable +@JsonSerialize(as = ImmutableXChainClaim.class) +@JsonDeserialize(as = ImmutableXChainClaim.class) +public interface XChainClaim extends Transaction { + + /** + * Construct a {@code XChainClaim} builder. + * + * @return An {@link ImmutableXChainClaim.Builder}. + */ + static ImmutableXChainClaim.Builder builder() { + return ImmutableXChainClaim.builder(); + } + + /** + * Set of {@link TransactionFlags}s for this {@link XChainClaim}, which only allows the {@code tfFullyCanonicalSig} + * flag, which is deprecated. + * + * @return A set of {@link TransactionFlags}, default is {@link TransactionFlags#EMPTY}. + */ + @JsonProperty("Flags") + @Value.Default + default TransactionFlags flags() { + return TransactionFlags.EMPTY; + } + + /** + * The amount to claim on the destination chain. This must match the amount attested to on the attestations associated + * with {@link #xChainClaimId()}. + * + * @return A {@link CurrencyAmount}. + */ + @JsonProperty("Amount") + CurrencyAmount amount(); + + /** + * The destination account on the destination chain. It must exist or the transaction will fail. However, if the + * transaction fails in this case, the sequence number and collected signatures won't be destroyed, and the + * transaction can be rerun with a different destination. + * + * @return An {@link Address}. + */ + @JsonProperty("Destination") + Address destination(); + + /** + * A 32-bit unsigned integer destination tag. + * + * @return An optional {@link UnsignedInteger} + */ + @JsonProperty("DestinationTag") + Optional destinationTag(); + + /** + * The bridge to use to transfer funds. + * + * @return An {@link XChainBridge}. + */ + @JsonProperty("XChainBridge") + @SuppressWarnings("MethodName") + XChainBridge xChainBridge(); + + /** + * The unique integer ID for the cross-chain transfer that was referenced in the corresponding {@code XChainCommit} + * transaction. + * + * @return An {@link XChainClaimId}. + */ + @JsonProperty("XChainClaimID") + @SuppressWarnings("MethodName") + XChainClaimId xChainClaimId(); + +} diff --git a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/XChainCommit.java b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/XChainCommit.java new file mode 100644 index 000000000..41633ef41 --- /dev/null +++ b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/XChainCommit.java @@ -0,0 +1,86 @@ +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.primitives.UnsignedInteger; +import org.immutables.value.Value; +import org.immutables.value.Value.Immutable; +import org.xrpl.xrpl4j.model.flags.TransactionFlags; + +import java.util.Optional; + +/** + * Object mapping for the {@code XChainCommit} transaction. + * + *

This interface will be marked {@link Beta} until the featureXChainBridge amendment is enabled on mainnet. Its API + * is subject to change.

+ */ +@Beta +@Immutable +@JsonSerialize(as = ImmutableXChainCommit.class) +@JsonDeserialize(as = ImmutableXChainCommit.class) +public interface XChainCommit extends Transaction { + + /** + * Construct a {@code XChainCommit} builder. + * + * @return An {@link ImmutableXChainCommit.Builder}. + */ + static ImmutableXChainCommit.Builder builder() { + return ImmutableXChainCommit.builder(); + } + + /** + * Set of {@link TransactionFlags}s for this {@link XChainCommit}, which only allows the {@code tfFullyCanonicalSig} + * flag, which is deprecated. + * + * @return A set of {@link TransactionFlags}, default is {@link TransactionFlags#EMPTY}. + */ + @JsonProperty("Flags") + @Value.Default + default TransactionFlags flags() { + return TransactionFlags.EMPTY; + } + + /** + * The asset to commit, and the quantity. This must match the door account's {@code LockingChainIssue} (if on the + * locking chain) or the door account's {@code IssuingChainIssue} (if on the issuing chain). + * + * @return A {@link CurrencyAmount}. + */ + @JsonProperty("Amount") + CurrencyAmount amount(); + + /** + * The destination account on the destination chain. If this is not specified, the account that submitted the + * {@link org.xrpl.xrpl4j.model.transactions.XChainCreateClaimId} transaction on the destination chain will need to + * submit a {@link XChainClaim} transaction to claim the funds. + * + * @return An optionally-present {@link Address}. + */ + @JsonProperty("OtherChainDestination") + Optional
otherChainDestination(); + + /** + * The bridge to use to transfer funds. + * + * @return An {@link XChainBridge}. + */ + @JsonProperty("XChainBridge") + @SuppressWarnings("MethodName") + XChainBridge xChainBridge(); + + /** + * The unique integer ID for a cross-chain transfer. This must be acquired on the destination chain (via a + * {@link org.xrpl.xrpl4j.model.transactions.XChainCreateClaimId} transaction) and checked from a validated ledger + * before submitting this transaction. If an incorrect sequence number is specified, the funds will be lost. + * + * @return An {@link XChainClaimId}. + */ + @JsonProperty("XChainClaimID") + @SuppressWarnings("MethodName") + XChainClaimId xChainClaimId(); + +} diff --git a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/XChainCreateBridge.java b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/XChainCreateBridge.java new file mode 100644 index 000000000..744331b14 --- /dev/null +++ b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/XChainCreateBridge.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.immutables.value.Value.Immutable; +import org.xrpl.xrpl4j.model.flags.TransactionFlags; + +import java.util.Optional; + +/** + * Object mapping for the {@code XChainCreateBridge} transaction. + * + *

This interface will be marked {@link Beta} until the featureXChainBridge amendment is enabled on mainnet. Its API + * is subject to change.

+ */ +@Beta +@Immutable +@JsonSerialize(as = ImmutableXChainCreateBridge.class) +@JsonDeserialize(as = ImmutableXChainCreateBridge.class) +public interface XChainCreateBridge extends Transaction { + + /** + * Construct a {@code XChainCreateBridge} builder. + * + * @return An {@link ImmutableXChainCreateBridge.Builder}. + */ + static ImmutableXChainCreateBridge.Builder builder() { + return ImmutableXChainCreateBridge.builder(); + } + + /** + * Set of {@link TransactionFlags}s for this {@link XChainCreateBridge}, which only allows the + * {@code tfFullyCanonicalSig} flag, which is deprecated. + * + * @return A set of {@link TransactionFlags}, default is {@link TransactionFlags#EMPTY}. + */ + @JsonProperty("Flags") + @Value.Default + default TransactionFlags flags() { + return TransactionFlags.EMPTY; + } + + /** + * The minimum amount, in XRP, required for a {@link XChainAccountCreateCommit} transaction. If this isn't present, + * the {@link XChainAccountCreateCommit} transaction will fail. This field can only be present on XRP-XRP bridges. + * + * @return An optionally-present {@link XrpCurrencyAmount}. + */ + @JsonProperty("MinAccountCreateAmount") + Optional minAccountCreateAmount(); + + /** + * The total amount to pay the witness servers for their signatures. This amount will be split among the signers. + * + * @return An {@link XrpCurrencyAmount}. + */ + @JsonProperty("SignatureReward") + XrpCurrencyAmount signatureReward(); + + /** + * The bridge (door accounts and assets) to create. + * + * @return An {@link XChainBridge}. + */ + @JsonProperty("XChainBridge") + @SuppressWarnings("MethodName") + XChainBridge xChainBridge(); + +} diff --git a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/XChainCreateClaimId.java b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/XChainCreateClaimId.java new file mode 100644 index 000000000..1e4525b36 --- /dev/null +++ b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/XChainCreateClaimId.java @@ -0,0 +1,70 @@ +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; + +/** + * Object mapping for the {@code XChainCreateClaimId} transaction. + * + *

This interface will be marked {@link Beta} until the featureXChainBridge amendment is enabled on mainnet. Its API + * is subject to change.

+ */ +@Beta +@Immutable +@JsonSerialize(as = ImmutableXChainCreateClaimId.class) +@JsonDeserialize(as = ImmutableXChainCreateClaimId.class) +public interface XChainCreateClaimId extends Transaction { + + /** + * Construct a {@code XChainCreateClaimId} builder. + * + * @return An {@link ImmutableXChainCreateClaimId.Builder}. + */ + static ImmutableXChainCreateClaimId.Builder builder() { + return ImmutableXChainCreateClaimId.builder(); + } + + /** + * Set of {@link TransactionFlags}s for this {@link XChainCreateClaimId}, which only allows the + * {@code tfFullyCanonicalSig} flag, which is deprecated. + * + * @return A set of {@link TransactionFlags}, default is {@link TransactionFlags#EMPTY}. + */ + @JsonProperty("Flags") + @Value.Default + default TransactionFlags flags() { + return TransactionFlags.EMPTY; + } + + /** + * The account that must send the {@link XChainCommit} transaction on the source chain. + * + * @return An {@link Address}. + */ + @JsonProperty("OtherChainSource") + Address otherChainSource(); + + /** + * The amount, in XRP, to reward the witness servers for providing signatures. This must match the amount on the + * {@code Bridge} ledger object. + * + * @return An {@link XrpCurrencyAmount}. + */ + @JsonProperty("SignatureReward") + XrpCurrencyAmount signatureReward(); + + /** + * The bridge to create the claim ID for. + * + * @return An {@link XChainBridge}. + */ + @JsonProperty("XChainBridge") + @SuppressWarnings("MethodName") + XChainBridge xChainBridge(); + +} diff --git a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/XChainModifyBridge.java b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/XChainModifyBridge.java new file mode 100644 index 000000000..8df520a1f --- /dev/null +++ b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/XChainModifyBridge.java @@ -0,0 +1,76 @@ +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.PaymentFlags; +import org.xrpl.xrpl4j.model.flags.TransactionFlags; +import org.xrpl.xrpl4j.model.flags.XChainModifyBridgeFlags; + +import java.util.Optional; + +/** + * Object mapping for the {@code XChainModifyBridge} transaction. + * + *

This interface will be marked {@link Beta} until the featureXChainBridge amendment is enabled on mainnet. Its API + * is subject to change.

+ */ +@Beta +@Immutable +@JsonSerialize(as = ImmutableXChainModifyBridge.class) +@JsonDeserialize(as = ImmutableXChainModifyBridge.class) +public interface XChainModifyBridge extends Transaction { + + /** + * Construct a {@code XChainModifyBridge} builder. + * + * @return An {@link ImmutableXChainModifyBridge.Builder}. + */ + static ImmutableXChainModifyBridge.Builder builder() { + return ImmutableXChainModifyBridge.builder(); + } + + /** + * Set of {@link XChainModifyBridgeFlags}s for this {@link XChainModifyBridge}, which have been properly combined to + * yield a {@link XChainModifyBridgeFlags} object containing the {@link Long} representation of the set bits. + * + *

The value of the flags can either be set manually, or constructed using {@link XChainModifyBridgeFlags.Builder}. + * + * @return The {@link XChainModifyBridgeFlags} for this transaction. + */ + @JsonProperty("Flags") + @Value.Default + default XChainModifyBridgeFlags flags() { + return XChainModifyBridgeFlags.empty(); + } + + /** + * The minimum amount, in XRP, required for a {@link XChainAccountCreateCommit} transaction. If this isn't present, + * the {@link XChainAccountCreateCommit} transaction will fail. This field can only be present on XRP-XRP bridges. + * + * @return An optionally-present {@link XrpCurrencyAmount}. + */ + @JsonProperty("MinAccountCreateAmount") + Optional minAccountCreateAmount(); + + /** + * The signature reward split between the witnesses for submitting attestations. + * + * @return An optionally-present {@link XrpCurrencyAmount}. + */ + @JsonProperty("SignatureReward") + Optional signatureReward(); + + /** + * The bridge to create the claim ID for. + * + * @return An {@link XChainBridge}. + */ + @JsonProperty("XChainBridge") + @SuppressWarnings("MethodName") + XChainBridge xChainBridge(); + +} diff --git a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/metadata/MetaBridgeObject.java b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/metadata/MetaBridgeObject.java new file mode 100644 index 000000000..dca99e082 --- /dev/null +++ b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/metadata/MetaBridgeObject.java @@ -0,0 +1,145 @@ +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 com.google.common.primitives.UnsignedInteger; +import org.immutables.value.Value; +import org.immutables.value.Value.Immutable; +import org.xrpl.xrpl4j.model.flags.Flags; +import org.xrpl.xrpl4j.model.ledger.ImmutableBridgeObject; +import org.xrpl.xrpl4j.model.ledger.LedgerObject; +import org.xrpl.xrpl4j.model.transactions.Address; +import org.xrpl.xrpl4j.model.transactions.Hash256; +import org.xrpl.xrpl4j.model.transactions.XChainAccountCreateCommit; +import org.xrpl.xrpl4j.model.transactions.XChainBridge; +import org.xrpl.xrpl4j.model.transactions.XChainClaimId; +import org.xrpl.xrpl4j.model.transactions.XChainCount; +import org.xrpl.xrpl4j.model.transactions.XrpCurrencyAmount; + +import java.util.Optional; + +/** + * Represents a single cross-chain bridge that connects the XRP Ledger with another blockchain, such as its sidechain, + * and enables value in the form of XRP and other tokens (IOUs) to move efficiently between the two blockchains. + * + *

This interface will be marked {@link Beta} until the featureXChainBridge amendment is enabled on mainnet. Its API + * is subject to change.

+ */ +@Beta +@Immutable +@JsonSerialize(as = ImmutableMetaBridgeObject.class) +@JsonDeserialize(as = ImmutableMetaBridgeObject.class) +public interface MetaBridgeObject extends MetaLedgerObject { + + /** + * A bit-map of boolean flags. No flags are defined for {@link MetaBridgeObject}, so this value is always 0. + * + * @return Always {@link Flags#UNSET}. + */ + @JsonProperty("Flags") + @Value.Derived + default Flags flags() { + return Flags.UNSET; + } + + /** + * The account that owns this object. + * + * @return An {@link Address}. + */ + @JsonProperty("Account") + Optional
account(); + + /** + * The minimum amount, in XRP, required for an XChainAccountCreateCommit transaction. If this isn't present, the + * {@link org.xrpl.xrpl4j.model.transactions.XChainAccountCreateCommit} transaction will fail. This field can only be + * present on XRP-XRP bridges. + * + * @return An optionally-present {@link XrpCurrencyAmount}. + */ + @JsonProperty("MinAccountCreateAmount") + Optional minAccountCreateAmount(); + + /** + * The total amount, in XRP, to be rewarded for providing a signature for cross-chain transfer or for signing for the + * cross-chain reward. This amount will be split among the signers. + * + * @return An {@link XrpCurrencyAmount}. + */ + @JsonProperty("SignatureReward") + Optional signatureReward(); + + /** + * The door accounts and assets of the bridge this object correlates to. + * + * @return An {@link XChainBridge}. + */ + @JsonProperty("XChainBridge") + @SuppressWarnings("MethodName") + Optional xChainBridge(); + + /** + * The value of the next XChainClaimID to be created. + * + * @return An {@link XChainClaimId}. + */ + @JsonProperty("XChainClaimID") + @SuppressWarnings("MethodName") + Optional xChainClaimId(); + + /** + * A counter used to order the execution of account create transactions. It is incremented every time a successful + * {@link org.xrpl.xrpl4j.model.transactions.XChainAccountCreateCommit} transaction is run for the source chain. + * + * @return An {@link XChainCount}. + */ + @JsonProperty("XChainAccountCreateCount") + @SuppressWarnings("MethodName") + Optional xChainAccountCreateCount(); + + /** + * A counter used to order the execution of account create transactions. It is incremented every time a + * {@link org.xrpl.xrpl4j.model.transactions.XChainAccountCreateCommit} transaction is "claimed" on the destination + * chain. When the "claim" transaction is run on the destination chain, the {@link #xChainAccountClaimCount()} must + * match the value that the {@link #xChainAccountCreateCount()} had at the time the {@link #xChainAccountClaimCount()} + * was run on the source chain. This orders the claims so that they run in the same order that the + * {@link org.xrpl.xrpl4j.model.transactions.XChainAccountCreateCommit} transactions ran on the source chain, to + * prevent transaction replay. + * + * @return An {@link XChainCount}. + */ + @JsonProperty("XChainAccountClaimCount") + @SuppressWarnings("MethodName") + Optional xChainAccountClaimCount(); + + /** + * 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") + Optional ownerNode(); + + /** + * The identifying hash of the transaction that most recently modified this object. + * + * @return A {@link Hash256} containing the previous transaction hash. + */ + @JsonProperty("PreviousTxnID") + Optional previousTransactionId(); + + /** + * The index of the ledger that contains the transaction that most recently modified this object. + * + * @return An {@link UnsignedInteger} representing the previous transaction ledger sequence. + */ + @JsonProperty("PreviousTxnLgrSeq") + Optional previousTransactionLedgerSequence(); + +} 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 f1f07f9fd..8c1a24936 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 @@ -1,8 +1,11 @@ package org.xrpl.xrpl4j.model.transactions.metadata; +import com.fasterxml.jackson.annotation.JsonIgnore; 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.Derived; @Value.Immutable @JsonSerialize(as = ImmutableMetaLedgerEntryType.class) @@ -27,10 +30,22 @@ public interface MetaLedgerEntryType { MetaLedgerEntryType NFTOKEN_PAGE = MetaLedgerEntryType.of("NFTokenPage"); MetaLedgerEntryType AMM = MetaLedgerEntryType.of("AMM"); + @Beta + MetaLedgerEntryType BRIDGE = MetaLedgerEntryType.of("Bridge"); + + @Beta + MetaLedgerEntryType XCHAIN_OWNED_CREATE_ACCOUNT_CLAIM_ID = MetaLedgerEntryType.of( + "XChainOwnedCreateAccountClaimID" + ); + + @Beta + MetaLedgerEntryType XCHAIN_OWNED_CLAIM_ID = MetaLedgerEntryType.of("XChainOwnedClaimID"); + /** * Construct a new {@link MetaLedgerEntryType} from a {@link String}. * * @param value The {@link String} value. + * * @return A {@link MetaLedgerEntryType} wrapping the supplied value. */ static MetaLedgerEntryType of(String value) { @@ -39,6 +54,50 @@ static MetaLedgerEntryType of(String value) { .build(); } + /** + * Get the {@link MetaLedgerObject} concrete type associated with this {@link MetaLedgerEntryType}. + * + * @return A {@link Class} of {@link MetaLedgerObject}. + */ + @Derived + @JsonIgnore + default Class ledgerObjectType() { + switch (this.value()) { + case "AccountRoot": + return MetaAccountRootObject.class; + case "Check": + return MetaCheckObject.class; + case "DepositPreauth": + return MetaDepositPreAuthObject.class; + case "Escrow": + return MetaEscrowObject.class; + case "NFTokenOffer": + return MetaNfTokenOfferObject.class; + case "Offer": + return MetaOfferObject.class; + case "PayChannel": + return MetaPayChannelObject.class; + case "RippleState": + return MetaRippleStateObject.class; + case "SignerList": + return MetaSignerListObject.class; + case "Ticket": + return MetaTicketObject.class; + case "NFTokenPage": + return MetaNfTokenPageObject.class; + case "AMM": + return MetaAmmObject.class; + case "Bridge": + return MetaBridgeObject.class; + case "XChainOwnedClaimID": + return MetaXChainOwnedClaimIdObject.class; + case "XChainOwnedCreateAccountClaimID": + return MetaXChainOwnedCreateAccountClaimIdObject.class; + default: + return MetaUnknownObject.class; + } + } + String value(); } diff --git a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/metadata/MetaXChainClaimAttestation.java b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/metadata/MetaXChainClaimAttestation.java new file mode 100644 index 000000000..a2852d1bc --- /dev/null +++ b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/metadata/MetaXChainClaimAttestation.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.Immutable; +import org.xrpl.xrpl4j.model.ledger.ImmutableXChainClaimAttestation; + +/** + * A wrapper around {@link MetaXChainClaimProofSig}s. + * + *

This class will be marked {@link Beta} until the featureXChainBridge amendment is enabled on mainnet. Its API is + * subject to change.

+ */ +@Beta +@Immutable +@JsonSerialize(as = ImmutableMetaXChainClaimAttestation.class) +@JsonDeserialize(as = ImmutableMetaXChainClaimAttestation.class) +public interface MetaXChainClaimAttestation { + + /** + * An {@link MetaXChainClaimProofSig}. + * + * @return An {@link MetaXChainClaimProofSig}. + */ + @JsonProperty("XChainClaimProofSig") + @SuppressWarnings("MethodName") + MetaXChainClaimProofSig xChainClaimProofSig(); + +} diff --git a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/metadata/MetaXChainClaimProofSig.java b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/metadata/MetaXChainClaimProofSig.java new file mode 100644 index 000000000..28d6bf2c6 --- /dev/null +++ b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/metadata/MetaXChainClaimProofSig.java @@ -0,0 +1,79 @@ +package org.xrpl.xrpl4j.model.transactions.metadata; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonFormat.Shape; +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.Immutable; +import org.xrpl.xrpl4j.crypto.keys.PublicKey; +import org.xrpl.xrpl4j.model.ledger.ImmutableXChainClaimProofSig; +import org.xrpl.xrpl4j.model.transactions.Address; +import org.xrpl.xrpl4j.model.transactions.CurrencyAmount; +import org.xrpl.xrpl4j.model.transactions.XChainCommit; + +import java.util.Optional; + +/** + * Represents an attestation for an {@link MetaXChainOwnedClaimIdObject}. + * + *

This class will be marked {@link Beta} until the featureXChainBridge amendment is enabled on mainnet. Its API is + * subject to change.

+ */ +@Immutable +@JsonSerialize(as = ImmutableMetaXChainClaimProofSig.class) +@JsonDeserialize(as = ImmutableMetaXChainClaimProofSig.class) +public interface MetaXChainClaimProofSig { + + /** + * The account on the door account's signer list that is signing the transaction. + * + * @return An {@link Address}. + */ + @JsonProperty("AttestationSignerAccount") + Optional
attestationSignerAccount(); + + /** + * The public key used to verify the signature. + * + * @return A {@link PublicKey}. + */ + @JsonProperty("PublicKey") + Optional publicKey(); + + /** + * The amount to claim in the {@link org.xrpl.xrpl4j.model.transactions.XChainCommit} transaction on the destination + * chain. + * + * @return A {@link CurrencyAmount}. + */ + @JsonProperty("Amount") + Optional amount(); + + /** + * The account that should receive this signer's share of the SignatureReward. + * + * @return An {@link Address}. + */ + @JsonProperty("AttestationRewardAccount") + Optional
attestationRewardAccount(); + + /** + * A boolean representing the chain where the event occurred. + * + * @return {@code true} if the event occurred on the locking chain, otherwise {@code false}. + */ + @JsonProperty("WasLockingChainSend") + @JsonFormat(shape = Shape.NUMBER) + Optional wasLockingChainSend(); + + /** + * The destination account for the funds on the destination chain. + * + * @return An {@link Address}. + */ + @JsonProperty("Destination") + Optional
destination(); + +} diff --git a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/metadata/MetaXChainCreateAccountAttestation.java b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/metadata/MetaXChainCreateAccountAttestation.java new file mode 100644 index 000000000..629a2877f --- /dev/null +++ b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/metadata/MetaXChainCreateAccountAttestation.java @@ -0,0 +1,30 @@ +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.Immutable; + +/** + * A wrapper around {@link MetaXChainCreateAccountProofSig}s. + * + *

This class will be marked {@link Beta} until the AMM amendment is enabled on mainnet. Its API is subject to + * change.

+ */ +@Beta +@Immutable +@JsonSerialize(as = ImmutableMetaXChainCreateAccountAttestation.class) +@JsonDeserialize(as = ImmutableMetaXChainCreateAccountAttestation.class) +public interface MetaXChainCreateAccountAttestation { + + /** + * An {@link MetaXChainCreateAccountProofSig}. + * + * @return An {@link MetaXChainCreateAccountProofSig}. + */ + @JsonProperty("XChainCreateAccountProofSig") + @SuppressWarnings("MethodName") + MetaXChainCreateAccountProofSig xChainCreateAccountProofSig(); + +} diff --git a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/metadata/MetaXChainCreateAccountProofSig.java b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/metadata/MetaXChainCreateAccountProofSig.java new file mode 100644 index 000000000..1b3b09fd9 --- /dev/null +++ b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/metadata/MetaXChainCreateAccountProofSig.java @@ -0,0 +1,90 @@ +package org.xrpl.xrpl4j.model.transactions.metadata; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonFormat.Shape; +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.Immutable; +import org.xrpl.xrpl4j.crypto.keys.PublicKey; +import org.xrpl.xrpl4j.model.ledger.ImmutableXChainCreateAccountProofSig; +import org.xrpl.xrpl4j.model.transactions.Address; +import org.xrpl.xrpl4j.model.transactions.CurrencyAmount; +import org.xrpl.xrpl4j.model.transactions.XChainAccountCreateCommit; +import org.xrpl.xrpl4j.model.transactions.XrpCurrencyAmount; + +import java.util.Optional; + +/** + * Represents an attestation for an {@link MetaXChainOwnedCreateAccountClaimIdObject}. + * + *

This interface will be marked {@link Beta} until the featureXChainBridge amendment is enabled on mainnet. Its API + * is subject to change.

+ */ +@Beta +@Immutable +@JsonSerialize(as = ImmutableMetaXChainCreateAccountProofSig.class) +@JsonDeserialize(as = ImmutableMetaXChainCreateAccountProofSig.class) +public interface MetaXChainCreateAccountProofSig { + + /** + * The amount committed by the {@link org.xrpl.xrpl4j.model.transactions.XChainAccountCreateCommit} transaction on the + * source chain. + * + * @return A {@link CurrencyAmount}. + */ + @JsonProperty("Amount") + Optional amount(); + + /** + * The total amount, in XRP, to be rewarded for providing a signature for cross-chain transfer or for signing for the + * cross-chain reward. This amount will be split among the signers. + * + * @return An {@link XrpCurrencyAmount}. + */ + @JsonProperty("SignatureReward") + Optional signatureReward(); + + /** + * The account that should receive this signer's share of the {@link #signatureReward()}. + * + * @return An {@link Address}. + */ + @JsonProperty("AttestationRewardAccount") + Optional
attestationRewardAccount(); + + /** + * The account on the door account's signer list that is signing the transaction. + * + * @return An {@link Address}. + */ + @JsonProperty("AttestationSignerAccount") + Optional
attestationSignerAccount(); + + /** + * The destination account for the funds on the destination chain. + * + * @return An {@link Address}. + */ + @JsonProperty("Destination") + Optional
destination(); + + /** + * The public key used to verify the signature. + * + * @return A {@link PublicKey}. + */ + @JsonProperty("PublicKey") + Optional publicKey(); + + /** + * A boolean representing the chain where the event occurred. + * + * @return {@code true} if the event occurred on the locking chain, otherwise {@code false}. + */ + @JsonProperty("WasLockingChainSend") + @JsonFormat(shape = Shape.NUMBER) + Optional wasLockingChainSend(); + +} diff --git a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/metadata/MetaXChainOwnedClaimIdObject.java b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/metadata/MetaXChainOwnedClaimIdObject.java new file mode 100644 index 000000000..53914e68b --- /dev/null +++ b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/metadata/MetaXChainOwnedClaimIdObject.java @@ -0,0 +1,142 @@ +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 com.google.common.primitives.UnsignedInteger; +import org.immutables.value.Value; +import org.immutables.value.Value.Immutable; +import org.xrpl.xrpl4j.model.flags.Flags; +import org.xrpl.xrpl4j.model.ledger.ImmutableXChainOwnedClaimIdObject; +import org.xrpl.xrpl4j.model.ledger.LedgerObject; +import org.xrpl.xrpl4j.model.transactions.Address; +import org.xrpl.xrpl4j.model.transactions.Hash256; +import org.xrpl.xrpl4j.model.transactions.XChainBridge; +import org.xrpl.xrpl4j.model.transactions.XChainClaimId; +import org.xrpl.xrpl4j.model.transactions.XChainCommit; +import org.xrpl.xrpl4j.model.transactions.XChainCreateClaimId; +import org.xrpl.xrpl4j.model.transactions.XrpCurrencyAmount; + +import java.util.List; +import java.util.Optional; + +/** + * An {@code XChainOwnedClaimID} object represents one cross-chain transfer of value and includes information of the + * account on the source chain that locks or burns the funds on the source chain. + * + *

The {@code XChainOwnedClaimID} object must be acquired on the destination chain before submitting a + * {@link XChainCommit} on the source chain. Its purpose is to prevent transaction replay attacks and is also used as a + * place to collect attestations from witness servers.

+ * + *

An {@link XChainCreateClaimId} transaction is used to create a new {@code XChainOwnedClaimID}. The ledger object + * is destroyed when the funds are successfully claimed on the destination chain.

+ * + *

This interface will be marked {@link Beta} until the featureXChainBridge amendment is enabled on mainnet. Its API + * is subject to change.

+ */ +@Beta +@Immutable +@JsonSerialize(as = ImmutableMetaXChainOwnedClaimIdObject.class) +@JsonDeserialize(as = ImmutableMetaXChainOwnedClaimIdObject.class) +public interface MetaXChainOwnedClaimIdObject extends MetaLedgerObject { + + /** + * A bit-map of boolean flags. No flags are defined for {@link MetaXChainOwnedClaimIdObject}, so this value is always + * 0. + * + * @return Always {@link Flags#UNSET}. + */ + @JsonProperty("Flags") + @Value.Derived + default Flags flags() { + return Flags.UNSET; + } + + /** + * The account that owns this object. + * + * @return An {@link Address}. + */ + @JsonProperty("Account") + Optional
account(); + + /** + * The door accounts and assets of the bridge this object correlates to. + * + * @return An {@link XChainBridge}. + */ + @JsonProperty("XChainBridge") + @SuppressWarnings("MethodName") + Optional xChainBridge(); + + /** + * The unique sequence number for a cross-chain transfer. + * + * @return An {@link XChainClaimId}. + */ + @JsonProperty("XChainClaimID") + @SuppressWarnings("MethodName") + Optional xChainClaimId(); + + /** + * The account that must send the corresponding {@link org.xrpl.xrpl4j.model.transactions.XChainCommit} on the source + * chain. The destination may be specified in the {@link org.xrpl.xrpl4j.model.transactions.XChainCommit} transaction, + * which means that if the OtherChainSource isn't specified, another account can try to specify a different + * destination and steal the funds. This also allows tracking only a single set of signatures, since we know which + * account will send the {@link org.xrpl.xrpl4j.model.transactions.XChainCommit} transaction. + * + * @return An {@link Address}. + */ + @JsonProperty("OtherChainSource") + Optional
otherChainSource(); + + /** + * Attestations collected from the witness servers. This includes the parameters needed to recreate the message that + * was signed, including the amount, which chain (locking or issuing), optional destination, and reward account for + * that signature. + * + * @return A {@link List} of {@link MetaXChainClaimAttestation}s. + */ + @JsonProperty("XChainClaimAttestations") + @SuppressWarnings("MethodName") + List xChainClaimAttestations(); + + /** + * The total amount to pay the witness servers for their signatures. It must be at least the value of SignatureReward + * in the Bridge ledger object. + * + * @return An {@link XrpCurrencyAmount}. + */ + @JsonProperty("SignatureReward") + Optional signatureReward(); + + /** + * 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") + Optional ownerNode(); + + /** + * The identifying hash of the transaction that most recently modified this object. + * + * @return A {@link Hash256} containing the previous transaction hash. + */ + @JsonProperty("PreviousTxnID") + Optional previousTransactionId(); + + /** + * The index of the ledger that contains the transaction that most recently modified this object. + * + * @return An {@link UnsignedInteger} representing the previous transaction ledger sequence. + */ + @JsonProperty("PreviousTxnLgrSeq") + Optional previousTransactionLedgerSequence(); + +} diff --git a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/metadata/MetaXChainOwnedCreateAccountClaimIdObject.java b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/metadata/MetaXChainOwnedCreateAccountClaimIdObject.java new file mode 100644 index 000000000..fed7e17d2 --- /dev/null +++ b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/metadata/MetaXChainOwnedCreateAccountClaimIdObject.java @@ -0,0 +1,117 @@ +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 com.google.common.primitives.UnsignedInteger; +import org.immutables.value.Value; +import org.immutables.value.Value.Immutable; +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.XChainBridge; +import org.xrpl.xrpl4j.model.transactions.XChainCount; + +import java.util.List; +import java.util.Optional; + +/** + * The {@code XChainOwnedCreateAccountClaimID} ledger object is used to collect attestations for creating an account via + * a cross-chain transfer. + * + *

It is created when an XChainAddAccountCreateAttestation transaction adds a signature attesting to a + * XChainAccountCreateCommit transaction and the XChainAccountCreateCount is greater than or equal to the current + * XChainAccountClaimCount on the Bridge ledger object.

+ * + *

The ledger object is destroyed when all the attestations have been received and the funds have transferred to the + * new account.

+ * + *

This interface will be marked {@link Beta} until the featureXChainBridge amendment is enabled on mainnet. Its API + * is subject to change.

+ */ +@Beta +@Immutable +@JsonSerialize(as = ImmutableMetaXChainOwnedCreateAccountClaimIdObject.class) +@JsonDeserialize(as = ImmutableMetaXChainOwnedCreateAccountClaimIdObject.class) +public interface MetaXChainOwnedCreateAccountClaimIdObject extends MetaLedgerObject { + + /** + * A bit-map of boolean flags. No flags are defined for {@link MetaXChainOwnedCreateAccountClaimIdObject}, so this + * value is always 0. + * + * @return Always {@link Flags#UNSET}. + */ + @JsonProperty("Flags") + @Value.Derived + default Flags flags() { + return Flags.UNSET; + } + + /** + * The account that owns this object. + * + * @return An {@link Address}. + */ + @JsonProperty("Account") + Optional
account(); + + /** + * The door accounts and assets of the bridge this object correlates to. + * + * @return An {@link XChainBridge}. + */ + @JsonProperty("XChainBridge") + @SuppressWarnings("MethodName") + Optional xChainBridge(); + + /** + * Attestations collected from the witness servers. This includes the parameters needed to recreate the message that + * was signed, including the amount, destination, signature reward amount, and reward account for that signature. With + * the exception of the reward account, all signatures must sign the message created with common parameters. + * + * @return A {@link List} of {@link MetaXChainCreateAccountAttestation}s. + */ + @JsonProperty("XChainCreateAccountAttestations") + @SuppressWarnings("MethodName") + List xChainCreateAccountAttestations(); + + /** + * An integer that determines the order that accounts created through cross-chain transfers must be performed. Smaller + * numbers must execute before larger numbers. + * + * @return An {@link XChainCount}. + */ + @JsonProperty("XChainAccountCreateCount") + @SuppressWarnings("MethodName") + Optional xChainAccountCreateCount(); + + /** + * 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") + Optional ownerNode(); + + /** + * The identifying hash of the transaction that most recently modified this object. + * + * @return A {@link Hash256} containing the previous transaction hash. + */ + @JsonProperty("PreviousTxnID") + Optional previousTransactionId(); + + /** + * The index of the ledger that contains the transaction that most recently modified this object. + * + * @return An {@link UnsignedInteger} representing the previous transaction ledger sequence. + */ + @JsonProperty("PreviousTxnLgrSeq") + Optional previousTransactionLedgerSequence(); + +} diff --git a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/codec/binary/BinarySerializationTests.java b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/codec/binary/BinarySerializationTests.java index 8520ac601..292c14e30 100644 --- a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/codec/binary/BinarySerializationTests.java +++ b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/codec/binary/BinarySerializationTests.java @@ -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. @@ -22,6 +22,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.xrpl.xrpl4j.crypto.TestConstants.ED_PUBLIC_KEY; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; @@ -34,6 +35,7 @@ import org.junit.jupiter.api.Test; import org.xrpl.xrpl4j.codec.binary.XrplBinaryCodec; import org.xrpl.xrpl4j.crypto.keys.PublicKey; +import org.xrpl.xrpl4j.crypto.signing.Signature; import org.xrpl.xrpl4j.model.flags.AccountSetTransactionFlags; import org.xrpl.xrpl4j.model.flags.OfferCreateFlags; import org.xrpl.xrpl4j.model.flags.PaymentChannelClaimFlags; @@ -41,7 +43,9 @@ import org.xrpl.xrpl4j.model.flags.RippleStateFlags; import org.xrpl.xrpl4j.model.flags.TransactionFlags; import org.xrpl.xrpl4j.model.flags.TrustSetFlags; +import org.xrpl.xrpl4j.model.flags.XChainModifyBridgeFlags; import org.xrpl.xrpl4j.model.jackson.ObjectMapperFactory; +import org.xrpl.xrpl4j.model.ledger.Issue; import org.xrpl.xrpl4j.model.ledger.RippleStateObject; import org.xrpl.xrpl4j.model.ledger.SignerEntry; import org.xrpl.xrpl4j.model.ledger.SignerEntryWrapper; @@ -57,6 +61,12 @@ import org.xrpl.xrpl4j.model.transactions.EscrowCreate; import org.xrpl.xrpl4j.model.transactions.EscrowFinish; import org.xrpl.xrpl4j.model.transactions.Hash256; +import org.xrpl.xrpl4j.model.transactions.ImmutableXChainAccountCreateCommit; +import org.xrpl.xrpl4j.model.transactions.ImmutableXChainAddClaimAttestation; +import org.xrpl.xrpl4j.model.transactions.ImmutableXChainClaim; +import org.xrpl.xrpl4j.model.transactions.ImmutableXChainCreateBridge; +import org.xrpl.xrpl4j.model.transactions.ImmutableXChainCreateClaimId; +import org.xrpl.xrpl4j.model.transactions.ImmutableXChainModifyBridge; import org.xrpl.xrpl4j.model.transactions.IssuedCurrencyAmount; import org.xrpl.xrpl4j.model.transactions.NetworkId; import org.xrpl.xrpl4j.model.transactions.OfferCancel; @@ -69,6 +79,17 @@ import org.xrpl.xrpl4j.model.transactions.SignerListSet; import org.xrpl.xrpl4j.model.transactions.Transaction; import org.xrpl.xrpl4j.model.transactions.TrustSet; +import org.xrpl.xrpl4j.model.transactions.XChainAccountCreateCommit; +import org.xrpl.xrpl4j.model.transactions.XChainAddAccountCreateAttestation; +import org.xrpl.xrpl4j.model.transactions.XChainAddClaimAttestation; +import org.xrpl.xrpl4j.model.transactions.XChainBridge; +import org.xrpl.xrpl4j.model.transactions.XChainClaim; +import org.xrpl.xrpl4j.model.transactions.XChainClaimId; +import org.xrpl.xrpl4j.model.transactions.XChainCommit; +import org.xrpl.xrpl4j.model.transactions.XChainCount; +import org.xrpl.xrpl4j.model.transactions.XChainCreateBridge; +import org.xrpl.xrpl4j.model.transactions.XChainCreateClaimId; +import org.xrpl.xrpl4j.model.transactions.XChainModifyBridge; import org.xrpl.xrpl4j.model.transactions.XrpCurrencyAmount; import java.math.BigDecimal; @@ -925,7 +946,6 @@ void serializeTrustSetWithEmptyFlags() throws JsonProcessingException { .networkId(NetworkId.of(UnsignedInteger.MAX_VALUE)) .build(); - String expectedBinary = "12001421FFFFFFFF240000002C63D6438D7EA4C68000000000000000000000000000574347000000" + "0000832297BEF589D59F9C03A84F920F8D9128CC1CE468400000000000000C73008114BE6C30732AE33CF2AF3344CE8172A6B9" + "300183E3"; @@ -946,7 +966,6 @@ void serializeTrustSetWithZeroFlags() throws JsonProcessingException { .build()) .build(); - String expectedBinary = "1200142200000000240000002C63D6438D7EA4C68000000000000000000000000000574347000000" + "0000832297BEF589D59F9C03A84F920F8D9128CC1CE468400000000000000C73008114BE6C30732AE33CF2AF3344CE8172A6B9" + "300183E3"; @@ -969,7 +988,6 @@ void serializeTrustSetWithNonZeroFlags() throws JsonProcessingException { .build()) .build(); - String expectedBinary = "1200142280020000240000002C63D6438D7EA4C68000000000000000000000000000574347000000" + "0000832297BEF589D59F9C03A84F920F8D9128CC1CE468400000000000000C73008114BE6C30732AE33CF2AF3344CE8172A6B9" + "300183E3"; @@ -1693,6 +1711,331 @@ void serializeSignerListSetWithNonZeroFlags() throws JsonProcessingException { assertSerializesAndDeserializes(signerListSet, expectedBinary); } + @Test + void serializeXChainAccountCreateCommitTest() throws JsonProcessingException { + XChainAccountCreateCommit commit = XChainAccountCreateCommit.builder() + .account(Address.of("rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh")) + .amount(XrpCurrencyAmount.ofDrops(1000000)) + .fee(XrpCurrencyAmount.ofDrops(10)) + .flags(TransactionFlags.FULLY_CANONICAL_SIG) + .destination(Address.of("rGzx83BVoqTYbGn7tiVAnFw7cbxjin13jL")) + .sequence(UnsignedInteger.ONE) + .signatureReward(XrpCurrencyAmount.ofDrops(10000)) + .signingPublicKey(PublicKey.fromBase16EncodedPublicKey("0330E7FC9D56BB25D6893BA3F317AE5BCF33B3291BD63DB3265" + + "4A313222F7FD020")) + .transactionSignature(Signature.fromBase16( + "304402202984DDE7F0B566F081F7953D7212BF031ACBF8860FE114102E9512C4C8768C770220701" + + "13F4630B1DC3045E4A98DDD648CEBC31B12774F7B44A1B8123CD2C9F5CF18") + ) + .xChainBridge( + XChainBridge.builder() + .lockingChainDoor(Address.of("rGzx83BVoqTYbGn7tiVAnFw7cbxjin13jL")) + .lockingChainIssue(Issue.XRP) + .issuingChainDoor(Address.of("r3kmLJN5D28dHuH8vZNUZpMC43pEHpaocV")) + .issuingChainIssue( + Issue.builder() + .currency("ETH") + .issuer(Address.of("rPyfep3gcLzkosKC9XiE77Y8DZWG6iWDT9")) + .build() + ) + .build() + ) + .build(); + + String expectedBinary = "12002C228000000024000000016140000000000F424068400000000000000A601D4000000000002" + + "71073210330E7FC9D56BB25D6893BA3F317AE5BCF33B3291BD63DB32654A313222F7FD0207446304402202984DDE7F0B566F0" + + "81F7953D7212BF031ACBF8860FE114102E9512C4C8768C77022070113F4630B1DC3045E4A98DDD648CEBC31B12774F7B44A1B" + + "8123CD2C9F5CF188114B5F762798A53D543A014CAF8B297CFF8F2F937E88314AF80285F637EE4AF3C20378F9DFB12511ACB8D" + + "27011914AF80285F637EE4AF3C20378F9DFB12511ACB8D27000000000000000000000000000000000000000014550FC62003E" + + "785DC231A1058A05E56E3F09CF4E60000000000000000000000004554480000000000FBEF9A3A2B814E807745FA3D9C32FFD155FA2E8C"; + + assertSerializesAndDeserializes(commit, expectedBinary); + } + + @Test + void serializeXChainAddAccountCreateAttestation() throws JsonProcessingException { + XChainAddAccountCreateAttestation attestation = XChainAddAccountCreateAttestation.builder() + .account(Address.of("r9cYxdjQsoXAEz3qQJc961SNLaXRkWXCvT")) + .otherChainSource(Address.of("raFcdz1g8LWJDJWJE2ZKLRGdmUmsTyxaym")) + .destination(Address.of("rJdTJRJZ6GXCCRaamHJgEqVzB7Zy4557Pi")) + .amount(XrpCurrencyAmount.ofDrops(10000000)) + .publicKey( + PublicKey.fromBase16EncodedPublicKey("ED1F4A024ACFEBDB6C7AA88DEDE3364E060487EA31B14CC9E0D610D152B31AADC2") + ) + .signature( + Signature.fromBase16("EEFCFA3DC2AB4AB7C4D2EBBC168CB621A11B82BABD86534DFC8EFA72439A49662" + + "D744073CD848E7A587A95B35162CDF9A69BB237E72C9537A987F5B8C394F30D") + ) + .wasLockingChainSend(true) + .attestationRewardAccount(Address.of("r9cYxdjQsoXAEz3qQJc961SNLaXRkWXCvT")) + .attestationSignerAccount(Address.of("r9cYxdjQsoXAEz3qQJc961SNLaXRkWXCvT")) + .xChainAccountCreateCount(XChainCount.of(UnsignedLong.valueOf("ffffffffffffffff", 16))) + .signatureReward(XrpCurrencyAmount.ofDrops(100)) + .xChainBridge( + XChainBridge.builder() + .lockingChainDoor(Address.of("rDJVtEuDKr4rj1B3qtW7R5TVWdXV2DY7Qg")) + .lockingChainIssue(Issue.XRP) + .issuingChainDoor(Address.of("rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh")) + .issuingChainIssue(Issue.XRP) + .build() + ) + .fee(XrpCurrencyAmount.ofDrops(20)) + .sequence(UnsignedInteger.valueOf(5)) + .lastLedgerSequence(UnsignedInteger.valueOf(13)) + .signingPublicKey( + PublicKey.fromBase16EncodedPublicKey("EDF54108BA2E0A0D3DC2AE3897F8BE0EFE776AE8D0F9FB0D0B9D64233084A8DDD1") + ) + .transactionSignature( + Signature.fromBase16("03E74AEF1F585F156786429D2FC87A89E5C6B5A56D68BFC9A6A329F3AC67CBF" + + "2B6958283C663A4522278CA162C69B23CF75149AF022B410EA0508C16F4205800") + ) + .build(); + + String expectedBinary = "12002E2400000005201B0000000D3015FFFFFFFFFFFFFFFF61400000000098968068400000000000001" + + "4601D40000000000000647121ED1F4A024ACFEBDB6C7AA88DEDE3364E060487EA31B14CC9E0D610D152B31AADC27321EDF54108BA" + + "2E0A0D3DC2AE3897F8BE0EFE776AE8D0F9FB0D0B9D64233084A8DDD1744003E74AEF1F585F156786429D2FC87A89E5C6B5A56D68B" + + "FC9A6A329F3AC67CBF2B6958283C663A4522278CA162C69B23CF75149AF022B410EA0508C16F42058007640EEFCFA3DC2AB4AB7C4" + + "D2EBBC168CB621A11B82BABD86534DFC8EFA72439A49662D744073CD848E7A587A95B35162CDF9A69BB237E72C9537A987F5B8C39" + + "4F30D81145E7A3E3D7200A794FA801C66CE3775B6416EE4128314C15F113E49BCC4B9FFF43CD0366C23ACD82F75638012143FD9ED" + + "9A79DEA67CB5D585111FEF0A29203FA0408014145E7A3E3D7200A794FA801C66CE3775B6416EE4128015145E7A3E3D7200A794FA8" + + "01C66CE3775B6416EE4120010130101191486F0B1126CE1205E59FDFDD2661A9FB7505CA70F000000000000000000000000000000" + + "000000000014B5F762798A53D543A014CAF8B297CFF8F2F937E80000000000000000000000000000000000000000"; + + assertSerializesAndDeserializes(attestation, expectedBinary); + } + + @Test + void serializeXChainAddClaimAttestation() throws JsonProcessingException { + XChainAddClaimAttestation attestation = XChainAddClaimAttestation.builder() + .account(Address.of("rsqvD8WFFEBBv4nztpoW9YYXJ7eRzLrtc3")) + .amount(XrpCurrencyAmount.ofDrops(10000000)) + .attestationRewardAccount(Address.of("rsqvD8WFFEBBv4nztpoW9YYXJ7eRzLrtc3")) + .attestationSignerAccount(Address.of("rsqvD8WFFEBBv4nztpoW9YYXJ7eRzLrtc3")) + .destination(Address.of("rJdTJRJZ6GXCCRaamHJgEqVzB7Zy4557Pi")) + .fee(XrpCurrencyAmount.ofDrops(20)) + .lastLedgerSequence(UnsignedInteger.valueOf(19)) + .otherChainSource(Address.of("raFcdz1g8LWJDJWJE2ZKLRGdmUmsTyxaym")) + .sequence(UnsignedInteger.valueOf(9)) + .publicKey( + PublicKey.fromBase16EncodedPublicKey("ED7541DEC700470F54276C90C333A13CDBB5D341FD43C60CEA12170F6D6D4E1136") + ) + .signature( + Signature.fromBase16("7C175050B08000AD35EEB2D87E16CD3F95A0AEEBF2A049474275153D9D4DD44" + + "528FE99AA50E71660A15B0B768E1B90E609BBD5DC7AFAFD45D9705D72D40EA10C") + ) + .signingPublicKey( + PublicKey.fromBase16EncodedPublicKey("ED0406B134786FE0751717226657F7BF8AFE96442C05D28ACEC66FB64852BA604C") + ) + .wasLockingChainSend(true) + .xChainBridge( + XChainBridge.builder() + .issuingChainDoor(Address.of("rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh")) + .issuingChainIssue(Issue.XRP) + .lockingChainDoor(Address.of("rDJVtEuDKr4rj1B3qtW7R5TVWdXV2DY7Qg")) + .lockingChainIssue(Issue.XRP) + .build() + ) + .xChainClaimId(XChainClaimId.of(UnsignedLong.MAX_VALUE)) + .transactionSignature( + Signature.fromBase16("D0423649E48A44F181262CF5FC08A68E7FA5CD9E55843E4F09014B76E602574" + + "741E8553383A4B43CABD194BB96713647FC0B885BE248E4FFA068FA3E6994CF04") + ) + .build(); + + String expectedBinary = "12002D2400000009201B000000133014FFFFFFFFFFFFFFFF614000000000989680684000000000000014" + + "7121ED7541DEC700470F54276C90C333A13CDBB5D341FD43C60CEA12170F6D6D4E11367321ED0406B134786FE0751717226657F7BF" + + "8AFE96442C05D28ACEC66FB64852BA604C7440D0423649E48A44F181262CF5FC08A68E7FA5CD9E55843E4F09014B76E602574741E8" + + "553383A4B43CABD194BB96713647FC0B885BE248E4FFA068FA3E6994CF0476407C175050B08000AD35EEB2D87E16CD3F95A0AEEBF2" + + "A049474275153D9D4DD44528FE99AA50E71660A15B0B768E1B90E609BBD5DC7AFAFD45D9705D72D40EA10C81141F30A4D728AB98B0" + + "950EC3B9815E6C8D43A7D5598314C15F113E49BCC4B9FFF43CD0366C23ACD82F75638012143FD9ED9A79DEA67CB5D585111FEF0A29" + + "203FA0408014141F30A4D728AB98B0950EC3B9815E6C8D43A7D5598015141F30A4D728AB98B0950EC3B9815E6C8D43A7D559001013" + + "0101191486F0B1126CE1205E59FDFDD2661A9FB7505CA70F000000000000000000000000000000000000000014B5F762798A53D543" + + "A014CAF8B297CFF8F2F937E80000000000000000000000000000000000000000"; + + assertSerializesAndDeserializes(attestation, expectedBinary); + } + + @Test + void serializeXChainClaim() throws JsonProcessingException { + XChainClaim claim = XChainClaim.builder() + .account(Address.of("rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh")) + .fee(XrpCurrencyAmount.ofDrops(10)) + .flags(TransactionFlags.FULLY_CANONICAL_SIG) + .sequence(UnsignedInteger.ONE) + .signingPublicKey( + PublicKey.fromBase16EncodedPublicKey("0330E7FC9D56BB25D6893BA3F317AE5BCF33B3291BD63DB32654A313222F7FD020") + ) + .amount(XrpCurrencyAmount.ofDrops(10000)) + .transactionSignature( + Signature.fromBase16("30440220445F7469FDA401787D9EE8A9B6E24DFF81E94F4C09FD311D2C0" + + "A58FCC02C684A022029E2EF34A5EA35F50D5BB57AC6320AD3AE12C13C8D1379B255A486D72CED142E") + ) + .xChainClaimId(XChainClaimId.of(UnsignedLong.MAX_VALUE)) + .destination(Address.of("r3kmLJN5D28dHuH8vZNUZpMC43pEHpaocV")) + .xChainBridge( + XChainBridge.builder() + .lockingChainDoor(Address.of("rGzx83BVoqTYbGn7tiVAnFw7cbxjin13jL")) + .lockingChainIssue(Issue.XRP) + .issuingChainDoor(Address.of("r3kmLJN5D28dHuH8vZNUZpMC43pEHpaocV")) + .issuingChainIssue(Issue.XRP) + .build() + ) + .build(); + + String expectedBinary = "12002B228000000024000000013014FFFFFFFFFFFFFFFF61400000000000271068400000000000000A" + + "73210330E7FC9D56BB25D6893BA3F317AE5BCF33B3291BD63DB32654A313222F7FD020744630440220445F7469FDA401787D9EE8" + + "A9B6E24DFF81E94F4C09FD311D2C0A58FCC02C684A022029E2EF34A5EA35F50D5BB57AC6320AD3AE12C13C8D1379B255A486D72C" + + "ED142E8114B5F762798A53D543A014CAF8B297CFF8F2F937E88314550FC62003E785DC231A1058A05E56E3F09CF4E6011914AF80" + + "285F637EE4AF3C20378F9DFB12511ACB8D27000000000000000000000000000000000000000014550FC62003E785DC231A1058A05" + + "E56E3F09CF4E60000000000000000000000000000000000000000"; + + assertSerializesAndDeserializes(claim, expectedBinary); + } + + @Test + void serializeXChainCommit() throws JsonProcessingException { + XChainCommit commit = XChainCommit.builder() + .fee(XrpCurrencyAmount.ofDrops(10)) + .sequence(UnsignedInteger.ONE) + .account(Address.of("rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh")) + .amount(XrpCurrencyAmount.ofDrops(10000)) + .xChainClaimId(XChainClaimId.of(UnsignedLong.MAX_VALUE)) + .xChainBridge( + XChainBridge.builder() + .lockingChainDoor(Address.of("rGzx83BVoqTYbGn7tiVAnFw7cbxjin13jL")) + .lockingChainIssue(Issue.XRP) + .issuingChainDoor(Address.of("r3kmLJN5D28dHuH8vZNUZpMC43pEHpaocV")) + .issuingChainIssue(Issue.XRP) + .build() + ) + .flags(TransactionFlags.FULLY_CANONICAL_SIG) + .signingPublicKey( + PublicKey.fromBase16EncodedPublicKey("0330E7FC9D56BB25D6893BA3F317AE5BCF33B3291BD63DB32654A313222F7FD020") + ) + .transactionSignature( + Signature.fromBase16("3043021F177323F0D93612C82A4393A99B23905A7E675753FD80C52997AFA" + + "B13F5F9D002203BFFAF457E90BDA65AABE8F8762BD96162FAD98A0C030CCD69B06EE9B12BBFFE") + ) + .build(); + + String expectedBinary = "12002A228000000024000000013014FFFFFFFFFFFFFFFF6140000000000027106840000000000000" + + "0A73210330E7FC9D56BB25D6893BA3F317AE5BCF33B3291BD63DB32654A313222F7FD02074453043021F177323F0D93612C82A" + + "4393A99B23905A7E675753FD80C52997AFAB13F5F9D002203BFFAF457E90BDA65AABE8F8762BD96162FAD98A0C030CCD69B06E" + + "E9B12BBFFE8114B5F762798A53D543A014CAF8B297CFF8F2F937E8011914AF80285F637EE4AF3C20378F9DFB12511ACB8D2700" + + "0000000000000000000000000000000000000014550FC62003E785DC231A1058A05E56E3F09CF4E60000000000000000000000" + + "000000000000000000"; + + assertSerializesAndDeserializes(commit, expectedBinary); + } + + @Test + void serializeXChainCreateBridge() throws JsonProcessingException { + XChainCreateBridge createBridge = XChainCreateBridge.builder() + .fee(XrpCurrencyAmount.ofDrops(10)) + .sequence(UnsignedInteger.ONE) + .account(Address.of("rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh")) + .signatureReward(XrpCurrencyAmount.ofDrops(1000)) + .xChainBridge( + XChainBridge.builder() + .lockingChainDoor(Address.of("rGzx83BVoqTYbGn7tiVAnFw7cbxjin13jL")) + .lockingChainIssue(Issue.XRP) + .issuingChainDoor(Address.of("r3kmLJN5D28dHuH8vZNUZpMC43pEHpaocV")) + .issuingChainIssue(Issue.XRP) + .build() + ) + .minAccountCreateAmount(XrpCurrencyAmount.ofDrops(10000)) + .signingPublicKey( + PublicKey.fromBase16EncodedPublicKey("0330E7FC9D56BB25D6893BA3F317AE5BCF33B3291BD63DB32654A313222F7FD020") + ) + .transactionSignature( + Signature.fromBase16("30440220101BCA4B5B5A37C6F44480F9A34752C9AA8B2CDF5AD47E3CB424DEDC" + + "21C06DB702206EEB257E82A89B1F46A0A2C7F070B0BD181D980FF86FE4269E369F6FC7A27091") + ) + .flags(TransactionFlags.UNSET) + .build(); + + String binary = "1200302200000000240000000168400000000000000A601D40000000000003E8601E40000000000027" + + "1073210330E7FC9D56BB25D6893BA3F317AE5BCF33B3291BD63DB32654A313222F7FD020744630440220101BCA4B5B5A" + + "37C6F44480F9A34752C9AA8B2CDF5AD47E3CB424DEDC21C06DB702206EEB257E82A89B1F46A0A2C7F070B0BD181D980F" + + "F86FE4269E369F6FC7A270918114B5F762798A53D543A014CAF8B297CFF8F2F937E8011914AF80285F637EE4AF3C2037" + + "8F9DFB12511ACB8D27000000000000000000000000000000000000000014550FC62003E785DC231A1058A05E56E3F09C" + + "F4E60000000000000000000000000000000000000000"; + + assertSerializesAndDeserializes(createBridge, binary); + } + + @Test + void serializeXChainCreateClaimId() throws JsonProcessingException { + XChainCreateClaimId claimId = XChainCreateClaimId.builder() + .fee(XrpCurrencyAmount.ofDrops(10)) + .sequence(UnsignedInteger.ONE) + .account(Address.of("rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh")) + .otherChainSource(Address.of("rGzx83BVoqTYbGn7tiVAnFw7cbxjin13jL")) + .signatureReward(XrpCurrencyAmount.ofDrops(10000)) + .xChainBridge( + XChainBridge.builder() + .lockingChainDoor(Address.of("rGzx83BVoqTYbGn7tiVAnFw7cbxjin13jL")) + .lockingChainIssue(Issue.XRP) + .issuingChainDoor(Address.of("r3kmLJN5D28dHuH8vZNUZpMC43pEHpaocV")) + .issuingChainIssue(Issue.XRP) + .build() + ) + .flags(TransactionFlags.FULLY_CANONICAL_SIG) + .signingPublicKey( + PublicKey.fromBase16EncodedPublicKey("0330E7FC9D56BB25D6893BA3F317AE5BCF33B3291BD63DB32654A313222F7FD020") + ) + .transactionSignature( + Signature.fromBase16("30440220247B20A1B9C48E21A374CB9B3E1FE2A7C528151868DF8D307E9FBE" + + "15237E531A02207C20C092DDCC525E583EF4AB7CB91E862A6DED19426997D3F0A2C84E2BE8C5DD") + ) + .build(); + + String binary = "1200292280000000240000000168400000000000000A601D400000000000271073210330E7FC9D56BB25D68" + + "93BA3F317AE5BCF33B3291BD63DB32654A313222F7FD020744630440220247B20A1B9C48E21A374CB9B3E1FE2A7C528151868" + + "DF8D307E9FBE15237E531A02207C20C092DDCC525E583EF4AB7CB91E862A6DED19426997D3F0A2C84E2BE8C5DD8114B5F7627" + + "98A53D543A014CAF8B297CFF8F2F937E8801214AF80285F637EE4AF3C20378F9DFB12511ACB8D27011914AF80285F637EE4AF" + + "3C20378F9DFB12511ACB8D27000000000000000000000000000000000000000014550FC62003E785DC231A1058A05E56E3F0" + + "9CF4E60000000000000000000000000000000000000000"; + + assertSerializesAndDeserializes(claimId, binary); + } + + @Test + void serializeXChainModifyBridge() throws JsonProcessingException { + XChainModifyBridge transaction = XChainModifyBridge.builder() + .fee(XrpCurrencyAmount.ofDrops(10)) + .sequence(UnsignedInteger.ONE) + .account(Address.of("rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh")) + .xChainBridge( + XChainBridge.builder() + .lockingChainDoor(Address.of("rGzx83BVoqTYbGn7tiVAnFw7cbxjin13jL")) + .lockingChainIssue(Issue.XRP) + .issuingChainDoor(Address.of("r3kmLJN5D28dHuH8vZNUZpMC43pEHpaocV")) + .issuingChainIssue(Issue.XRP) + .build() + ) + .signatureReward(XrpCurrencyAmount.ofDrops(1000)) + .minAccountCreateAmount(XrpCurrencyAmount.ofDrops(10000)) + .flags(XChainModifyBridgeFlags.UNSET) + .signingPublicKey( + PublicKey.fromBase16EncodedPublicKey("0330E7FC9D56BB25D6893BA3F317AE5BCF33B3291BD63DB32654A313222F7FD020") + ) + .transactionSignature( + Signature.fromBase16("3045022100D2CABC1B0E0635A8EE2E6554F6D474C49BC292C995C5C9F83179F" + + "4A60634B04C02205D1DB569D9593136F2FBEA7140010C8F46794D653AFDBEA8D30B8750BA4805E5") + ) + .build(); + + String binary = "12002F2200000000240000000168400000000000000A601D40000000000003E8601E40000000000027107" + + "3210330E7FC9D56BB25D6893BA3F317AE5BCF33B3291BD63DB32654A313222F7FD02074473045022100D2CABC1B0E0635A8E" + + "E2E6554F6D474C49BC292C995C5C9F83179F4A60634B04C02205D1DB569D9593136F2FBEA7140010C8F46794D653AFDBEA8D" + + "30B8750BA4805E58114B5F762798A53D543A014CAF8B297CFF8F2F937E8011914AF80285F637EE4AF3C20378F9DFB12511AC" + + "B8D27000000000000000000000000000000000000000014550FC62003E785DC231A1058A05E56E3F09CF4E60000000000000" + + "000000000000000000000000000"; + + assertSerializesAndDeserializes(transaction, binary); + } + private void assertSerializesAndDeserializes( T transaction, String expectedBinary diff --git a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/codec/binary/types/UInt64TypeUnitTest.java b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/codec/binary/types/UInt64TypeUnitTest.java index 849ab6cdd..e162389b0 100644 --- a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/codec/binary/types/UInt64TypeUnitTest.java +++ b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/codec/binary/types/UInt64TypeUnitTest.java @@ -41,11 +41,11 @@ void decode() { @Test void encode() { - assertThat(codec.fromJson("0").toHex()).isEqualTo("0000000000000000"); - assertThat(codec.fromJson("15").toHex()).isEqualTo("000000000000000F"); - assertThat(codec.fromJson("65535").toHex()).isEqualTo("000000000000FFFF"); - assertThat(codec.fromJson("4294967295").toHex()).isEqualTo("00000000FFFFFFFF"); - assertThat(codec.fromJson(maxUint64.toString()).toHex()).isEqualTo("FFFFFFFFFFFFFFFF"); + assertThat(codec.fromJson("\"0\"").toHex()).isEqualTo("0000000000000000"); + assertThat(codec.fromJson("\"F\"").toHex()).isEqualTo("000000000000000F"); + assertThat(codec.fromJson("\"FFFF\"").toHex()).isEqualTo("000000000000FFFF"); + assertThat(codec.fromJson("\"FFFFFFFF\"").toHex()).isEqualTo("00000000FFFFFFFF"); + assertThat(codec.fromJson("\"FFFFFFFFFFFFFFFF\"").toHex()).isEqualTo("FFFFFFFFFFFFFFFF"); } @Test 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 03abb1692..aec228279 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 @@ -41,6 +41,7 @@ import org.xrpl.xrpl4j.crypto.keys.PrivateKeyable; import org.xrpl.xrpl4j.crypto.keys.PublicKey; import org.xrpl.xrpl4j.model.client.channels.UnsignedClaim; +import org.xrpl.xrpl4j.model.ledger.Attestation; import org.xrpl.xrpl4j.model.transactions.Signer; import org.xrpl.xrpl4j.model.transactions.Transaction; @@ -268,6 +269,34 @@ public void signUnsignedClaimEd25519() { verifyNoMoreInteractions(signatureUtilsMock); } + /////////////////// + // Sign (privateKey, Attestation) + /////////////////// + + @Test + public void signAttestationWithNullPrivateKey() { + assertThrows(NullPointerException.class, () -> signatureService.sign(null, mock(Attestation.class))); + } + + @Test + public void signAttestationWithNullUnsignedClaim() { + assertThrows(NullPointerException.class, + () -> signatureService.sign(TestConstants.getEdPrivateKey(), (Attestation) null)); + } + + @Test + public void signAttestationEd25519() { + Attestation unsignedAttestationMock = mock(Attestation.class); + when(signatureUtilsMock.toSignableBytes(unsignedAttestationMock)).thenReturn(UnsignedByteArray.empty()); + + Signature actualSignature = signatureService.sign(TestConstants.getEdPrivateKey(), unsignedAttestationMock); + assertThat(actualSignature).isEqualTo(ed25519SignatureMock); + + verify(signatureUtilsMock, times(0)).toMultiSignableBytes(any(), any()); + verify(signatureUtilsMock).toSignableBytes(unsignedAttestationMock); + verifyNoMoreInteractions(signatureUtilsMock); + } + /////////////////// // verify /////////////////// diff --git a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/crypto/signing/AbstractTransactionSignerTest.java b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/crypto/signing/AbstractTransactionSignerTest.java index 30a056588..2fada9ccb 100644 --- a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/crypto/signing/AbstractTransactionSignerTest.java +++ b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/crypto/signing/AbstractTransactionSignerTest.java @@ -42,6 +42,7 @@ import org.xrpl.xrpl4j.crypto.keys.PrivateKeyable; import org.xrpl.xrpl4j.crypto.keys.PublicKey; import org.xrpl.xrpl4j.model.client.channels.UnsignedClaim; +import org.xrpl.xrpl4j.model.ledger.Attestation; import org.xrpl.xrpl4j.model.transactions.Signer; import org.xrpl.xrpl4j.model.transactions.Transaction; @@ -183,6 +184,48 @@ void signUnsignedClaimSecp256k1() { assertThat(actual).isEqualTo(secp256k1SignatureMock); } + /////////////////// + // Sign (Attestation) + /////////////////// + + @Test + void signAttestationWithNullMetadata() { + Attestation unsignedAttestationMock = mock(Attestation.class); + assertThrows(NullPointerException.class, () -> transactionSigner.sign(null, unsignedAttestationMock)); + } + + @Test + void signAttestationWithNullTransaction() { + assertThrows(NullPointerException.class, + () -> transactionSigner.sign(privateKeyableMock, (Attestation) null)); + } + + @Test + void signAttestationEd25519() { + keyType = KeyType.ED25519; + Attestation unsignedAttestationMock = mock(Attestation.class); + when(signatureUtilsMock.toSignableBytes(unsignedAttestationMock)).thenReturn(UnsignedByteArray.empty()); + + Signature actual = transactionSigner.sign(privateKeyableMock, unsignedAttestationMock); + + verify(signatureUtilsMock).toSignableBytes(unsignedAttestationMock); + verifyNoMoreInteractions(signatureUtilsMock); + assertThat(actual).isEqualTo(ed25519SignatureMock); + } + + @Test + void signAttestationClaimSecp256k1() { + keyType = KeyType.SECP256K1; + Attestation unsignedAttestationMock = mock(Attestation.class); + when(signatureUtilsMock.toSignableBytes(unsignedAttestationMock)).thenReturn(UnsignedByteArray.empty()); + + Signature actual = transactionSigner.sign(privateKeyableMock, unsignedAttestationMock); + + verify(signatureUtilsMock).toSignableBytes(unsignedAttestationMock); + verifyNoMoreInteractions(signatureUtilsMock); + assertThat(actual).isEqualTo(secp256k1SignatureMock); + } + /////////////////// // MultiSign to Signature /////////////////// 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 bd0441ff1..75e286c6e 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 @@ -31,6 +31,7 @@ import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; import static org.mockito.MockitoAnnotations.openMocks; +import static org.xrpl.xrpl4j.crypto.TestConstants.ED_PUBLIC_KEY; import com.fasterxml.jackson.core.JsonLocation; import com.fasterxml.jackson.core.JsonParseException; @@ -46,10 +47,13 @@ import org.xrpl.xrpl4j.codec.addresses.UnsignedByteArray; import org.xrpl.xrpl4j.codec.binary.XrplBinaryCodec; import org.xrpl.xrpl4j.crypto.keys.PublicKey; +import org.xrpl.xrpl4j.model.AddressConstants; 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.AttestationClaim; +import org.xrpl.xrpl4j.model.ledger.AttestationCreateAccount; import org.xrpl.xrpl4j.model.ledger.AuthAccount; import org.xrpl.xrpl4j.model.ledger.AuthAccountWrapper; import org.xrpl.xrpl4j.model.ledger.Issue; @@ -71,6 +75,10 @@ import org.xrpl.xrpl4j.model.transactions.EscrowCreate; import org.xrpl.xrpl4j.model.transactions.EscrowFinish; import org.xrpl.xrpl4j.model.transactions.Hash256; +import org.xrpl.xrpl4j.model.transactions.ImmutableXChainAddClaimAttestation; +import org.xrpl.xrpl4j.model.transactions.ImmutableXChainBridge; +import org.xrpl.xrpl4j.model.transactions.ImmutableXChainClaim; +import org.xrpl.xrpl4j.model.transactions.ImmutableXChainCreateClaimId; import org.xrpl.xrpl4j.model.transactions.IssuedCurrencyAmount; import org.xrpl.xrpl4j.model.transactions.NfTokenAcceptOffer; import org.xrpl.xrpl4j.model.transactions.NfTokenBurn; @@ -91,6 +99,17 @@ 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.XChainAccountCreateCommit; +import org.xrpl.xrpl4j.model.transactions.XChainAddAccountCreateAttestation; +import org.xrpl.xrpl4j.model.transactions.XChainAddClaimAttestation; +import org.xrpl.xrpl4j.model.transactions.XChainBridge; +import org.xrpl.xrpl4j.model.transactions.XChainClaim; +import org.xrpl.xrpl4j.model.transactions.XChainClaimId; +import org.xrpl.xrpl4j.model.transactions.XChainCommit; +import org.xrpl.xrpl4j.model.transactions.XChainCount; +import org.xrpl.xrpl4j.model.transactions.XChainCreateBridge; +import org.xrpl.xrpl4j.model.transactions.XChainCreateClaimId; +import org.xrpl.xrpl4j.model.transactions.XChainModifyBridge; import org.xrpl.xrpl4j.model.transactions.XrpCurrencyAmount; import java.util.ArrayList; @@ -104,6 +123,17 @@ public class SignatureUtilsTest { private static final String HEX_PUBLIC_KEY = "027535A4E90B2189CF9885563F45C4F454B3BFAB21930089C3878A9427B4D648D9"; + public static final ImmutableXChainBridge XCHAIN_BRIDGE = XChainBridge.builder() + .lockingChainDoor(Address.of("rMAXACCrp3Y8PpswXcg3bKggHX76V3F8M4")) + .lockingChainIssue(Issue.XRP) + .issuingChainDoor(Address.of("rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh")) + .issuingChainIssue( + Issue.builder() + .currency("TST") + .issuer(Address.of("rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh")) + .build() + ) + .build(); PublicKey sourcePublicKey; @@ -234,6 +264,180 @@ void unsignedClaimToSignableBytesActual() { .isEqualTo("434C4D00ABCDABCDABCDABCDABCDABCDABCDABCDABCDABCDABCDABCDABCDABCDABCDABCD0000000000000001"); } + ////////////////// + // toSignableBytes (AttestationClaim) + ////////////////// + + @Test + void attestationClaimToSignableBytesWhenNull() { + assertThrows(NullPointerException.class, () -> signatureUtils.toSignableBytes((AttestationClaim) null)); + } + + @Test + void attestationClaimToSignableBytes() throws JsonProcessingException { + when(xrplBinaryCodecMock.encode(any())).thenReturn("ABCD1234"); + final AttestationClaim unsignedAttestation = AttestationClaim.builder() + .xChainBridge( + XChainBridge.builder() + .lockingChainDoor(AddressConstants.GENESIS_ACCOUNT) + .lockingChainIssue(Issue.XRP) + .issuingChainDoor(AddressConstants.GENESIS_ACCOUNT) + .issuingChainIssue(Issue.XRP) + .build() + ) + .otherChainSource(AddressConstants.GENESIS_ACCOUNT) + .amount(XrpCurrencyAmount.ofDrops(10)) + .attestationRewardAccount(AddressConstants.GENESIS_ACCOUNT) + .wasLockingChainSend(true) + .xChainClaimId(XChainClaimId.of(UnsignedLong.ONE)) + .destination(AddressConstants.GENESIS_ACCOUNT) + .build(); + assertThat(signatureUtils.toSignableBytes(unsignedAttestation).hexValue()).isEqualTo("ABCD1234"); + + verify(objectMapperMock).writeValueAsString(any()); + verifyNoMoreInteractions(objectMapperMock); + verify(xrplBinaryCodecMock).encode(any()); + verifyNoMoreInteractions(xrplBinaryCodecMock); + } + + @Test + public void attestationClaimToSignableBytesWithJsonException() throws JsonProcessingException { + final AttestationClaim unsignedAttestation = AttestationClaim.builder() + .xChainBridge( + XChainBridge.builder() + .lockingChainDoor(AddressConstants.GENESIS_ACCOUNT) + .lockingChainIssue(Issue.XRP) + .issuingChainDoor(AddressConstants.GENESIS_ACCOUNT) + .issuingChainIssue(Issue.XRP) + .build() + ) + .otherChainSource(AddressConstants.GENESIS_ACCOUNT) + .amount(XrpCurrencyAmount.ofDrops(10)) + .attestationRewardAccount(AddressConstants.GENESIS_ACCOUNT) + .wasLockingChainSend(true) + .xChainClaimId(XChainClaimId.of(UnsignedLong.ONE)) + .destination(AddressConstants.GENESIS_ACCOUNT) + .build(); + doThrow(new JsonParseException(mock(JsonParser.class), "", mock(JsonLocation.class))) + .when(objectMapperMock).writeValueAsString(unsignedAttestation); + assertThrows(RuntimeException.class, () -> signatureUtils.toSignableBytes(unsignedAttestation)); + } + + @Test + void attestationClaimToSignableBytesActual() { + final AttestationClaim unsignedAttestation = AttestationClaim.builder() + .xChainBridge( + XChainBridge.builder() + .lockingChainDoor(AddressConstants.GENESIS_ACCOUNT) + .lockingChainIssue(Issue.XRP) + .issuingChainDoor(AddressConstants.GENESIS_ACCOUNT) + .issuingChainIssue(Issue.XRP) + .build() + ) + .otherChainSource(AddressConstants.GENESIS_ACCOUNT) + .amount(XrpCurrencyAmount.ofDrops(10)) + .attestationRewardAccount(AddressConstants.GENESIS_ACCOUNT) + .wasLockingChainSend(true) + .xChainClaimId(XChainClaimId.of(UnsignedLong.ONE)) + .destination(AddressConstants.GENESIS_ACCOUNT) + .build(); + assertThat(SignatureUtils.getInstance().toSignableBytes(unsignedAttestation).hexValue()) + .isEqualTo("3014000000000000000161400000000000000A8314B5F762798A53D543A014CAF8B297CFF8F2F937E" + + "8801214B5F762798A53D543A014CAF8B297CFF8F2F937E8801514B5F762798A53D543A014CAF8B297CFF8F2F937E8001013" + + "01011914B5F762798A53D543A014CAF8B297CFF8F2F937E8000000000000000000000000000000000000000014B5F762798" + + "A53D543A014CAF8B297CFF8F2F937E80000000000000000000000000000000000000000"); + } + + ////////////////// + // toSignableBytes (AttestationCreateAccount) + ////////////////// + + @Test + void attestationCreateAccountToSignableBytesWhenNull() { + assertThrows(NullPointerException.class, () -> signatureUtils.toSignableBytes((AttestationCreateAccount) null)); + } + + @Test + void attestationCreateToSignableBytes() throws JsonProcessingException { + when(xrplBinaryCodecMock.encode(any())).thenReturn("ABCD1234"); + final AttestationCreateAccount unsignedAttestation = AttestationCreateAccount.builder() + .xChainBridge( + XChainBridge.builder() + .lockingChainDoor(AddressConstants.GENESIS_ACCOUNT) + .lockingChainIssue(Issue.XRP) + .issuingChainDoor(AddressConstants.GENESIS_ACCOUNT) + .issuingChainIssue(Issue.XRP) + .build() + ) + .otherChainSource(AddressConstants.GENESIS_ACCOUNT) + .amount(XrpCurrencyAmount.ofDrops(10)) + .attestationRewardAccount(AddressConstants.GENESIS_ACCOUNT) + .wasLockingChainSend(true) + .destination(AddressConstants.GENESIS_ACCOUNT) + .xChainAccountCreateCount(XChainCount.of(UnsignedLong.ONE)) + .signatureReward(XrpCurrencyAmount.ofDrops(200)) + .build(); + + assertThat(signatureUtils.toSignableBytes(unsignedAttestation).hexValue()).isEqualTo("ABCD1234"); + + verify(objectMapperMock).writeValueAsString(any()); + verifyNoMoreInteractions(objectMapperMock); + verify(xrplBinaryCodecMock).encode(any()); + verifyNoMoreInteractions(xrplBinaryCodecMock); + } + + @Test + public void attestationCreateToSignableBytesWithJsonException() throws JsonProcessingException { + final AttestationCreateAccount unsignedAttestation = AttestationCreateAccount.builder() + .xChainBridge( + XChainBridge.builder() + .lockingChainDoor(AddressConstants.GENESIS_ACCOUNT) + .lockingChainIssue(Issue.XRP) + .issuingChainDoor(AddressConstants.GENESIS_ACCOUNT) + .issuingChainIssue(Issue.XRP) + .build() + ) + .otherChainSource(AddressConstants.GENESIS_ACCOUNT) + .amount(XrpCurrencyAmount.ofDrops(10)) + .attestationRewardAccount(AddressConstants.GENESIS_ACCOUNT) + .wasLockingChainSend(true) + .destination(AddressConstants.GENESIS_ACCOUNT) + .xChainAccountCreateCount(XChainCount.of(UnsignedLong.ONE)) + .signatureReward(XrpCurrencyAmount.ofDrops(200)) + .build(); + + doThrow(new JsonParseException(mock(JsonParser.class), "", mock(JsonLocation.class))) + .when(objectMapperMock).writeValueAsString(unsignedAttestation); + assertThrows(RuntimeException.class, () -> signatureUtils.toSignableBytes(unsignedAttestation)); + } + + @Test + void attestationCreateToSignableBytesActual() { + final AttestationCreateAccount unsignedAttestation = AttestationCreateAccount.builder() + .xChainBridge( + XChainBridge.builder() + .lockingChainDoor(AddressConstants.GENESIS_ACCOUNT) + .lockingChainIssue(Issue.XRP) + .issuingChainDoor(AddressConstants.GENESIS_ACCOUNT) + .issuingChainIssue(Issue.XRP) + .build() + ) + .otherChainSource(AddressConstants.GENESIS_ACCOUNT) + .amount(XrpCurrencyAmount.ofDrops(10)) + .attestationRewardAccount(AddressConstants.GENESIS_ACCOUNT) + .wasLockingChainSend(true) + .destination(AddressConstants.GENESIS_ACCOUNT) + .xChainAccountCreateCount(XChainCount.of(UnsignedLong.ONE)) + .signatureReward(XrpCurrencyAmount.ofDrops(200)) + .build(); + + assertThat(SignatureUtils.getInstance().toSignableBytes(unsignedAttestation).hexValue()) + .isEqualTo("3015000000000000000161400000000000000A601D40000000000000C88314B5F762798A53D543A014C" + + "AF8B297CFF8F2F937E8801214B5F762798A53D543A014CAF8B297CFF8F2F937E8801514B5F762798A53D543A014CAF8B297CF" + + "F8F2F937E800101301011914B5F762798A53D543A014CAF8B297CFF8F2F937E80000000000000000000000000000000000000" + + "00014B5F762798A53D543A014CAF8B297CFF8F2F937E80000000000000000000000000000000000000000"); + } + ////////////////// // toMultiSignableBytes ////////////////// @@ -735,6 +939,147 @@ void addSignatureToClawback() { addSignatureToTransactionHelper(clawback); } + @Test + void addSignatureToXChainAccountCreateCommit() { + XChainAccountCreateCommit commit = XChainAccountCreateCommit.builder() + .account(sourcePublicKey.deriveAddress()) + .fee(XrpCurrencyAmount.ofDrops(1)) + .sequence(UnsignedInteger.ONE) + .destination(Address.of("rD323VyRjgzzhY4bFpo44rmyh2neB5d8Mo")) + .amount(XrpCurrencyAmount.ofDrops(20000000)) + .signatureReward(XrpCurrencyAmount.ofDrops(100)) + .signingPublicKey(sourcePublicKey) + .xChainBridge(XCHAIN_BRIDGE) + .build(); + + addSignatureToTransactionHelper(commit); + } + + @Test + void addSignatureToXChainAddAccountCreateAttestation() { + XChainAddAccountCreateAttestation transaction = XChainAddAccountCreateAttestation.builder() + .account(sourcePublicKey.deriveAddress()) + .otherChainSource(Address.of("rUzB7yg1LcFa7m3q1hfrjr5w53vcWzNh3U")) + .destination(Address.of("rJMfWNVbyjcCtds8kpoEjEbYQ41J5B6MUd")) + .amount(XrpCurrencyAmount.ofDrops(2000000000)) + .publicKey(sourcePublicKey) + .wasLockingChainSend(true) + .attestationRewardAccount(Address.of("rpFp36UHW6FpEcZjZqq5jSJWY6UCj3k4Es")) + .attestationSignerAccount(Address.of("rpWLegmW9WrFBzHUj7brhQNZzrxgLj9oxw")) + .xChainAccountCreateCount(XChainCount.of(UnsignedLong.valueOf(2))) + .signatureReward(XrpCurrencyAmount.ofDrops(204)) + .xChainBridge(XCHAIN_BRIDGE) + .fee(XrpCurrencyAmount.ofDrops(20)) + .sequence(UnsignedInteger.ONE) + .signingPublicKey(sourcePublicKey) + .signature( + Signature.fromBase16("F95675BA8FDA21030DE1B687937A79E8491CE51832D6BEEBC071484FA5AF5B8A0E" + + "9AFF11A4AA46F09ECFFB04C6A8DAE8284AF3ED8128C7D0046D842448478500") + ) + .build(); + + addSignatureToTransactionHelper(transaction); + } + + @Test + void addSignatureToXChainAddClaimAttestation() { + XChainAddClaimAttestation transaction = XChainAddClaimAttestation.builder() + .account(sourcePublicKey.deriveAddress()) + .amount(XrpCurrencyAmount.ofDrops(10000000)) + .attestationRewardAccount(Address.of("rsqvD8WFFEBBv4nztpoW9YYXJ7eRzLrtc3")) + .attestationSignerAccount(Address.of("rsqvD8WFFEBBv4nztpoW9YYXJ7eRzLrtc3")) + .destination(Address.of("rJdTJRJZ6GXCCRaamHJgEqVzB7Zy4557Pi")) + .fee(XrpCurrencyAmount.ofDrops(20)) + .lastLedgerSequence(UnsignedInteger.valueOf(19)) + .otherChainSource(Address.of("raFcdz1g8LWJDJWJE2ZKLRGdmUmsTyxaym")) + .sequence(UnsignedInteger.valueOf(9)) + .publicKey(sourcePublicKey) + .signingPublicKey(sourcePublicKey) + .signature( + Signature.fromBase16("F95675BA8FDA21030DE1B687937A79E8491CE51832D6BEEBC071484FA5AF5B8A0E" + + "9AFF11A4AA46F09ECFFB04C6A8DAE8284AF3ED8128C7D0046D842448478500") + ) + .wasLockingChainSend(true) + .xChainBridge(XCHAIN_BRIDGE) + .xChainClaimId(XChainClaimId.of(UnsignedLong.ONE)) + .build(); + + addSignatureToTransactionHelper(transaction); + } + + @Test + void addSignatureToXChainClaim() { + XChainClaim transaction = XChainClaim.builder() + .account(sourcePublicKey.deriveAddress()) + .fee(XrpCurrencyAmount.ofDrops(12)) + .sequence(UnsignedInteger.ONE) + .signingPublicKey(sourcePublicKey) + .amount(XrpCurrencyAmount.ofDrops(10000)) + .xChainClaimId(XChainClaimId.of(UnsignedLong.valueOf(0x13F))) + .destination(Address.of("rahDmoXrtPdh7sUdrPjini3gcnTVYjbjjw")) + .xChainBridge(XCHAIN_BRIDGE) + .build(); + + addSignatureToTransactionHelper(transaction); + } + + @Test + void addSignatureToXChainCommit() { + XChainCommit transaction = XChainCommit.builder() + .fee(XrpCurrencyAmount.ofDrops(10)) + .sequence(UnsignedInteger.ONE) + .account(sourcePublicKey.deriveAddress()) + .amount(XrpCurrencyAmount.ofDrops(10000)) + .xChainClaimId(XChainClaimId.of(UnsignedLong.valueOf(0x13f))) + .xChainBridge(XCHAIN_BRIDGE) + .signingPublicKey(sourcePublicKey) + .build(); + + addSignatureToTransactionHelper(transaction); + } + + @Test + void addSignatureToXChainCreateBridge() { + XChainCreateBridge transaction = XChainCreateBridge.builder() + .fee(XrpCurrencyAmount.ofDrops(10)) + .sequence(UnsignedInteger.ONE) + .account(sourcePublicKey.deriveAddress()) + .signatureReward(XrpCurrencyAmount.ofDrops(200)) + .xChainBridge(XCHAIN_BRIDGE) + .signingPublicKey(sourcePublicKey) + .build(); + + addSignatureToTransactionHelper(transaction); + } + + @Test + void addSignatureToXChainCreateClaimId() { + XChainCreateClaimId transaction = XChainCreateClaimId.builder() + .fee(XrpCurrencyAmount.ofDrops(10)) + .sequence(UnsignedInteger.ONE) + .account(sourcePublicKey.deriveAddress()) + .otherChainSource(Address.of("rMTi57fNy2UkUb4RcdoUeJm7gjxVQvxzUo")) + .signatureReward(XrpCurrencyAmount.ofDrops(100)) + .xChainBridge(XCHAIN_BRIDGE) + .signingPublicKey(sourcePublicKey) + .build(); + + addSignatureToTransactionHelper(transaction); + } + + @Test + void addSignatureToXChainModifyBridge() { + XChainModifyBridge transaction = XChainModifyBridge.builder() + .fee(XrpCurrencyAmount.ofDrops(10)) + .sequence(UnsignedInteger.ONE) + .account(sourcePublicKey.deriveAddress()) + .xChainBridge(XCHAIN_BRIDGE) + .signingPublicKey(sourcePublicKey) + .build(); + + addSignatureToTransactionHelper(transaction); + } + @Test public void addSignatureToTransactionUnsupported() { assertThrows(IllegalArgumentException.class, () -> addSignatureToTransactionHelper(transactionMock)); @@ -1195,6 +1540,139 @@ void addMultiSignatureToAmmDelete() { addMultiSignatureToTransactionHelper(ammDelete); } + @Test + void addMultiSignatureToXChainAccountCreateCommit() { + XChainAccountCreateCommit transaction = XChainAccountCreateCommit.builder() + .account(sourcePublicKey.deriveAddress()) + .fee(XrpCurrencyAmount.ofDrops(1)) + .sequence(UnsignedInteger.ONE) + .destination(Address.of("rD323VyRjgzzhY4bFpo44rmyh2neB5d8Mo")) + .amount(XrpCurrencyAmount.ofDrops(20000000)) + .signatureReward(XrpCurrencyAmount.ofDrops(100)) + .xChainBridge(XCHAIN_BRIDGE) + .build(); + + addMultiSignatureToTransactionHelper(transaction); + } + + @Test + void addMultiSignatureToXChainAddAccountCreateAttestation() { + XChainAddAccountCreateAttestation transaction = XChainAddAccountCreateAttestation.builder() + .account(sourcePublicKey.deriveAddress()) + .otherChainSource(Address.of("rUzB7yg1LcFa7m3q1hfrjr5w53vcWzNh3U")) + .destination(Address.of("rJMfWNVbyjcCtds8kpoEjEbYQ41J5B6MUd")) + .amount(XrpCurrencyAmount.ofDrops(2000000000)) + .publicKey(sourcePublicKey) + .wasLockingChainSend(true) + .attestationRewardAccount(Address.of("rpFp36UHW6FpEcZjZqq5jSJWY6UCj3k4Es")) + .attestationSignerAccount(Address.of("rpWLegmW9WrFBzHUj7brhQNZzrxgLj9oxw")) + .xChainAccountCreateCount(XChainCount.of(UnsignedLong.valueOf(2))) + .signatureReward(XrpCurrencyAmount.ofDrops(204)) + .xChainBridge(XCHAIN_BRIDGE) + .fee(XrpCurrencyAmount.ofDrops(20)) + .sequence(UnsignedInteger.ONE) + .signature( + Signature.fromBase16("F95675BA8FDA21030DE1B687937A79E8491CE51832D6BEEBC071484FA5AF5B8A0E" + + "9AFF11A4AA46F09ECFFB04C6A8DAE8284AF3ED8128C7D0046D842448478500") + ) + .build(); + + addMultiSignatureToTransactionHelper(transaction); + } + + @Test + void addMultiSignatureToXChainAddClaimAttestation() { + XChainAddClaimAttestation transaction = XChainAddClaimAttestation.builder() + .account(sourcePublicKey.deriveAddress()) + .amount(XrpCurrencyAmount.ofDrops(10000000)) + .attestationRewardAccount(Address.of("rsqvD8WFFEBBv4nztpoW9YYXJ7eRzLrtc3")) + .attestationSignerAccount(Address.of("rsqvD8WFFEBBv4nztpoW9YYXJ7eRzLrtc3")) + .destination(Address.of("rJdTJRJZ6GXCCRaamHJgEqVzB7Zy4557Pi")) + .fee(XrpCurrencyAmount.ofDrops(20)) + .lastLedgerSequence(UnsignedInteger.valueOf(19)) + .otherChainSource(Address.of("raFcdz1g8LWJDJWJE2ZKLRGdmUmsTyxaym")) + .sequence(UnsignedInteger.valueOf(9)) + .publicKey(sourcePublicKey) + .signature( + Signature.fromBase16("F95675BA8FDA21030DE1B687937A79E8491CE51832D6BEEBC071484FA5AF5B8A0E" + + "9AFF11A4AA46F09ECFFB04C6A8DAE8284AF3ED8128C7D0046D842448478500") + ) + .wasLockingChainSend(true) + .xChainBridge(XCHAIN_BRIDGE) + .xChainClaimId(XChainClaimId.of(UnsignedLong.ONE)) + .build(); + + addMultiSignatureToTransactionHelper(transaction); + } + + @Test + void addMultiSignatureToXChainClaim() { + XChainClaim transaction = XChainClaim.builder() + .account(sourcePublicKey.deriveAddress()) + .fee(XrpCurrencyAmount.ofDrops(12)) + .sequence(UnsignedInteger.ONE) + .amount(XrpCurrencyAmount.ofDrops(10000)) + .xChainClaimId(XChainClaimId.of(UnsignedLong.valueOf(0x13F))) + .destination(Address.of("rahDmoXrtPdh7sUdrPjini3gcnTVYjbjjw")) + .xChainBridge(XCHAIN_BRIDGE) + .build(); + + addMultiSignatureToTransactionHelper(transaction); + } + + @Test + void addMultiSignatureToXChainCommit() { + XChainCommit transaction = XChainCommit.builder() + .fee(XrpCurrencyAmount.ofDrops(10)) + .sequence(UnsignedInteger.ONE) + .account(sourcePublicKey.deriveAddress()) + .amount(XrpCurrencyAmount.ofDrops(10000)) + .xChainClaimId(XChainClaimId.of(UnsignedLong.valueOf(0x13f))) + .xChainBridge(XCHAIN_BRIDGE) + .build(); + + addMultiSignatureToTransactionHelper(transaction); + } + + @Test + void addMultiSignatureToXChainCreateBridge() { + XChainCreateBridge transaction = XChainCreateBridge.builder() + .fee(XrpCurrencyAmount.ofDrops(10)) + .sequence(UnsignedInteger.ONE) + .account(sourcePublicKey.deriveAddress()) + .signatureReward(XrpCurrencyAmount.ofDrops(200)) + .xChainBridge(XCHAIN_BRIDGE) + .build(); + + addMultiSignatureToTransactionHelper(transaction); + } + + @Test + void addMultiSignatureToXChainCreateClaimId() { + XChainCreateClaimId transaction = XChainCreateClaimId.builder() + .fee(XrpCurrencyAmount.ofDrops(10)) + .sequence(UnsignedInteger.ONE) + .account(sourcePublicKey.deriveAddress()) + .otherChainSource(Address.of("rMTi57fNy2UkUb4RcdoUeJm7gjxVQvxzUo")) + .signatureReward(XrpCurrencyAmount.ofDrops(100)) + .xChainBridge(XCHAIN_BRIDGE) + .build(); + + addMultiSignatureToTransactionHelper(transaction); + } + + @Test + void addMultiSignatureToXChainModifyBridge() { + XChainModifyBridge transaction = XChainModifyBridge.builder() + .fee(XrpCurrencyAmount.ofDrops(10)) + .sequence(UnsignedInteger.ONE) + .account(sourcePublicKey.deriveAddress()) + .xChainBridge(XCHAIN_BRIDGE) + .build(); + + addMultiSignatureToTransactionHelper(transaction); + } + @Test public void addMultiSignaturesToTransactionUnsupported() { when(transactionMock.transactionSignature()).thenReturn(Optional.empty()); diff --git a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/crypto/signing/bc/BcDerivedKeySignatureServiceTest.java b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/crypto/signing/bc/BcDerivedKeySignatureServiceTest.java index 2d9db6f04..e407ae1c2 100644 --- a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/crypto/signing/bc/BcDerivedKeySignatureServiceTest.java +++ b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/crypto/signing/bc/BcDerivedKeySignatureServiceTest.java @@ -42,14 +42,21 @@ import org.xrpl.xrpl4j.crypto.keys.Seed; import org.xrpl.xrpl4j.crypto.signing.Signature; import org.xrpl.xrpl4j.crypto.signing.SingleSignedTransaction; +import org.xrpl.xrpl4j.model.AddressConstants; import org.xrpl.xrpl4j.model.client.channels.UnsignedClaim; import org.xrpl.xrpl4j.model.flags.PaymentFlags; import org.xrpl.xrpl4j.model.flags.TransactionFlags; +import org.xrpl.xrpl4j.model.ledger.AttestationClaim; +import org.xrpl.xrpl4j.model.ledger.AttestationCreateAccount; +import org.xrpl.xrpl4j.model.ledger.Issue; 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.Payment; import org.xrpl.xrpl4j.model.transactions.Signer; +import org.xrpl.xrpl4j.model.transactions.XChainBridge; +import org.xrpl.xrpl4j.model.transactions.XChainClaimId; +import org.xrpl.xrpl4j.model.transactions.XChainCount; import org.xrpl.xrpl4j.model.transactions.XrpCurrencyAmount; import java.nio.charset.StandardCharsets; @@ -517,6 +524,200 @@ void signUnsignedClaimEc() { .forEach(validSig -> assertThat(validSig).isTrue()); } + @Test + void signAttestationClaimEd() { + final PrivateKeyReference privateKeyReference = privateKeyReference("foo", KeyType.ED25519); + + final AttestationClaim unsignedAttestation = AttestationClaim.builder() + .xChainBridge( + XChainBridge.builder() + .lockingChainDoor(AddressConstants.GENESIS_ACCOUNT) + .lockingChainIssue(Issue.XRP) + .issuingChainDoor(AddressConstants.GENESIS_ACCOUNT) + .issuingChainIssue(Issue.XRP) + .build() + ) + .otherChainSource(AddressConstants.GENESIS_ACCOUNT) + .amount(XrpCurrencyAmount.ofDrops(10)) + .attestationRewardAccount(AddressConstants.GENESIS_ACCOUNT) + .wasLockingChainSend(true) + .xChainClaimId(XChainClaimId.of(UnsignedLong.ONE)) + .destination(AddressConstants.GENESIS_ACCOUNT) + .build(); + + final ExecutorService pool = Executors.newFixedThreadPool(5); + final Callable signedTxCallable = () -> { + Signature signature = this.derivedKeySignatureService.sign(privateKeyReference, unsignedAttestation); + assertThat(signature).isNotNull(); + assertThat(signature.base16Value()).isEqualTo( + "C436AA1F579C2ADCE04143A8BCF77C8A10BD2EA2ADD9989FD381AD65123C977294C248157686149D9D8552C2A35A90" + + "28D577B244CE079020372A229D06D03504" + ); + return true; + }; + + final List> futureSeeds = new ArrayList<>(); + for (int i = 0; i < 500; i++) { + futureSeeds.add(pool.submit(signedTxCallable)); + } + + futureSeeds.stream() + .map($ -> { + try { + return $.get(); + } catch (InterruptedException | ExecutionException e) { + throw new RuntimeException(e.getMessage(), e); + } + }) + .forEach(validSig -> assertThat(validSig).isTrue()); + } + + @Test + void signAttestationClaimEc() { + final PrivateKeyReference privateKeyReference = privateKeyReference("foo", KeyType.SECP256K1); + + final AttestationClaim unsignedAttestation = AttestationClaim.builder() + .xChainBridge( + XChainBridge.builder() + .lockingChainDoor(AddressConstants.GENESIS_ACCOUNT) + .lockingChainIssue(Issue.XRP) + .issuingChainDoor(AddressConstants.GENESIS_ACCOUNT) + .issuingChainIssue(Issue.XRP) + .build() + ) + .otherChainSource(AddressConstants.GENESIS_ACCOUNT) + .amount(XrpCurrencyAmount.ofDrops(10)) + .attestationRewardAccount(AddressConstants.GENESIS_ACCOUNT) + .wasLockingChainSend(true) + .xChainClaimId(XChainClaimId.of(UnsignedLong.ONE)) + .destination(AddressConstants.GENESIS_ACCOUNT) + .build(); + + final ExecutorService pool = Executors.newFixedThreadPool(5); + final Callable signedTxCallable = () -> { + Signature signature = this.derivedKeySignatureService.sign(privateKeyReference, unsignedAttestation); + assertThat(signature).isNotNull(); + assertThat(signature.base16Value()).isEqualTo( + "30440220078E2379E68E59D60DFF4054FE0F988A95595E7A0DB2DB7215A3B7C03232CC7C022021BDB527050084BD9" + + "533BD21FAC0ABB63FD21754AC87C5F580AC583DDF1A9740" + ); + return true; + }; + + final List> futureSeeds = new ArrayList<>(); + for (int i = 0; i < 500; i++) { + futureSeeds.add(pool.submit(signedTxCallable)); + } + + futureSeeds.stream() + .map($ -> { + try { + return $.get(); + } catch (InterruptedException | ExecutionException e) { + throw new RuntimeException(e.getMessage(), e); + } + }) + .forEach(validSig -> assertThat(validSig).isTrue()); + } + + @Test + void signAttestationCreateAccountEd() { + final PrivateKeyReference privateKeyReference = privateKeyReference("foo", KeyType.ED25519); + + final AttestationCreateAccount unsignedAttestation = AttestationCreateAccount.builder() + .xChainBridge( + XChainBridge.builder() + .lockingChainDoor(AddressConstants.GENESIS_ACCOUNT) + .lockingChainIssue(Issue.XRP) + .issuingChainDoor(AddressConstants.GENESIS_ACCOUNT) + .issuingChainIssue(Issue.XRP) + .build() + ) + .otherChainSource(AddressConstants.GENESIS_ACCOUNT) + .amount(XrpCurrencyAmount.ofDrops(10)) + .attestationRewardAccount(AddressConstants.GENESIS_ACCOUNT) + .wasLockingChainSend(true) + .destination(AddressConstants.GENESIS_ACCOUNT) + .xChainAccountCreateCount(XChainCount.of(UnsignedLong.ONE)) + .signatureReward(XrpCurrencyAmount.ofDrops(200)) + .build(); + + final ExecutorService pool = Executors.newFixedThreadPool(5); + final Callable signedTxCallable = () -> { + Signature signature = this.derivedKeySignatureService.sign(privateKeyReference, unsignedAttestation); + assertThat(signature).isNotNull(); + assertThat(signature.base16Value()).isEqualTo( + "D44CFFD228B1A17DEC47433F4E14DD3FD844513129EB68725D45A3D9FA89AFDD45C433579BC9B31FFC109181" + + "AF565BD63B49011BC6784F9861C33BD7B1235007" + ); + return true; + }; + + final List> futureSeeds = new ArrayList<>(); + for (int i = 0; i < 500; i++) { + futureSeeds.add(pool.submit(signedTxCallable)); + } + + futureSeeds.stream() + .map($ -> { + try { + return $.get(); + } catch (InterruptedException | ExecutionException e) { + throw new RuntimeException(e.getMessage(), e); + } + }) + .forEach(validSig -> assertThat(validSig).isTrue()); + } + + @Test + void signAttestationCreateAccountEc() { + final PrivateKeyReference privateKeyReference = privateKeyReference("foo", KeyType.SECP256K1); + + final AttestationCreateAccount unsignedAttestation = AttestationCreateAccount.builder() + .xChainBridge( + XChainBridge.builder() + .lockingChainDoor(AddressConstants.GENESIS_ACCOUNT) + .lockingChainIssue(Issue.XRP) + .issuingChainDoor(AddressConstants.GENESIS_ACCOUNT) + .issuingChainIssue(Issue.XRP) + .build() + ) + .otherChainSource(AddressConstants.GENESIS_ACCOUNT) + .amount(XrpCurrencyAmount.ofDrops(10)) + .attestationRewardAccount(AddressConstants.GENESIS_ACCOUNT) + .wasLockingChainSend(true) + .destination(AddressConstants.GENESIS_ACCOUNT) + .xChainAccountCreateCount(XChainCount.of(UnsignedLong.ONE)) + .signatureReward(XrpCurrencyAmount.ofDrops(200)) + .build(); + + final ExecutorService pool = Executors.newFixedThreadPool(5); + final Callable signedTxCallable = () -> { + Signature signature = this.derivedKeySignatureService.sign(privateKeyReference, unsignedAttestation); + assertThat(signature).isNotNull(); + assertThat(signature.base16Value()).isEqualTo( + "3044022024A4E54773DCC751082D2F9DAEC60C06960E2DE40BC2896B9690D917DA310EB902202547C4569E817" + + "C4505C4B672D65CF24B93B3EB6748564E819EB26E8ED5A20790" + ); + return true; + }; + + final List> futureSeeds = new ArrayList<>(); + for (int i = 0; i < 500; i++) { + futureSeeds.add(pool.submit(signedTxCallable)); + } + + futureSeeds.stream() + .map($ -> { + try { + return $.get(); + } catch (InterruptedException | ExecutionException e) { + throw new RuntimeException(e.getMessage(), e); + } + }) + .forEach(validSig -> assertThat(validSig).isTrue()); + } + @Test void generateEd25519XrplSeed() { final ExecutorService pool = Executors.newFixedThreadPool(5); 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 index 1955540d3..9f3c3d1d8 100644 --- 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 @@ -16,6 +16,7 @@ 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.BridgeObject; import org.xrpl.xrpl4j.model.ledger.CheckObject; import org.xrpl.xrpl4j.model.ledger.DepositPreAuthObject; import org.xrpl.xrpl4j.model.ledger.EscrowObject; @@ -27,6 +28,8 @@ 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.ImmutableXChainBridge; +import org.xrpl.xrpl4j.model.transactions.XChainBridge; class LedgerEntryRequestParamsTest extends AbstractJsonTest { @@ -47,6 +50,8 @@ void testTypedIndexParams() throws JSONException, JsonProcessingException { assertThat(params.depositPreAuth()).isEmpty(); assertThat(params.ticket()).isEmpty(); assertThat(params.nftPage()).isEmpty(); + assertThat(params.bridgeAccount()).isEmpty(); + assertThat(params.bridge()).isEmpty(); String json = String.format("{\n" + " \"index\": \"%s\",\n" + @@ -80,6 +85,8 @@ void testUntypedIndexParams() throws JSONException, JsonProcessingException { assertThat(params.depositPreAuth()).isEmpty(); assertThat(params.ticket()).isEmpty(); assertThat(params.nftPage()).isEmpty(); + assertThat(params.bridgeAccount()).isEmpty(); + assertThat(params.bridge()).isEmpty(); String json = String.format("{\n" + " \"index\": \"%s\",\n" + @@ -108,6 +115,8 @@ void testAccountRootParams() throws JSONException, JsonProcessingException { assertThat(params.depositPreAuth()).isEmpty(); assertThat(params.ticket()).isEmpty(); assertThat(params.nftPage()).isEmpty(); + assertThat(params.bridgeAccount()).isEmpty(); + assertThat(params.bridge()).isEmpty(); String json = String.format("{\n" + " \"account_root\": \"%s\",\n" + @@ -144,6 +153,8 @@ void testAmmParams() throws JSONException, JsonProcessingException { assertThat(params.depositPreAuth()).isEmpty(); assertThat(params.ticket()).isEmpty(); assertThat(params.nftPage()).isEmpty(); + assertThat(params.bridgeAccount()).isEmpty(); + assertThat(params.bridge()).isEmpty(); String json = "{\n" + " \"amm\": {\n" + @@ -185,6 +196,8 @@ void testOfferParams() throws JSONException, JsonProcessingException { assertThat(params.depositPreAuth()).isEmpty(); assertThat(params.ticket()).isEmpty(); assertThat(params.nftPage()).isEmpty(); + assertThat(params.bridgeAccount()).isEmpty(); + assertThat(params.bridge()).isEmpty(); String json = "{\n" + " \"offer\": {\n" + @@ -224,6 +237,8 @@ void testRippleStateParams() throws JSONException, JsonProcessingException { assertThat(params.depositPreAuth()).isEmpty(); assertThat(params.ticket()).isEmpty(); assertThat(params.nftPage()).isEmpty(); + assertThat(params.bridgeAccount()).isEmpty(); + assertThat(params.bridge()).isEmpty(); String json = "{\n" + " \"ripple_state\": {\n" + @@ -256,6 +271,8 @@ void testCheckParams() throws JSONException, JsonProcessingException { assertThat(params.depositPreAuth()).isEmpty(); assertThat(params.ticket()).isEmpty(); assertThat(params.nftPage()).isEmpty(); + assertThat(params.bridgeAccount()).isEmpty(); + assertThat(params.bridge()).isEmpty(); String json = String.format("{\n" + " \"check\": \"%s\",\n" + @@ -288,6 +305,8 @@ void testEscrowParams() throws JSONException, JsonProcessingException { assertThat(params.depositPreAuth()).isEmpty(); assertThat(params.ticket()).isEmpty(); assertThat(params.nftPage()).isEmpty(); + assertThat(params.bridgeAccount()).isEmpty(); + assertThat(params.bridge()).isEmpty(); String json = "{\n" + " \"escrow\": {\n" + @@ -319,6 +338,8 @@ void testPaymentChannelParams() throws JSONException, JsonProcessingException { assertThat(params.depositPreAuth()).isEmpty(); assertThat(params.ticket()).isEmpty(); assertThat(params.nftPage()).isEmpty(); + assertThat(params.bridgeAccount()).isEmpty(); + assertThat(params.bridge()).isEmpty(); String json = String.format("{\n" + " \"payment_channel\": \"%s\",\n" + @@ -352,6 +373,8 @@ void testDepositPreAuthParams() throws JSONException, JsonProcessingException { assertThat(params.paymentChannel()).isEmpty(); assertThat(params.ticket()).isEmpty(); assertThat(params.nftPage()).isEmpty(); + assertThat(params.bridgeAccount()).isEmpty(); + assertThat(params.bridge()).isEmpty(); String json = "{\n" + " \"deposit_preauth\": {\n" + @@ -388,6 +411,8 @@ void testTicketParams() throws JSONException, JsonProcessingException { assertThat(params.paymentChannel()).isEmpty(); assertThat(params.depositPreAuth()).isEmpty(); assertThat(params.nftPage()).isEmpty(); + assertThat(params.bridgeAccount()).isEmpty(); + assertThat(params.bridge()).isEmpty(); String json = "{\n" + " \"ticket\": {\n" + @@ -420,6 +445,8 @@ void testNftPageParams() throws JSONException, JsonProcessingException { assertThat(params.paymentChannel()).isEmpty(); assertThat(params.depositPreAuth()).isEmpty(); assertThat(params.ticket()).isEmpty(); + assertThat(params.bridgeAccount()).isEmpty(); + assertThat(params.bridge()).isEmpty(); String json = String.format("{\n" + " \"nft_page\": \"%s\",\n" + @@ -429,4 +456,43 @@ void testNftPageParams() throws JSONException, JsonProcessingException { assertCanSerializeAndDeserialize(params, json); } + + @Test + void testBridgeParams() throws JSONException, JsonProcessingException { + XChainBridge bridge = XChainBridge.builder() + .lockingChainDoor(ED_ADDRESS) + .lockingChainIssue(Issue.XRP) + .issuingChainDoor(ED_ADDRESS) + .issuingChainIssue(Issue.XRP) + .build(); + LedgerEntryRequestParams params = LedgerEntryRequestParams.bridge( + ED_ADDRESS, + bridge, + LedgerSpecifier.VALIDATED + ); + assertThat(params.bridgeAccount()).isNotEmpty().get().isEqualTo(ED_ADDRESS); + assertThat(params.bridge()).isNotEmpty().get().isEqualTo(bridge); + assertThat(params.ledgerObjectClass()).isEqualTo(BridgeObject.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(); + assertThat(params.nftPage()).isEmpty(); + + String json = String.format("{\n" + + " \"bridge_account\": \"%s\",\n" + + " \"bridge\": %s,\n" + + " \"binary\": false,\n" + + " \"ledger_index\": \"validated\"\n" + + " }", ED_ADDRESS, objectMapper.writeValueAsString(bridge)); + + assertCanSerializeAndDeserialize(params, json); + } } \ No newline at end of file diff --git a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/flags/XChainModifyBridgeFlagsTest.java b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/flags/XChainModifyBridgeFlagsTest.java new file mode 100644 index 000000000..d647ce82a --- /dev/null +++ b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/flags/XChainModifyBridgeFlagsTest.java @@ -0,0 +1,89 @@ +package org.xrpl.xrpl4j.model.flags; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +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 java.util.stream.Stream; + +class XChainModifyBridgeFlagsTest extends AbstractFlagsTest { + + public static Stream data() { + return getBooleanCombinations(1); + } + + @ParameterizedTest + @MethodSource("data") + public void testFlagsConstructionWithIndividualFlags( + boolean tfClearAccountCreateAmount + ) { + XChainModifyBridgeFlags flags = XChainModifyBridgeFlags.builder() + .tfClearAccountCreateAmount(tfClearAccountCreateAmount) + .build(); + + assertThat(flags.getValue()) + .isEqualTo(getExpectedFlags(tfClearAccountCreateAmount)); + } + + @ParameterizedTest + @MethodSource("data") + void testDeriveIndividualFlagsFromFlags( + boolean tfClearAccountCreateAmount + ) { + long expectedFlags = getExpectedFlags(tfClearAccountCreateAmount); + XChainModifyBridgeFlags flags = XChainModifyBridgeFlags.of(expectedFlags); + + assertThat(flags.getValue()).isEqualTo(expectedFlags); + + assertThat(flags.tfFullyCanonicalSig()).isTrue(); + assertThat(flags.tfClearAccountCreateAmount()).isEqualTo(tfClearAccountCreateAmount); + } + + @Test + void testEmptyFlags() { + XChainModifyBridgeFlags flags = XChainModifyBridgeFlags.empty(); + assertThat(flags.isEmpty()).isTrue(); + assertThat(flags.tfFullyCanonicalSig()).isFalse(); + assertThat(flags.tfClearAccountCreateAmount()).isFalse(); + assertThat(flags.getValue()).isEqualTo(0L); + } + + @ParameterizedTest + @MethodSource("data") + void testJson( + boolean tfClearAccountCreateAmount + ) throws JSONException, JsonProcessingException { + XChainModifyBridgeFlags flags = XChainModifyBridgeFlags.builder() + .tfClearAccountCreateAmount(tfClearAccountCreateAmount) + .build(); + + TransactionFlagsWrapper wrapper = TransactionFlagsWrapper.of(flags); + String json = String.format("{\n" + + " \"flags\": %s\n" + + "}", flags.getValue()); + + assertCanSerializeAndDeserialize(wrapper, json); + } + + @Test + void testEmptyJson() throws JSONException, JsonProcessingException { + XChainModifyBridgeFlags flags = XChainModifyBridgeFlags.empty(); + TransactionFlagsWrapper wrapper = TransactionFlagsWrapper.of(flags); + String json = "{\n" + + "}"; + + assertCanSerializeAndDeserialize(wrapper, json); + } + + private long getExpectedFlags( + boolean tfClearAccountCreateAmount + ) { + return (XChainModifyBridgeFlags.FULLY_CANONICAL_SIG.getValue()) | + (tfClearAccountCreateAmount ? XChainModifyBridgeFlags.CLEAR_ACCOUNT_CREATE_AMOUNT.getValue() : 0L); + } +} \ No newline at end of file diff --git a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/ledger/BridgeObjectTest.java b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/ledger/BridgeObjectTest.java new file mode 100644 index 000000000..8698c7a3e --- /dev/null +++ b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/ledger/BridgeObjectTest.java @@ -0,0 +1,119 @@ +package org.xrpl.xrpl4j.model.ledger; + +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.transactions.Address; +import org.xrpl.xrpl4j.model.transactions.Hash256; +import org.xrpl.xrpl4j.model.transactions.XChainBridge; +import org.xrpl.xrpl4j.model.transactions.XChainClaimId; +import org.xrpl.xrpl4j.model.transactions.XChainCount; +import org.xrpl.xrpl4j.model.transactions.XrpCurrencyAmount; + +class BridgeObjectTest extends AbstractJsonTest { + + @Test + void testFullyPopulatedJson() throws JSONException, JsonProcessingException { + BridgeObject bridge = BridgeObject.builder() + .account(Address.of("r3nCVTbZGGYoWvZ58BcxDmiMUU7ChMa1eC")) + .minAccountCreateAmount(XrpCurrencyAmount.ofDrops(2000000000)) + .ownerNode("0") + .previousTransactionId(Hash256.of("67A8A1B36C1B97BE3AAB6B19CB3A3069034877DE917FD1A71919EAE7548E5636")) + .previousTransactionLedgerSequence(UnsignedInteger.valueOf(102)) + .signatureReward(XrpCurrencyAmount.ofDrops(204)) + .xChainAccountClaimCount(XChainCount.of(UnsignedLong.ZERO)) + .xChainAccountCreateCount(XChainCount.of(UnsignedLong.ZERO)) + .xChainBridge( + XChainBridge.builder() + .issuingChainDoor(Address.of("rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh")) + .issuingChainIssue(Issue.XRP) + .lockingChainDoor(Address.of("r3nCVTbZGGYoWvZ58BcxDmiMUU7ChMa1eC")) + .lockingChainIssue(Issue.XRP) + .build() + ) + .xChainClaimId(XChainClaimId.of(UnsignedLong.ONE)) + .index(Hash256.of("9F2C9E23343852036AFD323025A8506018ABF9D4DBAA746D61BF1CFB5C297D10")) + .build(); + + String json = "\n" + + "{\n" + + " \"Account\": \"r3nCVTbZGGYoWvZ58BcxDmiMUU7ChMa1eC\",\n" + + " \"Flags\": 0,\n" + + " \"LedgerEntryType\": \"Bridge\",\n" + + " \"MinAccountCreateAmount\": \"2000000000\",\n" + + " \"OwnerNode\": \"0\",\n" + + " \"PreviousTxnID\": \"67A8A1B36C1B97BE3AAB6B19CB3A3069034877DE917FD1A71919EAE7548E5636\",\n" + + " \"PreviousTxnLgrSeq\": 102,\n" + + " \"SignatureReward\": \"204\",\n" + + " \"XChainAccountClaimCount\": \"0\",\n" + + " \"XChainAccountCreateCount\": \"0\",\n" + + " \"XChainBridge\": {\n" + + " \"IssuingChainDoor\": \"rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh\",\n" + + " \"IssuingChainIssue\": {\n" + + " \"currency\": \"XRP\"\n" + + " },\n" + + " \"LockingChainDoor\": \"r3nCVTbZGGYoWvZ58BcxDmiMUU7ChMa1eC\",\n" + + " \"LockingChainIssue\": {\n" + + " \"currency\": \"XRP\"\n" + + " }\n" + + " },\n" + + " \"XChainClaimID\": \"1\",\n" + + " \"index\": \"9F2C9E23343852036AFD323025A8506018ABF9D4DBAA746D61BF1CFB5C297D10\"\n" + + "}"; + + assertCanSerializeAndDeserialize(bridge, json); + } + + @Test + void testJsonWithEmptyAccountCreateAmount() throws JSONException, JsonProcessingException { + BridgeObject bridge = BridgeObject.builder() + .account(Address.of("r3nCVTbZGGYoWvZ58BcxDmiMUU7ChMa1eC")) + .ownerNode("0") + .previousTransactionId(Hash256.of("67A8A1B36C1B97BE3AAB6B19CB3A3069034877DE917FD1A71919EAE7548E5636")) + .previousTransactionLedgerSequence(UnsignedInteger.valueOf(102)) + .signatureReward(XrpCurrencyAmount.ofDrops(204)) + .xChainAccountClaimCount(XChainCount.of(UnsignedLong.ZERO)) + .xChainAccountCreateCount(XChainCount.of(UnsignedLong.ZERO)) + .xChainBridge( + XChainBridge.builder() + .issuingChainDoor(Address.of("rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh")) + .issuingChainIssue(Issue.XRP) + .lockingChainDoor(Address.of("r3nCVTbZGGYoWvZ58BcxDmiMUU7ChMa1eC")) + .lockingChainIssue(Issue.XRP) + .build() + ) + .xChainClaimId(XChainClaimId.of(UnsignedLong.ONE)) + .index(Hash256.of("9F2C9E23343852036AFD323025A8506018ABF9D4DBAA746D61BF1CFB5C297D10")) + .build(); + + String json = "\n" + + "{\n" + + " \"Account\": \"r3nCVTbZGGYoWvZ58BcxDmiMUU7ChMa1eC\",\n" + + " \"Flags\": 0,\n" + + " \"LedgerEntryType\": \"Bridge\",\n" + + " \"OwnerNode\": \"0\",\n" + + " \"PreviousTxnID\": \"67A8A1B36C1B97BE3AAB6B19CB3A3069034877DE917FD1A71919EAE7548E5636\",\n" + + " \"PreviousTxnLgrSeq\": 102,\n" + + " \"SignatureReward\": \"204\",\n" + + " \"XChainAccountClaimCount\": \"0\",\n" + + " \"XChainAccountCreateCount\": \"0\",\n" + + " \"XChainBridge\": {\n" + + " \"IssuingChainDoor\": \"rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh\",\n" + + " \"IssuingChainIssue\": {\n" + + " \"currency\": \"XRP\"\n" + + " },\n" + + " \"LockingChainDoor\": \"r3nCVTbZGGYoWvZ58BcxDmiMUU7ChMa1eC\",\n" + + " \"LockingChainIssue\": {\n" + + " \"currency\": \"XRP\"\n" + + " }\n" + + " },\n" + + " \"XChainClaimID\": \"1\",\n" + + " \"index\": \"9F2C9E23343852036AFD323025A8506018ABF9D4DBAA746D61BF1CFB5C297D10\"\n" + + "}"; + + assertCanSerializeAndDeserialize(bridge, json); + } +} \ No newline at end of file diff --git a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/ledger/XChainOwnedClaimIdObjectTest.java b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/ledger/XChainOwnedClaimIdObjectTest.java new file mode 100644 index 000000000..6f3578aaf --- /dev/null +++ b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/ledger/XChainOwnedClaimIdObjectTest.java @@ -0,0 +1,122 @@ +package org.xrpl.xrpl4j.model.ledger; + +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.crypto.keys.PublicKey; +import org.xrpl.xrpl4j.model.AbstractJsonTest; +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.XChainBridge; +import org.xrpl.xrpl4j.model.transactions.XChainClaimId; +import org.xrpl.xrpl4j.model.transactions.XrpCurrencyAmount; + +class XChainOwnedClaimIdObjectTest extends AbstractJsonTest { + + @Test + void testJson() throws JSONException, JsonProcessingException { + XChainOwnedClaimIdObject object = XChainOwnedClaimIdObject.builder() + .account(Address.of("rBW1U7J9mEhEdk6dMHEFUjqQ7HW7WpaEMi")) + .otherChainSource(Address.of("r9oXrvBX5aDoyMGkoYvzazxDhYoWFUjz8p")) + .ownerNode("0") + .previousTransactionId(Hash256.of("1CFD80E9CF232B8EED62A52857DE97438D12230C06496932A81DEFA6E66070A6")) + .previousTransactionLedgerSequence(UnsignedInteger.valueOf(58673)) + .signatureReward(XrpCurrencyAmount.ofDrops(100)) + .xChainBridge( + XChainBridge.builder() + .issuingChainDoor(Address.of("rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh")) + .issuingChainIssue(Issue.XRP) + .lockingChainDoor(Address.of("rMAXACCrp3Y8PpswXcg3bKggHX76V3F8M4")) + .lockingChainIssue(Issue.XRP) + .build() + ) + .addXChainClaimAttestations( + XChainClaimAttestation.of( + XChainClaimProofSig.builder() + .amount(XrpCurrencyAmount.ofDrops(1000000)) + .attestationRewardAccount(Address.of("rfgjrgEJGDxfUY2U8VEDs7BnB1jiH3ofu6")) + .attestationSignerAccount(Address.of("rfsxNxZ6xB1nTPhTMwQajNnkCxWG8B714n")) + .destination(Address.of("rBW1U7J9mEhEdk6dMHEFUjqQ7HW7WpaEMi")) + .publicKey( + PublicKey.fromBase16EncodedPublicKey("025CA526EF20567A50FEC504589F949E0E3401C13EF76DD5FD1CC2850FA485BD7B") + ) + .wasLockingChainSend(true) + .build() + ), + XChainClaimAttestation.of( + XChainClaimProofSig.builder() + .amount( + IssuedCurrencyAmount.builder() + .value("1") + .issuer(Address.of("rESSoiapL4EmPZTos6ks9FDZ6pbf261b3g")) + .currency("USD") + .build() + ) + .attestationRewardAccount(Address.of("rUUL1tP523M8KimERqVS7sxb1tLLmpndyv")) + .attestationSignerAccount(Address.of("rEg5sHxZVTNwRL3BAdMwJatkmWDzHMmzDF")) + .publicKey( + PublicKey.fromBase16EncodedPublicKey("03D40434A6843638681E2F215310EBC4131AFB12EA85985DA073183B732525F7C9") + ) + .wasLockingChainSend(false) + .build() + ) + ) + .xChainClaimId(XChainClaimId.of(UnsignedLong.valueOf("b5", 16))) + .index(Hash256.of("20B136D7BF6D2E3D610E28E3E6BE09F5C8F4F0241BBF6E2D072AE1BACB1388F5")) + .build(); + + String json = "\n" + + "{\n" + + " \"Account\": \"rBW1U7J9mEhEdk6dMHEFUjqQ7HW7WpaEMi\",\n" + + " \"Flags\": 0,\n" + + " \"OtherChainSource\": \"r9oXrvBX5aDoyMGkoYvzazxDhYoWFUjz8p\",\n" + + " \"OwnerNode\": \"0\",\n" + + " \"PreviousTxnID\": \"1CFD80E9CF232B8EED62A52857DE97438D12230C06496932A81DEFA6E66070A6\",\n" + + " \"PreviousTxnLgrSeq\": 58673,\n" + + " \"SignatureReward\": \"100\",\n" + + " \"XChainBridge\": {\n" + + " \"IssuingChainDoor\": \"rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh\",\n" + + " \"IssuingChainIssue\": {\n" + + " \"currency\": \"XRP\"\n" + + " },\n" + + " \"LockingChainDoor\": \"rMAXACCrp3Y8PpswXcg3bKggHX76V3F8M4\",\n" + + " \"LockingChainIssue\": {\n" + + " \"currency\": \"XRP\"\n" + + " }\n" + + " },\n" + + " \"XChainClaimAttestations\": [\n" + + " {\n" + + " \"XChainClaimProofSig\": {\n" + + " \"Amount\": \"1000000\",\n" + + " \"AttestationRewardAccount\": \"rfgjrgEJGDxfUY2U8VEDs7BnB1jiH3ofu6\",\n" + + " \"AttestationSignerAccount\": \"rfsxNxZ6xB1nTPhTMwQajNnkCxWG8B714n\",\n" + + " \"Destination\": \"rBW1U7J9mEhEdk6dMHEFUjqQ7HW7WpaEMi\",\n" + + " \"PublicKey\": \"025CA526EF20567A50FEC504589F949E0E3401C13EF76DD5FD1CC2850FA485BD7B\",\n" + + " \"WasLockingChainSend\": 1\n" + + " }\n" + + " },\n" + + " {\n" + + " \"XChainClaimProofSig\": {\n" + + " \"Amount\": {\n" + + " \"currency\": \"USD\",\n" + + " \"issuer\": \"rESSoiapL4EmPZTos6ks9FDZ6pbf261b3g\",\n" + + " \"value\": \"1\"\n" + + " },\n" + + " \"AttestationRewardAccount\": \"rUUL1tP523M8KimERqVS7sxb1tLLmpndyv\",\n" + + " \"AttestationSignerAccount\": \"rEg5sHxZVTNwRL3BAdMwJatkmWDzHMmzDF\",\n" + + " \"PublicKey\": \"03D40434A6843638681E2F215310EBC4131AFB12EA85985DA073183B732525F7C9\",\n" + + " \"WasLockingChainSend\": 0\n" + + " }\n" + + " }\n" + + " ],\n" + + " \"XChainClaimID\": \"b5\",\n" + + " \"LedgerEntryType\": \"XChainOwnedClaimID\",\n" + + " \"index\": \"20B136D7BF6D2E3D610E28E3E6BE09F5C8F4F0241BBF6E2D072AE1BACB1388F5\"\n" + + "}"; + + assertCanSerializeAndDeserialize(object, json); + } +} \ No newline at end of file diff --git a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/ledger/XChainOwnedCreateAccountClaimIdObjectTest.java b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/ledger/XChainOwnedCreateAccountClaimIdObjectTest.java new file mode 100644 index 000000000..032e8a74e --- /dev/null +++ b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/ledger/XChainOwnedCreateAccountClaimIdObjectTest.java @@ -0,0 +1,123 @@ +package org.xrpl.xrpl4j.model.ledger; + +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.crypto.keys.PublicKey; +import org.xrpl.xrpl4j.model.AbstractJsonTest; +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.XChainBridge; +import org.xrpl.xrpl4j.model.transactions.XChainCount; +import org.xrpl.xrpl4j.model.transactions.XrpCurrencyAmount; + +class XChainOwnedCreateAccountClaimIdObjectTest extends AbstractJsonTest { + + @Test + void testJson() throws JSONException, JsonProcessingException { + XChainOwnedCreateAccountClaimIdObject object = XChainOwnedCreateAccountClaimIdObject.builder() + .account(Address.of("rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh")) + .ownerNode("0") + .previousTransactionId(Hash256.of("D6451F989A89E58C5E52C081D5C2DB34AE73035588968A6166151113A3B09E9A")) + .previousTransactionLedgerSequence(UnsignedInteger.valueOf(751272)) + .xChainAccountCreateCount(XChainCount.of(UnsignedLong.valueOf("2cf", 16))) + .xChainBridge( + XChainBridge.builder() + .issuingChainDoor(Address.of("rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh")) + .issuingChainIssue(Issue.XRP) + .lockingChainDoor(Address.of("rnQAXXWoFNN6PEqwqsdTngCtFPCrmfuqFJ")) + .lockingChainIssue(Issue.XRP) + .build() + ) + .addXChainCreateAccountAttestations( + XChainCreateAccountAttestation.of( + XChainCreateAccountProofSig.builder() + .amount(XrpCurrencyAmount.ofDrops(20000000)) + .attestationRewardAccount(Address.of("rDUQ7WUgMZ6V75v3CFb1tqm1XPizmkWtTm")) + .attestationSignerAccount(Address.of("rUNdUjNcQde1Ye3823hn4RWjBYJEZYye3x")) + .destination(Address.of("rESSoiapL4EmPZTos6ks9FDZ6pbf261b3g")) + .publicKey( + PublicKey.fromBase16EncodedPublicKey("0300C9F746EF04811BB5529F7E58ACECA6DC5CFD5FDFB42C55C8630FC981D37A4E") + ) + .signatureReward(XrpCurrencyAmount.ofDrops(100)) + .wasLockingChainSend(true) + .build() + ), + XChainCreateAccountAttestation.of( + XChainCreateAccountProofSig.builder() + .amount( + IssuedCurrencyAmount.builder() + .value("1") + .issuer(Address.of("rESSoiapL4EmPZTos6ks9FDZ6pbf261b3g")) + .currency("USD") + .build() + ) + .attestationRewardAccount(Address.of("rLsS3B2m23Ms4oydi1bzNEp4R4EVxTFMrU")) + .attestationSignerAccount(Address.of("rJMQeMMRjsKmSwJ4ewMhVMVq3mbxTBwT3a")) + .destination(Address.of("rESSoiapL4EmPZTos6ks9FDZ6pbf261b3g")) + .publicKey( + PublicKey.fromBase16EncodedPublicKey("02C39C1AD5DBE3702D7D6A4A115618F5A0105EA394A0BD52FFA0C4787C3CB626CD") + ) + .signatureReward(XrpCurrencyAmount.ofDrops(100)) + .wasLockingChainSend(false) + .build() + ) + ) + .index(Hash256.of("36AF95B8F602D97D3028968FAACEB5343435694990F1A0892BBB81DDCC033141")) + .build(); + + String json = "{\n" + + " \"Account\": \"rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh\",\n" + + " \"Flags\": 0,\n" + + " \"OwnerNode\": \"0\",\n" + + " \"PreviousTxnID\": \"D6451F989A89E58C5E52C081D5C2DB34AE73035588968A6166151113A3B09E9A\",\n" + + " \"PreviousTxnLgrSeq\": 751272,\n" + + " \"XChainAccountCreateCount\": \"2cf\",\n" + + " \"XChainBridge\": {\n" + + " \"IssuingChainDoor\": \"rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh\",\n" + + " \"IssuingChainIssue\": {\n" + + " \"currency\": \"XRP\"\n" + + " },\n" + + " \"LockingChainDoor\": \"rnQAXXWoFNN6PEqwqsdTngCtFPCrmfuqFJ\",\n" + + " \"LockingChainIssue\": {\n" + + " \"currency\": \"XRP\"\n" + + " }\n" + + " },\n" + + " \"XChainCreateAccountAttestations\": [\n" + + " {\n" + + " \"XChainCreateAccountProofSig\": {\n" + + " \"Amount\": \"20000000\",\n" + + " \"AttestationRewardAccount\": \"rDUQ7WUgMZ6V75v3CFb1tqm1XPizmkWtTm\",\n" + + " \"AttestationSignerAccount\": \"rUNdUjNcQde1Ye3823hn4RWjBYJEZYye3x\",\n" + + " \"Destination\": \"rESSoiapL4EmPZTos6ks9FDZ6pbf261b3g\",\n" + + " \"PublicKey\": \"0300C9F746EF04811BB5529F7E58ACECA6DC5CFD5FDFB42C55C8630FC981D37A4E\",\n" + + " \"SignatureReward\": \"100\",\n" + + " \"WasLockingChainSend\": 1\n" + + " }\n" + + " },\n" + + " {\n" + + " \"XChainCreateAccountProofSig\": {\n" + + " \"Amount\": {\n" + + " \"currency\": \"USD\"," + + " \"issuer\": \"rESSoiapL4EmPZTos6ks9FDZ6pbf261b3g\"," + + " \"value\": \"1\"" + + " },\n" + + " \"AttestationRewardAccount\": \"rLsS3B2m23Ms4oydi1bzNEp4R4EVxTFMrU\",\n" + + " \"AttestationSignerAccount\": \"rJMQeMMRjsKmSwJ4ewMhVMVq3mbxTBwT3a\",\n" + + " \"Destination\": \"rESSoiapL4EmPZTos6ks9FDZ6pbf261b3g\",\n" + + " \"PublicKey\": \"02C39C1AD5DBE3702D7D6A4A115618F5A0105EA394A0BD52FFA0C4787C3CB626CD\",\n" + + " \"SignatureReward\": \"100\",\n" + + " \"WasLockingChainSend\": 0\n" + + " }\n" + + " }\n" + + " ]," + + " \"LedgerEntryType\": \"XChainOwnedCreateAccountClaimID\",\n" + + " \"index\": \"36AF95B8F602D97D3028968FAACEB5343435694990F1A0892BBB81DDCC033141\"\n" + + "}"; + + assertCanSerializeAndDeserialize(object, json); + } +} \ No newline at end of file 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 1719a471a..7ae044486 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 @@ -705,7 +705,7 @@ private void deserializeFixtures(String fileName) throws IOException { assertThat(CreatedNode.class).isAssignableFrom(transactionMetadata.affectedNodes().get(i).getClass()); if (node.getValue().get("NewFields") != null) { MetaLedgerObject newFields = ((CreatedNode) transactionMetadata.affectedNodes().get(i)).newFields(); - assertThat(determineLedgerObjectType(node.getValue().get("LedgerEntryType").asText())) + assertThat(MetaLedgerEntryType.of(node.getValue().get("LedgerEntryType").asText()).ledgerObjectType()) .isAssignableFrom(newFields.getClass()); } } else if (node.getKey().equals("ModifiedNode")) { @@ -714,14 +714,14 @@ private void deserializeFixtures(String fileName) throws IOException { Optional previousFields = ((ModifiedNode) transactionMetadata.affectedNodes().get(i)) .previousFields(); assertThat(previousFields).isPresent(); - assertThat(determineLedgerObjectType(node.getValue().get("LedgerEntryType").asText())) + assertThat(MetaLedgerEntryType.of(node.getValue().get("LedgerEntryType").asText()).ledgerObjectType()) .isAssignableFrom(previousFields.get().getClass()); } if (node.getValue().get("FinalFields") != null) { Optional finalFields = ((ModifiedNode) transactionMetadata.affectedNodes().get(i)).finalFields(); assertThat(finalFields).isPresent(); - assertThat(determineLedgerObjectType(node.getValue().get("LedgerEntryType").asText())) + assertThat(MetaLedgerEntryType.of(node.getValue().get("LedgerEntryType").asText()).ledgerObjectType()) .isAssignableFrom(finalFields.get().getClass()); } } else if (node.getKey().equals("DeletedNode")) { @@ -729,7 +729,7 @@ private void deserializeFixtures(String fileName) throws IOException { if (node.getValue().get("FinalFields") != null) { MetaLedgerObject finalFields = ((DeletedNode) transactionMetadata.affectedNodes().get(i)) .finalFields(); - assertThat(determineLedgerObjectType(node.getValue().get("LedgerEntryType").asText())) + assertThat(MetaLedgerEntryType.of(node.getValue().get("LedgerEntryType").asText()).ledgerObjectType()) .isAssignableFrom(finalFields.getClass()); } } @@ -780,35 +780,4 @@ private File newFile(File destinationDir, ZipEntry zipEntry) throws IOException return destFile; } - - private Class determineLedgerObjectType(String ledgerEntryType) { - switch (ledgerEntryType) { - case "AccountRoot": - return MetaAccountRootObject.class; - case "Check": - return MetaCheckObject.class; - case "DepositPreauth": - return MetaDepositPreAuthObject.class; - case "Escrow": - return MetaEscrowObject.class; - case "NFTokenOffer": - return MetaNfTokenOfferObject.class; - case "Offer": - return MetaOfferObject.class; - case "PayChannel": - return MetaPayChannelObject.class; - case "RippleState": - return MetaRippleStateObject.class; - case "SignerList": - return MetaSignerListObject.class; - case "Ticket": - return MetaTicketObject.class; - case "NFTokenPage": - return MetaNfTokenPageObject.class; - case "AMM": - return MetaAmmObject.class; - default: - return MetaUnknownObject.class; - } - } } \ No newline at end of file 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 16e836144..77beee833 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 @@ -98,5 +98,21 @@ public void testTxTypeCapitalization() { assertThat(TransactionType.TRUST_SET.value()).isEqualTo("TrustSet"); assertThat(TransactionType.TICKET_CREATE.value()).isEqualTo("TicketCreate"); assertThat(TransactionType.UNL_MODIFY.value()).isEqualTo("UNLModify"); + assertThat(TransactionType.CLAWBACK.value()).isEqualTo("Clawback"); + assertThat(TransactionType.AMM_BID.value()).isEqualTo("AMMBid"); + assertThat(TransactionType.AMM_CREATE.value()).isEqualTo("AMMCreate"); + assertThat(TransactionType.AMM_DEPOSIT.value()).isEqualTo("AMMDeposit"); + assertThat(TransactionType.AMM_VOTE.value()).isEqualTo("AMMVote"); + assertThat(TransactionType.AMM_WITHDRAW.value()).isEqualTo("AMMWithdraw"); + assertThat(TransactionType.AMM_DELETE.value()).isEqualTo("AMMDelete"); + assertThat(TransactionType.XCHAIN_ACCOUNT_CREATE_COMMIT.value()).isEqualTo("XChainAccountCreateCommit"); + assertThat(TransactionType.XCHAIN_ADD_ACCOUNT_CREATE_ATTESTATION.value()) + .isEqualTo("XChainAddAccountCreateAttestation"); + assertThat(TransactionType.XCHAIN_ADD_CLAIM_ATTESTATION.value()).isEqualTo("XChainAddClaimAttestation"); + assertThat(TransactionType.XCHAIN_CLAIM.value()).isEqualTo("XChainClaim"); + assertThat(TransactionType.XCHAIN_COMMIT.value()).isEqualTo("XChainCommit"); + assertThat(TransactionType.XCHAIN_CREATE_BRIDGE.value()).isEqualTo("XChainCreateBridge"); + assertThat(TransactionType.XCHAIN_CREATE_CLAIM_ID.value()).isEqualTo("XChainCreateClaimID"); + assertThat(TransactionType.XCHAIN_MODIFY_BRIDGE.value()).isEqualTo("XChainModifyBridge"); } } \ No newline at end of file diff --git a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/XChainAccountCreateCommitTest.java b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/XChainAccountCreateCommitTest.java new file mode 100644 index 000000000..9996af22a --- /dev/null +++ b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/XChainAccountCreateCommitTest.java @@ -0,0 +1,216 @@ +package org.xrpl.xrpl4j.model.transactions; + +import static org.xrpl.xrpl4j.crypto.TestConstants.ED_PUBLIC_KEY; + +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.flags.TransactionFlags; +import org.xrpl.xrpl4j.model.ledger.Issue; + +class XChainAccountCreateCommitTest extends AbstractJsonTest { + + @Test + void testJsonWithEmptyFlags() throws JSONException, JsonProcessingException { + XChainAccountCreateCommit commit = XChainAccountCreateCommit.builder() + .account(Address.of("rwEqJ2UaQHe7jihxGqmx6J4xdbGiiyMaGa")) + .fee(XrpCurrencyAmount.ofDrops(1)) + .sequence(UnsignedInteger.ONE) + .destination(Address.of("rD323VyRjgzzhY4bFpo44rmyh2neB5d8Mo")) + .amount(XrpCurrencyAmount.ofDrops(20000000)) + .signatureReward(XrpCurrencyAmount.ofDrops(100)) + .signingPublicKey(ED_PUBLIC_KEY) + .xChainBridge( + XChainBridge.builder() + .lockingChainDoor(Address.of("rMAXACCrp3Y8PpswXcg3bKggHX76V3F8M4")) + .lockingChainIssue(Issue.XRP) + .issuingChainDoor(Address.of("rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh")) + .issuingChainIssue( + Issue.builder() + .currency("TST") + .issuer(Address.of("rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh")) + .build() + ) + .build() + ) + .build(); + + String json = String.format("{\n" + + " \"Account\": \"rwEqJ2UaQHe7jihxGqmx6J4xdbGiiyMaGa\",\n" + + " \"Fee\": \"1\",\n" + + " \"Sequence\": 1,\n" + + " \"Destination\": \"rD323VyRjgzzhY4bFpo44rmyh2neB5d8Mo\",\n" + + " \"TransactionType\": \"XChainAccountCreateCommit\",\n" + + " \"Amount\": \"20000000\",\n" + + " \"SignatureReward\": \"100\",\n" + + " \"SigningPubKey\": \"%s\",\n" + + " \"XChainBridge\": {\n" + + " \"LockingChainDoor\": \"rMAXACCrp3Y8PpswXcg3bKggHX76V3F8M4\",\n" + + " \"LockingChainIssue\": {\n" + + " \"currency\": \"XRP\"\n" + + " },\n" + + " \"IssuingChainDoor\": \"rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh\",\n" + + " \"IssuingChainIssue\": {\n" + + " \"currency\": \"TST\",\n" + + " \"issuer\": \"rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh\"\n" + + " }\n" + + " }\n" + + "}", ED_PUBLIC_KEY.base16Value()); + + assertCanSerializeAndDeserialize(commit, json); + } + + @Test + void testJsonWithUnsetFlags() throws JSONException, JsonProcessingException { + XChainAccountCreateCommit commit = XChainAccountCreateCommit.builder() + .account(Address.of("rwEqJ2UaQHe7jihxGqmx6J4xdbGiiyMaGa")) + .fee(XrpCurrencyAmount.ofDrops(1)) + .sequence(UnsignedInteger.ONE) + .destination(Address.of("rD323VyRjgzzhY4bFpo44rmyh2neB5d8Mo")) + .amount(XrpCurrencyAmount.ofDrops(20000000)) + .signatureReward(XrpCurrencyAmount.ofDrops(100)) + .signingPublicKey(ED_PUBLIC_KEY) + .xChainBridge( + XChainBridge.builder() + .lockingChainDoor(Address.of("rMAXACCrp3Y8PpswXcg3bKggHX76V3F8M4")) + .lockingChainIssue(Issue.XRP) + .issuingChainDoor(Address.of("rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh")) + .issuingChainIssue( + Issue.builder() + .currency("TST") + .issuer(Address.of("rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh")) + .build() + ) + .build() + ) + .flags(TransactionFlags.UNSET) + .build(); + + String json = String.format("{\n" + + " \"Account\": \"rwEqJ2UaQHe7jihxGqmx6J4xdbGiiyMaGa\",\n" + + " \"Fee\": \"1\",\n" + + " \"Sequence\": 1,\n" + + " \"Destination\": \"rD323VyRjgzzhY4bFpo44rmyh2neB5d8Mo\",\n" + + " \"TransactionType\": \"XChainAccountCreateCommit\",\n" + + " \"Amount\": \"20000000\",\n" + + " \"SignatureReward\": \"100\",\n" + + " \"SigningPubKey\": \"%s\",\n" + + " \"Flags\": 0,\n" + + " \"XChainBridge\": {\n" + + " \"LockingChainDoor\": \"rMAXACCrp3Y8PpswXcg3bKggHX76V3F8M4\",\n" + + " \"LockingChainIssue\": {\n" + + " \"currency\": \"XRP\"\n" + + " },\n" + + " \"IssuingChainDoor\": \"rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh\",\n" + + " \"IssuingChainIssue\": {\n" + + " \"currency\": \"TST\",\n" + + " \"issuer\": \"rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh\"\n" + + " }\n" + + " }\n" + + "}", ED_PUBLIC_KEY.base16Value()); + + assertCanSerializeAndDeserialize(commit, json); + } + + @Test + void testJsonWithFullyCanonicalSigFlags() throws JSONException, JsonProcessingException { + XChainAccountCreateCommit commit = XChainAccountCreateCommit.builder() + .account(Address.of("rwEqJ2UaQHe7jihxGqmx6J4xdbGiiyMaGa")) + .fee(XrpCurrencyAmount.ofDrops(1)) + .sequence(UnsignedInteger.ONE) + .destination(Address.of("rD323VyRjgzzhY4bFpo44rmyh2neB5d8Mo")) + .amount(XrpCurrencyAmount.ofDrops(20000000)) + .signatureReward(XrpCurrencyAmount.ofDrops(100)) + .signingPublicKey(ED_PUBLIC_KEY) + .xChainBridge( + XChainBridge.builder() + .lockingChainDoor(Address.of("rMAXACCrp3Y8PpswXcg3bKggHX76V3F8M4")) + .lockingChainIssue(Issue.XRP) + .issuingChainDoor(Address.of("rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh")) + .issuingChainIssue( + Issue.builder() + .currency("TST") + .issuer(Address.of("rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh")) + .build() + ) + .build() + ) + .flags(TransactionFlags.FULLY_CANONICAL_SIG) + .build(); + + String json = String.format("{\n" + + " \"Account\": \"rwEqJ2UaQHe7jihxGqmx6J4xdbGiiyMaGa\",\n" + + " \"Fee\": \"1\",\n" + + " \"Sequence\": 1,\n" + + " \"Destination\": \"rD323VyRjgzzhY4bFpo44rmyh2neB5d8Mo\",\n" + + " \"TransactionType\": \"XChainAccountCreateCommit\",\n" + + " \"Amount\": \"20000000\",\n" + + " \"SignatureReward\": \"100\",\n" + + " \"SigningPubKey\": \"%s\",\n" + + " \"Flags\": %s,\n" + + " \"XChainBridge\": {\n" + + " \"LockingChainDoor\": \"rMAXACCrp3Y8PpswXcg3bKggHX76V3F8M4\",\n" + + " \"LockingChainIssue\": {\n" + + " \"currency\": \"XRP\"\n" + + " },\n" + + " \"IssuingChainDoor\": \"rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh\",\n" + + " \"IssuingChainIssue\": {\n" + + " \"currency\": \"TST\",\n" + + " \"issuer\": \"rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh\"\n" + + " }\n" + + " }\n" + + "}", ED_PUBLIC_KEY.base16Value(), TransactionFlags.FULLY_CANONICAL_SIG); + + assertCanSerializeAndDeserialize(commit, json); + } + + @Test + void testJsonWithEmptySignatureReward() throws JSONException, JsonProcessingException { + XChainAccountCreateCommit commit = XChainAccountCreateCommit.builder() + .account(Address.of("rwEqJ2UaQHe7jihxGqmx6J4xdbGiiyMaGa")) + .fee(XrpCurrencyAmount.ofDrops(1)) + .sequence(UnsignedInteger.ONE) + .destination(Address.of("rD323VyRjgzzhY4bFpo44rmyh2neB5d8Mo")) + .amount(XrpCurrencyAmount.ofDrops(20000000)) + .signingPublicKey(ED_PUBLIC_KEY) + .xChainBridge( + XChainBridge.builder() + .lockingChainDoor(Address.of("rMAXACCrp3Y8PpswXcg3bKggHX76V3F8M4")) + .lockingChainIssue(Issue.XRP) + .issuingChainDoor(Address.of("rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh")) + .issuingChainIssue( + Issue.builder() + .currency("TST") + .issuer(Address.of("rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh")) + .build() + ) + .build() + ) + .build(); + + String json = String.format("{\n" + + " \"Account\": \"rwEqJ2UaQHe7jihxGqmx6J4xdbGiiyMaGa\",\n" + + " \"Fee\": \"1\",\n" + + " \"Sequence\": 1,\n" + + " \"Destination\": \"rD323VyRjgzzhY4bFpo44rmyh2neB5d8Mo\",\n" + + " \"TransactionType\": \"XChainAccountCreateCommit\",\n" + + " \"Amount\": \"20000000\",\n" + + " \"SigningPubKey\": \"%s\",\n" + + " \"XChainBridge\": {\n" + + " \"LockingChainDoor\": \"rMAXACCrp3Y8PpswXcg3bKggHX76V3F8M4\",\n" + + " \"LockingChainIssue\": {\n" + + " \"currency\": \"XRP\"\n" + + " },\n" + + " \"IssuingChainDoor\": \"rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh\",\n" + + " \"IssuingChainIssue\": {\n" + + " \"currency\": \"TST\",\n" + + " \"issuer\": \"rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh\"\n" + + " }\n" + + " }\n" + + "}", ED_PUBLIC_KEY.base16Value()); + + assertCanSerializeAndDeserialize(commit, json); + } +} \ No newline at end of file diff --git a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/XChainAddAccountCreateAttestationTest.java b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/XChainAddAccountCreateAttestationTest.java new file mode 100644 index 000000000..dbb05e850 --- /dev/null +++ b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/XChainAddAccountCreateAttestationTest.java @@ -0,0 +1,233 @@ +package org.xrpl.xrpl4j.model.transactions; + +import static org.xrpl.xrpl4j.crypto.TestConstants.ED_PUBLIC_KEY; + +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.crypto.keys.PublicKey; +import org.xrpl.xrpl4j.crypto.signing.Signature; +import org.xrpl.xrpl4j.model.AbstractJsonTest; +import org.xrpl.xrpl4j.model.flags.TransactionFlags; +import org.xrpl.xrpl4j.model.ledger.Issue; + +class XChainAddAccountCreateAttestationTest extends AbstractJsonTest { + + @Test + void testJsonWithEmptyFlags() throws JSONException, JsonProcessingException { + XChainAddAccountCreateAttestation transaction = baseBuilder().build(); + String json = String.format("{\n" + + " \"Account\": \"rDr5okqGKmMpn44Bbhe5WAfDQx8e9XquEv\",\n" + + " \"TransactionType\": \"XChainAddAccountCreateAttestation\",\n" + + " \"OtherChainSource\": \"rUzB7yg1LcFa7m3q1hfrjr5w53vcWzNh3U\",\n" + + " \"Destination\": \"rJMfWNVbyjcCtds8kpoEjEbYQ41J5B6MUd\",\n" + + " \"Amount\": \"2000000000\",\n" + + " \"PublicKey\": \"EDF7C3F9C80C102AF6D241752B37356E91ED454F26A35C567CF6F8477960F66614\",\n" + + " \"Signature\": \"F95675BA8FDA21030DE1B687937A79E8491CE51832D6BEEBC071484FA5AF5B8A0E9AFF11A4AA46F09EC" + + "FFB04C6A8DAE8284AF3ED8128C7D0046D842448478500\",\n" + + " \"WasLockingChainSend\": 1,\n" + + " \"AttestationRewardAccount\": \"rpFp36UHW6FpEcZjZqq5jSJWY6UCj3k4Es\",\n" + + " \"AttestationSignerAccount\": \"rpWLegmW9WrFBzHUj7brhQNZzrxgLj9oxw\",\n" + + " \"XChainAccountCreateCount\": \"2\",\n" + + " \"SignatureReward\": \"204\",\n" + + " \"Sequence\": 1,\n" + + " \"SigningPubKey\": %s,\n" + + " \"XChainBridge\": {\n" + + " \"LockingChainDoor\": \"r3nCVTbZGGYoWvZ58BcxDmiMUU7ChMa1eC\",\n" + + " \"LockingChainIssue\": {\n" + + " \"currency\": \"XRP\"\n" + + " },\n" + + " \"IssuingChainDoor\": \"rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh\",\n" + + " \"IssuingChainIssue\": {\n" + + " \"currency\": \"XRP\"\n" + + " }\n" + + " },\n" + + " \"Fee\": \"20\"\n" + + "}", ED_PUBLIC_KEY.base16Value()); + + assertCanSerializeAndDeserialize(transaction, json); + } + + @Test + void testJsonWithUnsetFlags() throws JSONException, JsonProcessingException { + XChainAddAccountCreateAttestation transaction = baseBuilder() + .flags(TransactionFlags.UNSET) + .build(); + String json = String.format("{\n" + + " \"Account\": \"rDr5okqGKmMpn44Bbhe5WAfDQx8e9XquEv\",\n" + + " \"TransactionType\": \"XChainAddAccountCreateAttestation\",\n" + + " \"OtherChainSource\": \"rUzB7yg1LcFa7m3q1hfrjr5w53vcWzNh3U\",\n" + + " \"Destination\": \"rJMfWNVbyjcCtds8kpoEjEbYQ41J5B6MUd\",\n" + + " \"Amount\": \"2000000000\",\n" + + " \"PublicKey\": \"EDF7C3F9C80C102AF6D241752B37356E91ED454F26A35C567CF6F8477960F66614\",\n" + + " \"Signature\": \"F95675BA8FDA21030DE1B687937A79E8491CE51832D6BEEBC071484FA5AF5B8A0E9AFF11A4AA46" + + "F09ECFFB04C6A8DAE8284AF3ED8128C7D0046D842448478500\",\n" + + " \"WasLockingChainSend\": 1,\n" + + " \"AttestationRewardAccount\": \"rpFp36UHW6FpEcZjZqq5jSJWY6UCj3k4Es\",\n" + + " \"AttestationSignerAccount\": \"rpWLegmW9WrFBzHUj7brhQNZzrxgLj9oxw\",\n" + + " \"XChainAccountCreateCount\": \"2\",\n" + + " \"SignatureReward\": \"204\",\n" + + " \"Sequence\": 1,\n" + + " \"SigningPubKey\": %s,\n" + + " \"Flags\": 0,\n" + + " \"XChainBridge\": {\n" + + " \"LockingChainDoor\": \"r3nCVTbZGGYoWvZ58BcxDmiMUU7ChMa1eC\",\n" + + " \"LockingChainIssue\": {\n" + + " \"currency\": \"XRP\"\n" + + " },\n" + + " \"IssuingChainDoor\": \"rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh\",\n" + + " \"IssuingChainIssue\": {\n" + + " \"currency\": \"XRP\"\n" + + " }\n" + + " },\n" + + " \"Fee\": \"20\"\n" + + "}", ED_PUBLIC_KEY.base16Value()); + + assertCanSerializeAndDeserialize(transaction, json); + } + + @Test + void testJsonWithFullyCanonicalSig() throws JSONException, JsonProcessingException { + XChainAddAccountCreateAttestation transaction = baseBuilder() + .flags(TransactionFlags.FULLY_CANONICAL_SIG) + .build(); + String json = String.format("{\n" + + " \"Account\": \"rDr5okqGKmMpn44Bbhe5WAfDQx8e9XquEv\",\n" + + " \"TransactionType\": \"XChainAddAccountCreateAttestation\",\n" + + " \"OtherChainSource\": \"rUzB7yg1LcFa7m3q1hfrjr5w53vcWzNh3U\",\n" + + " \"Destination\": \"rJMfWNVbyjcCtds8kpoEjEbYQ41J5B6MUd\",\n" + + " \"Amount\": \"2000000000\",\n" + + " \"PublicKey\": \"EDF7C3F9C80C102AF6D241752B37356E91ED454F26A35C567CF6F8477960F66614\",\n" + + " \"Signature\": \"F95675BA8FDA21030DE1B687937A79E8491CE51832D6BEEBC071484FA5AF5B8A0E9AFF11A4AA46F09ECFF" + + "B04C6A8DAE8284AF3ED8128C7D0046D842448478500\",\n" + + " \"WasLockingChainSend\": 1,\n" + + " \"AttestationRewardAccount\": \"rpFp36UHW6FpEcZjZqq5jSJWY6UCj3k4Es\",\n" + + " \"AttestationSignerAccount\": \"rpWLegmW9WrFBzHUj7brhQNZzrxgLj9oxw\",\n" + + " \"XChainAccountCreateCount\": \"2\",\n" + + " \"SignatureReward\": \"204\",\n" + + " \"Sequence\": 1,\n" + + " \"SigningPubKey\": %s,\n" + + " \"Flags\": %s,\n" + + " \"XChainBridge\": {\n" + + " \"LockingChainDoor\": \"r3nCVTbZGGYoWvZ58BcxDmiMUU7ChMa1eC\",\n" + + " \"LockingChainIssue\": {\n" + + " \"currency\": \"XRP\"\n" + + " },\n" + + " \"IssuingChainDoor\": \"rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh\",\n" + + " \"IssuingChainIssue\": {\n" + + " \"currency\": \"XRP\"\n" + + " }\n" + + " },\n" + + " \"Fee\": \"20\"\n" + + "}", ED_PUBLIC_KEY.base16Value(), TransactionFlags.FULLY_CANONICAL_SIG); + + assertCanSerializeAndDeserialize(transaction, json); + } + + @Test + void testJsonWithWasLockingChainSendTrue() throws JSONException, JsonProcessingException { + XChainAddAccountCreateAttestation transaction = baseBuilder() + .wasLockingChainSend(true) + .build(); + String json = String.format("{\n" + + " \"Account\": \"rDr5okqGKmMpn44Bbhe5WAfDQx8e9XquEv\",\n" + + " \"TransactionType\": \"XChainAddAccountCreateAttestation\",\n" + + " \"OtherChainSource\": \"rUzB7yg1LcFa7m3q1hfrjr5w53vcWzNh3U\",\n" + + " \"Destination\": \"rJMfWNVbyjcCtds8kpoEjEbYQ41J5B6MUd\",\n" + + " \"Amount\": \"2000000000\",\n" + + " \"PublicKey\": \"EDF7C3F9C80C102AF6D241752B37356E91ED454F26A35C567CF6F8477960F66614\",\n" + + " \"Signature\": \"F95675BA8FDA21030DE1B687937A79E8491CE51832D6BEEBC071484FA5AF5B8A0E9AFF11A4AA46F09E" + + "CFFB04C6A8DAE8284AF3ED8128C7D0046D842448478500\",\n" + + " \"WasLockingChainSend\": 1,\n" + + " \"AttestationRewardAccount\": \"rpFp36UHW6FpEcZjZqq5jSJWY6UCj3k4Es\",\n" + + " \"AttestationSignerAccount\": \"rpWLegmW9WrFBzHUj7brhQNZzrxgLj9oxw\",\n" + + " \"XChainAccountCreateCount\": \"2\",\n" + + " \"SignatureReward\": \"204\",\n" + + " \"Sequence\": 1,\n" + + " \"SigningPubKey\": %s,\n" + + " \"XChainBridge\": {\n" + + " \"LockingChainDoor\": \"r3nCVTbZGGYoWvZ58BcxDmiMUU7ChMa1eC\",\n" + + " \"LockingChainIssue\": {\n" + + " \"currency\": \"XRP\"\n" + + " },\n" + + " \"IssuingChainDoor\": \"rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh\",\n" + + " \"IssuingChainIssue\": {\n" + + " \"currency\": \"XRP\"\n" + + " }\n" + + " },\n" + + " \"Fee\": \"20\"\n" + + "}", ED_PUBLIC_KEY.base16Value()); + + assertCanSerializeAndDeserialize(transaction, json); + } + + @Test + void testJsonWithWasLockingChainSendFalse() throws JSONException, JsonProcessingException { + XChainAddAccountCreateAttestation transaction = baseBuilder() + .wasLockingChainSend(false) + .build(); + String json = String.format("{\n" + + " \"Account\": \"rDr5okqGKmMpn44Bbhe5WAfDQx8e9XquEv\",\n" + + " \"TransactionType\": \"XChainAddAccountCreateAttestation\",\n" + + " \"OtherChainSource\": \"rUzB7yg1LcFa7m3q1hfrjr5w53vcWzNh3U\",\n" + + " \"Destination\": \"rJMfWNVbyjcCtds8kpoEjEbYQ41J5B6MUd\",\n" + + " \"Amount\": \"2000000000\",\n" + + " \"PublicKey\": \"EDF7C3F9C80C102AF6D241752B37356E91ED454F26A35C567CF6F8477960F66614\",\n" + + " \"Signature\": \"F95675BA8FDA21030DE1B687937A79E8491CE51832D6BEEBC071484FA5AF5B8A0E9AFF11A4AA46F0" + + "9ECFFB04C6A8DAE8284AF3ED8128C7D0046D842448478500\",\n" + + " \"WasLockingChainSend\": 0,\n" + + " \"AttestationRewardAccount\": \"rpFp36UHW6FpEcZjZqq5jSJWY6UCj3k4Es\",\n" + + " \"AttestationSignerAccount\": \"rpWLegmW9WrFBzHUj7brhQNZzrxgLj9oxw\",\n" + + " \"XChainAccountCreateCount\": \"2\",\n" + + " \"SignatureReward\": \"204\",\n" + + " \"Sequence\": 1,\n" + + " \"SigningPubKey\": %s,\n" + + " \"XChainBridge\": {\n" + + " \"LockingChainDoor\": \"r3nCVTbZGGYoWvZ58BcxDmiMUU7ChMa1eC\",\n" + + " \"LockingChainIssue\": {\n" + + " \"currency\": \"XRP\"\n" + + " },\n" + + " \"IssuingChainDoor\": \"rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh\",\n" + + " \"IssuingChainIssue\": {\n" + + " \"currency\": \"XRP\"\n" + + " }\n" + + " },\n" + + " \"Fee\": \"20\"\n" + + "}", ED_PUBLIC_KEY.base16Value()); + + assertCanSerializeAndDeserialize(transaction, json); + } + + private ImmutableXChainAddAccountCreateAttestation.Builder baseBuilder() { + return XChainAddAccountCreateAttestation.builder() + .account(Address.of("rDr5okqGKmMpn44Bbhe5WAfDQx8e9XquEv")) + .otherChainSource(Address.of("rUzB7yg1LcFa7m3q1hfrjr5w53vcWzNh3U")) + .destination(Address.of("rJMfWNVbyjcCtds8kpoEjEbYQ41J5B6MUd")) + .amount(XrpCurrencyAmount.ofDrops(2000000000)) + .publicKey( + PublicKey.fromBase16EncodedPublicKey("EDF7C3F9C80C102AF6D241752B37356E91ED454F26A35C567CF6F8477960F66614") + ) + .signature( + Signature.fromBase16("F95675BA8FDA21030DE1B687937A79E8491CE51832D6BEEBC071484FA5AF5B8A0E" + + "9AFF11A4AA46F09ECFFB04C6A8DAE8284AF3ED8128C7D0046D842448478500") + ) + .wasLockingChainSend(true) + .attestationRewardAccount(Address.of("rpFp36UHW6FpEcZjZqq5jSJWY6UCj3k4Es")) + .attestationSignerAccount(Address.of("rpWLegmW9WrFBzHUj7brhQNZzrxgLj9oxw")) + .xChainAccountCreateCount(XChainCount.of(UnsignedLong.valueOf(2))) + .signatureReward(XrpCurrencyAmount.ofDrops(204)) + .xChainBridge( + XChainBridge.builder() + .lockingChainDoor(Address.of("r3nCVTbZGGYoWvZ58BcxDmiMUU7ChMa1eC")) + .lockingChainIssue(Issue.XRP) + .issuingChainDoor(Address.of("rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh")) + .issuingChainIssue(Issue.XRP) + .build() + ) + .fee(XrpCurrencyAmount.ofDrops(20)) + .sequence(UnsignedInteger.ONE) + .signingPublicKey(ED_PUBLIC_KEY); + } +} \ No newline at end of file diff --git a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/XChainAddClaimAttestationTest.java b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/XChainAddClaimAttestationTest.java new file mode 100644 index 000000000..d1c158446 --- /dev/null +++ b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/XChainAddClaimAttestationTest.java @@ -0,0 +1,239 @@ +package org.xrpl.xrpl4j.model.transactions; + +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.crypto.keys.PublicKey; +import org.xrpl.xrpl4j.crypto.signing.Signature; +import org.xrpl.xrpl4j.model.AbstractJsonTest; +import org.xrpl.xrpl4j.model.flags.TransactionFlags; +import org.xrpl.xrpl4j.model.ledger.Issue; + +import java.util.Optional; + +class XChainAddClaimAttestationTest extends AbstractJsonTest { + + @Test + void testJsonWithFalseWasLockingChainSend() throws JSONException, JsonProcessingException { + XChainAddClaimAttestation attestation = baseBuilder() + .wasLockingChainSend(false) + .build(); + + String json = "{\n" + + " \"Account\": \"rsqvD8WFFEBBv4nztpoW9YYXJ7eRzLrtc3\",\n" + + " \"Amount\": \"10000000\",\n" + + " \"AttestationRewardAccount\": \"rsqvD8WFFEBBv4nztpoW9YYXJ7eRzLrtc3\",\n" + + " \"AttestationSignerAccount\": \"rsqvD8WFFEBBv4nztpoW9YYXJ7eRzLrtc3\",\n" + + " \"Destination\": \"rJdTJRJZ6GXCCRaamHJgEqVzB7Zy4557Pi\",\n" + + " \"Fee\": \"20\",\n" + + " \"LastLedgerSequence\": 19,\n" + + " \"OtherChainSource\": \"raFcdz1g8LWJDJWJE2ZKLRGdmUmsTyxaym\",\n" + + " \"PublicKey\": \"ED7541DEC700470F54276C90C333A13CDBB5D341FD43C60CEA12170F6D6D4E1136\",\n" + + " \"Sequence\": 9,\n" + + " \"Signature\": \"7C175050B08000AD35EEB2D87E16CD3F95A0AEEBF2A049474275153D9D4DD44528FE99AA5" + + "0E71660A15B0B768E1B90E609BBD5DC7AFAFD45D9705D72D40EA10C\",\n" + + " \"SigningPubKey\": \"ED0406B134786FE0751717226657F7BF8AFE96442C05D28ACEC66FB64852BA604C\",\n" + + " \"TransactionType\": \"XChainAddClaimAttestation\",\n" + + " \"WasLockingChainSend\": 0,\n" + + " \"XChainBridge\": {\n" + + " \"IssuingChainDoor\": \"rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh\",\n" + + " \"IssuingChainIssue\": {\n" + + " \"currency\": \"XRP\"\n" + + " },\n" + + " \"LockingChainDoor\": \"rDJVtEuDKr4rj1B3qtW7R5TVWdXV2DY7Qg\",\n" + + " \"LockingChainIssue\": {\n" + + " \"currency\": \"XRP\"\n" + + " }\n" + + " },\n" + + " \"XChainClaimID\": \"1\"\n" + + " }"; + + assertCanSerializeAndDeserialize(attestation, json); + } + + @Test + void testJsonWithEmptyDestination() throws JSONException, JsonProcessingException { + XChainAddClaimAttestation attestation = baseBuilder() + .destination(Optional.empty()) + .build(); + + String json = "{\n" + + " \"Account\": \"rsqvD8WFFEBBv4nztpoW9YYXJ7eRzLrtc3\",\n" + + " \"Amount\": \"10000000\",\n" + + " \"AttestationRewardAccount\": \"rsqvD8WFFEBBv4nztpoW9YYXJ7eRzLrtc3\",\n" + + " \"AttestationSignerAccount\": \"rsqvD8WFFEBBv4nztpoW9YYXJ7eRzLrtc3\",\n" + + " \"Fee\": \"20\",\n" + + " \"LastLedgerSequence\": 19,\n" + + " \"OtherChainSource\": \"raFcdz1g8LWJDJWJE2ZKLRGdmUmsTyxaym\",\n" + + " \"PublicKey\": \"ED7541DEC700470F54276C90C333A13CDBB5D341FD43C60CEA12170F6D6D4E1136\",\n" + + " \"Sequence\": 9,\n" + + " \"Signature\": \"7C175050B08000AD35EEB2D87E16CD3F95A0AEEBF2A049474275153D9D4DD44528FE99AA50E716" + + "60A15B0B768E1B90E609BBD5DC7AFAFD45D9705D72D40EA10C\",\n" + + " \"SigningPubKey\": \"ED0406B134786FE0751717226657F7BF8AFE96442C05D28ACEC66FB64852BA604C\",\n" + + " \"TransactionType\": \"XChainAddClaimAttestation\",\n" + + " \"WasLockingChainSend\": 1,\n" + + " \"XChainBridge\": {\n" + + " \"IssuingChainDoor\": \"rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh\",\n" + + " \"IssuingChainIssue\": {\n" + + " \"currency\": \"XRP\"\n" + + " },\n" + + " \"LockingChainDoor\": \"rDJVtEuDKr4rj1B3qtW7R5TVWdXV2DY7Qg\",\n" + + " \"LockingChainIssue\": {\n" + + " \"currency\": \"XRP\"\n" + + " }\n" + + " },\n" + + " \"XChainClaimID\": \"1\"\n" + + " }"; + + assertCanSerializeAndDeserialize(attestation, json); + } + + @Test + void testJsonWithEmptyFlags() throws JSONException, JsonProcessingException { + XChainAddClaimAttestation attestation = baseBuilder().build(); + + String json = "{\n" + + " \"Account\": \"rsqvD8WFFEBBv4nztpoW9YYXJ7eRzLrtc3\",\n" + + " \"Amount\": \"10000000\",\n" + + " \"AttestationRewardAccount\": \"rsqvD8WFFEBBv4nztpoW9YYXJ7eRzLrtc3\",\n" + + " \"AttestationSignerAccount\": \"rsqvD8WFFEBBv4nztpoW9YYXJ7eRzLrtc3\",\n" + + " \"Destination\": \"rJdTJRJZ6GXCCRaamHJgEqVzB7Zy4557Pi\",\n" + + " \"Fee\": \"20\",\n" + + " \"LastLedgerSequence\": 19,\n" + + " \"OtherChainSource\": \"raFcdz1g8LWJDJWJE2ZKLRGdmUmsTyxaym\",\n" + + " \"PublicKey\": \"ED7541DEC700470F54276C90C333A13CDBB5D341FD43C60CEA12170F6D6D4E1136\",\n" + + " \"Sequence\": 9,\n" + + " \"Signature\": \"7C175050B08000AD35EEB2D87E16CD3F95A0AEEBF2A049474275153D9D4DD44528FE99AA50E7166" + + "0A15B0B768E1B90E609BBD5DC7AFAFD45D9705D72D40EA10C\",\n" + + " \"SigningPubKey\": \"ED0406B134786FE0751717226657F7BF8AFE96442C05D28ACEC66FB64852BA604C\",\n" + + " \"TransactionType\": \"XChainAddClaimAttestation\",\n" + + " \"WasLockingChainSend\": 1,\n" + + " \"XChainBridge\": {\n" + + " \"IssuingChainDoor\": \"rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh\",\n" + + " \"IssuingChainIssue\": {\n" + + " \"currency\": \"XRP\"\n" + + " },\n" + + " \"LockingChainDoor\": \"rDJVtEuDKr4rj1B3qtW7R5TVWdXV2DY7Qg\",\n" + + " \"LockingChainIssue\": {\n" + + " \"currency\": \"XRP\"\n" + + " }\n" + + " },\n" + + " \"XChainClaimID\": \"1\"\n" + + " }"; + + assertCanSerializeAndDeserialize(attestation, json); + } + + @Test + void testJsonWithUnsetFlags() throws JSONException, JsonProcessingException { + XChainAddClaimAttestation attestation = baseBuilder() + .flags(TransactionFlags.UNSET) + .build(); + + String json = "{\n" + + " \"Account\": \"rsqvD8WFFEBBv4nztpoW9YYXJ7eRzLrtc3\",\n" + + " \"Amount\": \"10000000\",\n" + + " \"AttestationRewardAccount\": \"rsqvD8WFFEBBv4nztpoW9YYXJ7eRzLrtc3\",\n" + + " \"AttestationSignerAccount\": \"rsqvD8WFFEBBv4nztpoW9YYXJ7eRzLrtc3\",\n" + + " \"Destination\": \"rJdTJRJZ6GXCCRaamHJgEqVzB7Zy4557Pi\",\n" + + " \"Fee\": \"20\",\n" + + " \"Flags\": 0,\n" + + " \"LastLedgerSequence\": 19,\n" + + " \"OtherChainSource\": \"raFcdz1g8LWJDJWJE2ZKLRGdmUmsTyxaym\",\n" + + " \"PublicKey\": \"ED7541DEC700470F54276C90C333A13CDBB5D341FD43C60CEA12170F6D6D4E1136\",\n" + + " \"Sequence\": 9,\n" + + " \"Signature\": \"7C175050B08000AD35EEB2D87E16CD3F95A0AEEBF2A049474275153D9D4DD44528FE99AA5" + + "0E71660A15B0B768E1B90E609BBD5DC7AFAFD45D9705D72D40EA10C\",\n" + + " \"SigningPubKey\": \"ED0406B134786FE0751717226657F7BF8AFE96442C05D28ACEC66FB64852BA604C\",\n" + + " \"TransactionType\": \"XChainAddClaimAttestation\",\n" + + " \"WasLockingChainSend\": 1,\n" + + " \"XChainBridge\": {\n" + + " \"IssuingChainDoor\": \"rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh\",\n" + + " \"IssuingChainIssue\": {\n" + + " \"currency\": \"XRP\"\n" + + " },\n" + + " \"LockingChainDoor\": \"rDJVtEuDKr4rj1B3qtW7R5TVWdXV2DY7Qg\",\n" + + " \"LockingChainIssue\": {\n" + + " \"currency\": \"XRP\"\n" + + " }\n" + + " },\n" + + " \"XChainClaimID\": \"1\"\n" + + " }"; + + assertCanSerializeAndDeserialize(attestation, json); + } + + @Test + void testJsonWithFullyCanonicalSigFlags() throws JSONException, JsonProcessingException { + XChainAddClaimAttestation attestation = baseBuilder() + .flags(TransactionFlags.FULLY_CANONICAL_SIG) + .build(); + + String json = String.format("{\n" + + " \"Account\": \"rsqvD8WFFEBBv4nztpoW9YYXJ7eRzLrtc3\",\n" + + " \"Amount\": \"10000000\",\n" + + " \"AttestationRewardAccount\": \"rsqvD8WFFEBBv4nztpoW9YYXJ7eRzLrtc3\",\n" + + " \"AttestationSignerAccount\": \"rsqvD8WFFEBBv4nztpoW9YYXJ7eRzLrtc3\",\n" + + " \"Destination\": \"rJdTJRJZ6GXCCRaamHJgEqVzB7Zy4557Pi\",\n" + + " \"Fee\": \"20\",\n" + + " \"Flags\": %s,\n" + + " \"LastLedgerSequence\": 19,\n" + + " \"OtherChainSource\": \"raFcdz1g8LWJDJWJE2ZKLRGdmUmsTyxaym\",\n" + + " \"PublicKey\": \"ED7541DEC700470F54276C90C333A13CDBB5D341FD43C60CEA12170F6D6D4E1136\",\n" + + " \"Sequence\": 9,\n" + + " \"Signature\": \"7C175050B08000AD35EEB2D87E16CD3F95A0AEEBF2A049474275153D9D4DD44528FE99A" + + "A50E71660A15B0B768E1B90E609BBD5DC7AFAFD45D9705D72D40EA10C\",\n" + + " \"SigningPubKey\": \"ED0406B134786FE0751717226657F7BF8AFE96442C05D28ACEC66FB64852BA604C\",\n" + + " \"TransactionType\": \"XChainAddClaimAttestation\",\n" + + " \"WasLockingChainSend\": 1,\n" + + " \"XChainBridge\": {\n" + + " \"IssuingChainDoor\": \"rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh\",\n" + + " \"IssuingChainIssue\": {\n" + + " \"currency\": \"XRP\"\n" + + " },\n" + + " \"LockingChainDoor\": \"rDJVtEuDKr4rj1B3qtW7R5TVWdXV2DY7Qg\",\n" + + " \"LockingChainIssue\": {\n" + + " \"currency\": \"XRP\"\n" + + " }\n" + + " },\n" + + " \"XChainClaimID\": \"1\"\n" + + " }", TransactionFlags.FULLY_CANONICAL_SIG); + + assertCanSerializeAndDeserialize(attestation, json); + } + + private ImmutableXChainAddClaimAttestation.Builder baseBuilder() { + return XChainAddClaimAttestation.builder() + .account(Address.of("rsqvD8WFFEBBv4nztpoW9YYXJ7eRzLrtc3")) + .amount(XrpCurrencyAmount.ofDrops(10000000)) + .attestationRewardAccount(Address.of("rsqvD8WFFEBBv4nztpoW9YYXJ7eRzLrtc3")) + .attestationSignerAccount(Address.of("rsqvD8WFFEBBv4nztpoW9YYXJ7eRzLrtc3")) + .destination(Address.of("rJdTJRJZ6GXCCRaamHJgEqVzB7Zy4557Pi")) + .fee(XrpCurrencyAmount.ofDrops(20)) + .lastLedgerSequence(UnsignedInteger.valueOf(19)) + .otherChainSource(Address.of("raFcdz1g8LWJDJWJE2ZKLRGdmUmsTyxaym")) + .sequence(UnsignedInteger.valueOf(9)) + .publicKey( + PublicKey.fromBase16EncodedPublicKey("ED7541DEC700470F54276C90C333A13CDBB5D341FD43C60CEA12170F6D6D4E1136") + ) + .signature( + Signature.fromBase16("7C175050B08000AD35EEB2D87E16CD3F95A0AEEBF2A049474275153D9D4DD44" + + "528FE99AA50E71660A15B0B768E1B90E609BBD5DC7AFAFD45D9705D72D40EA10C") + ) + .signingPublicKey( + PublicKey.fromBase16EncodedPublicKey("ED0406B134786FE0751717226657F7BF8AFE96442C05D28ACEC66FB64852BA604C") + ) + .wasLockingChainSend(true) + .xChainBridge( + XChainBridge.builder() + .issuingChainDoor(Address.of("rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh")) + .issuingChainIssue(Issue.XRP) + .lockingChainDoor(Address.of("rDJVtEuDKr4rj1B3qtW7R5TVWdXV2DY7Qg")) + .lockingChainIssue(Issue.XRP) + .build() + ) + .xChainClaimId(XChainClaimId.of(UnsignedLong.ONE)); + } +} \ No newline at end of file diff --git a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/XChainClaimIdTest.java b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/XChainClaimIdTest.java new file mode 100644 index 000000000..afe177207 --- /dev/null +++ b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/XChainClaimIdTest.java @@ -0,0 +1,76 @@ +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 com.google.common.primitives.UnsignedLong; +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 XChainClaimIdTest { + + ObjectMapper objectMapper = ObjectMapperFactory.create(); + + @Test + void testToString() { + XChainClaimId claimId = XChainClaimId.of(UnsignedLong.ZERO); + assertThat(claimId.toString()).isEqualTo("0"); + + XChainClaimId claimIdMax = XChainClaimId.of(UnsignedLong.MAX_VALUE); + assertThat(claimIdMax.toString()).isEqualTo("18446744073709551615"); + } + + @Test + void testJson() throws JsonProcessingException, JSONException { + XChainClaimId claimId = XChainClaimId.of(UnsignedLong.valueOf(1000)); + XChainClaimIdWrapper wrapper = XChainClaimIdWrapper.of(claimId); + + String json = "{\"claimId\": \"3e8\"}"; + assertSerializesAndDeserializes(wrapper, json); + } + + @Test + void testMaxJson() throws JSONException, JsonProcessingException { + XChainClaimId claimId = XChainClaimId.of(UnsignedLong.MAX_VALUE); + XChainClaimIdWrapper wrapper = XChainClaimIdWrapper.of(claimId); + + String json = "{\"claimId\": \"ffffffffffffffff\"}"; + assertSerializesAndDeserializes(wrapper, json); + } + + private void assertSerializesAndDeserializes( + XChainClaimIdWrapper wrapper, + String json + ) throws JsonProcessingException, JSONException { + String serialized = objectMapper.writeValueAsString(wrapper); + JSONAssert.assertEquals(json, serialized, JSONCompareMode.STRICT); + XChainClaimIdWrapper deserialized = objectMapper.readValue( + serialized, XChainClaimIdWrapper.class + ); + Assertions.assertThat(deserialized).isEqualTo(wrapper); + } + + @Value.Immutable + @JsonSerialize(as = ImmutableXChainClaimIdWrapper.class) + @JsonDeserialize(as = ImmutableXChainClaimIdWrapper.class) + interface XChainClaimIdWrapper { + + static XChainClaimIdWrapper of(XChainClaimId claimId) { + return ImmutableXChainClaimIdWrapper.builder().claimId(claimId).build(); + } + + XChainClaimId claimId(); + + } +} diff --git a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/XChainClaimTest.java b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/XChainClaimTest.java new file mode 100644 index 000000000..4426f54dd --- /dev/null +++ b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/XChainClaimTest.java @@ -0,0 +1,155 @@ +package org.xrpl.xrpl4j.model.transactions; + +import static org.xrpl.xrpl4j.crypto.TestConstants.ED_PUBLIC_KEY; + +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.flags.TransactionFlags; +import org.xrpl.xrpl4j.model.ledger.Issue; + +class XChainClaimTest extends AbstractJsonTest { + + @Test + void testJsonWithDestinationTag() throws JSONException, JsonProcessingException { + XChainClaim attestation = baseBuilder() + .destinationTag(UnsignedInteger.ONE) + .build(); + + String json = String.format("{\n" + + " \"Account\": \"rahDmoXrtPdh7sUdrPjini3gcnTVYjbjjw\",\n" + + " \"Amount\": \"10000\",\n" + + " \"Fee\": \"12\",\n" + + " \"Sequence\": 1,\n" + + " \"SigningPubKey\": %s,\n" + + " \"TransactionType\": \"XChainClaim\",\n" + + " \"XChainClaimID\": \"13f\",\n" + + " \"Destination\": \"rahDmoXrtPdh7sUdrPjini3gcnTVYjbjjw\",\n" + + " \"DestinationTag\": 1,\n" + + " \"XChainBridge\": {\n" + + " \"LockingChainDoor\": \"rMAXACCrp3Y8PpswXcg3bKggHX76V3F8M4\",\n" + + " \"LockingChainIssue\": {\n" + + " \"currency\": \"XRP\"\n" + + " },\n" + + " \"IssuingChainDoor\": \"rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh\",\n" + + " \"IssuingChainIssue\": {\n" + + " \"currency\": \"XRP\"\n" + + " }\n" + + " }\n" + + "}", ED_PUBLIC_KEY.base16Value()); + + assertCanSerializeAndDeserialize(attestation, json); + } + + @Test + void testJsonWithEmptyFlags() throws JSONException, JsonProcessingException { + XChainClaim attestation = baseBuilder().build(); + + String json = String.format("{\n" + + " \"Account\": \"rahDmoXrtPdh7sUdrPjini3gcnTVYjbjjw\",\n" + + " \"Amount\": \"10000\",\n" + + " \"Fee\": \"12\",\n" + + " \"Sequence\": 1,\n" + + " \"SigningPubKey\": %s,\n" + + " \"TransactionType\": \"XChainClaim\",\n" + + " \"XChainClaimID\": \"13f\",\n" + + " \"Destination\": \"rahDmoXrtPdh7sUdrPjini3gcnTVYjbjjw\",\n" + + " \"XChainBridge\": {\n" + + " \"LockingChainDoor\": \"rMAXACCrp3Y8PpswXcg3bKggHX76V3F8M4\",\n" + + " \"LockingChainIssue\": {\n" + + " \"currency\": \"XRP\"\n" + + " },\n" + + " \"IssuingChainDoor\": \"rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh\",\n" + + " \"IssuingChainIssue\": {\n" + + " \"currency\": \"XRP\"\n" + + " }\n" + + " }\n" + + "}", ED_PUBLIC_KEY.base16Value()); + + assertCanSerializeAndDeserialize(attestation, json); + } + + @Test + void testJsonWithUnsetFlags() throws JSONException, JsonProcessingException { + XChainClaim attestation = baseBuilder() + .flags(TransactionFlags.UNSET) + .build(); + + String json = String.format("{\n" + + " \"Account\": \"rahDmoXrtPdh7sUdrPjini3gcnTVYjbjjw\",\n" + + " \"Amount\": \"10000\",\n" + + " \"Fee\": \"12\",\n" + + " \"Sequence\": 1,\n" + + " \"SigningPubKey\": %s,\n" + + " \"Flags\": 0,\n" + + " \"TransactionType\": \"XChainClaim\",\n" + + " \"XChainClaimID\": \"13f\",\n" + + " \"Destination\": \"rahDmoXrtPdh7sUdrPjini3gcnTVYjbjjw\",\n" + + " \"XChainBridge\": {\n" + + " \"LockingChainDoor\": \"rMAXACCrp3Y8PpswXcg3bKggHX76V3F8M4\",\n" + + " \"LockingChainIssue\": {\n" + + " \"currency\": \"XRP\"\n" + + " },\n" + + " \"IssuingChainDoor\": \"rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh\",\n" + + " \"IssuingChainIssue\": {\n" + + " \"currency\": \"XRP\"\n" + + " }\n" + + " }\n" + + "}", ED_PUBLIC_KEY.base16Value()); + + assertCanSerializeAndDeserialize(attestation, json); + } + + @Test + void testJsonWithFullyCanonicalSigFlags() throws JSONException, JsonProcessingException { + XChainClaim attestation = baseBuilder() + .flags(TransactionFlags.FULLY_CANONICAL_SIG) + .build(); + + String json = String.format("{\n" + + " \"Account\": \"rahDmoXrtPdh7sUdrPjini3gcnTVYjbjjw\",\n" + + " \"Amount\": \"10000\",\n" + + " \"Fee\": \"12\",\n" + + " \"Sequence\": 1,\n" + + " \"SigningPubKey\": %s,\n" + + " \"Flags\": %s,\n" + + " \"TransactionType\": \"XChainClaim\",\n" + + " \"XChainClaimID\": \"13f\",\n" + + " \"Destination\": \"rahDmoXrtPdh7sUdrPjini3gcnTVYjbjjw\",\n" + + " \"XChainBridge\": {\n" + + " \"LockingChainDoor\": \"rMAXACCrp3Y8PpswXcg3bKggHX76V3F8M4\",\n" + + " \"LockingChainIssue\": {\n" + + " \"currency\": \"XRP\"\n" + + " },\n" + + " \"IssuingChainDoor\": \"rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh\",\n" + + " \"IssuingChainIssue\": {\n" + + " \"currency\": \"XRP\"\n" + + " }\n" + + " }\n" + + "}", ED_PUBLIC_KEY.base16Value(), TransactionFlags.FULLY_CANONICAL_SIG); + + assertCanSerializeAndDeserialize(attestation, json); + } + + private ImmutableXChainClaim.Builder baseBuilder() { + return XChainClaim.builder() + .account(Address.of("rahDmoXrtPdh7sUdrPjini3gcnTVYjbjjw")) + .fee(XrpCurrencyAmount.ofDrops(12)) + .sequence(UnsignedInteger.ONE) + .signingPublicKey(ED_PUBLIC_KEY) + .amount(XrpCurrencyAmount.ofDrops(10000)) + .xChainClaimId(XChainClaimId.of(UnsignedLong.valueOf(0x13F))) + .destination(Address.of("rahDmoXrtPdh7sUdrPjini3gcnTVYjbjjw")) + .xChainBridge( + XChainBridge.builder() + .lockingChainDoor(Address.of("rMAXACCrp3Y8PpswXcg3bKggHX76V3F8M4")) + .lockingChainIssue(Issue.XRP) + .issuingChainDoor(Address.of("rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh")) + .issuingChainIssue(Issue.XRP) + .build() + ); + } +} \ No newline at end of file diff --git a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/XChainCommitTest.java b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/XChainCommitTest.java new file mode 100644 index 000000000..270229447 --- /dev/null +++ b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/XChainCommitTest.java @@ -0,0 +1,159 @@ +package org.xrpl.xrpl4j.model.transactions; + +import static org.xrpl.xrpl4j.crypto.TestConstants.ED_PUBLIC_KEY; + +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.flags.TransactionFlags; +import org.xrpl.xrpl4j.model.ledger.Issue; + +class XChainCommitTest extends AbstractJsonTest { + + @Test + void testJsonWithIssuedCurrencyAmount() throws JSONException, JsonProcessingException { + XChainCommit commit = baseBuilder() + .amount( + IssuedCurrencyAmount.builder() + .currency("CNY") + .issuer(Address.of("r45dBj4S3VvMMYXxr9vHX4Z4Ma6ifPMCkK")) + .value("5000") + .build() + ) + .build(); + + String json = String.format("{\n" + + " \"Account\": \"rMTi57fNy2UkUb4RcdoUeJm7gjxVQvxzUo\",\n" + + " \"TransactionType\": \"XChainCommit\",\n" + + " \"XChainBridge\": {\n" + + " \"LockingChainDoor\": \"rMAXACCrp3Y8PpswXcg3bKggHX76V3F8M4\",\n" + + " \"LockingChainIssue\": {\n" + + " \"currency\": \"XRP\"\n" + + " },\n" + + " \"IssuingChainDoor\": \"rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh\",\n" + + " \"IssuingChainIssue\": {\n" + + " \"currency\": \"XRP\"\n" + + " }\n" + + " },\n" + + " \"Amount\": {" + + " \"currency\": \"CNY\",\n" + + " \"value\": \"5000\",\n" + + " \"issuer\": \"r45dBj4S3VvMMYXxr9vHX4Z4Ma6ifPMCkK\"\n" + + " },\n" + + " \"Fee\": \"10\",\n" + + " \"Sequence\": 1,\n" + + " \"SigningPubKey\": %s,\n" + + " \"XChainClaimID\": \"13f\"\n" + + "}", ED_PUBLIC_KEY.base16Value()); + + assertCanSerializeAndDeserialize(commit, json); + } + + @Test + void testJsonWithEmptyFlags() throws JSONException, JsonProcessingException { + XChainCommit commit = baseBuilder().build(); + + String json = String.format("{\n" + + " \"Account\": \"rMTi57fNy2UkUb4RcdoUeJm7gjxVQvxzUo\",\n" + + " \"TransactionType\": \"XChainCommit\",\n" + + " \"XChainBridge\": {\n" + + " \"LockingChainDoor\": \"rMAXACCrp3Y8PpswXcg3bKggHX76V3F8M4\",\n" + + " \"LockingChainIssue\": {\n" + + " \"currency\": \"XRP\"\n" + + " },\n" + + " \"IssuingChainDoor\": \"rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh\",\n" + + " \"IssuingChainIssue\": {\n" + + " \"currency\": \"XRP\"\n" + + " }\n" + + " },\n" + + " \"Amount\": \"10000\",\n" + + " \"Fee\": \"10\",\n" + + " \"Sequence\": 1,\n" + + " \"SigningPubKey\": %s,\n" + + " \"XChainClaimID\": \"13f\"\n" + + "}", ED_PUBLIC_KEY.base16Value()); + + assertCanSerializeAndDeserialize(commit, json); + } + + @Test + void testJsonWithUnsetFlags() throws JSONException, JsonProcessingException { + XChainCommit commit = baseBuilder() + .flags(TransactionFlags.UNSET) + .build(); + + String json = String.format("{\n" + + " \"Account\": \"rMTi57fNy2UkUb4RcdoUeJm7gjxVQvxzUo\",\n" + + " \"TransactionType\": \"XChainCommit\",\n" + + " \"XChainBridge\": {\n" + + " \"LockingChainDoor\": \"rMAXACCrp3Y8PpswXcg3bKggHX76V3F8M4\",\n" + + " \"LockingChainIssue\": {\n" + + " \"currency\": \"XRP\"\n" + + " },\n" + + " \"IssuingChainDoor\": \"rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh\",\n" + + " \"IssuingChainIssue\": {\n" + + " \"currency\": \"XRP\"\n" + + " }\n" + + " },\n" + + " \"Amount\": \"10000\",\n" + + " \"Fee\": \"10\",\n" + + " \"Sequence\": 1,\n" + + " \"SigningPubKey\": %s,\n" + + " \"Flags\": 0,\n" + + " \"XChainClaimID\": \"13f\"\n" + + "}", ED_PUBLIC_KEY.base16Value()); + + assertCanSerializeAndDeserialize(commit, json); + } + + @Test + void testJsonWithFullyCanonicalSigFlags() throws JSONException, JsonProcessingException { + XChainCommit commit = baseBuilder() + .flags(TransactionFlags.FULLY_CANONICAL_SIG) + .build(); + + String json = String.format("{\n" + + " \"Account\": \"rMTi57fNy2UkUb4RcdoUeJm7gjxVQvxzUo\",\n" + + " \"TransactionType\": \"XChainCommit\",\n" + + " \"XChainBridge\": {\n" + + " \"LockingChainDoor\": \"rMAXACCrp3Y8PpswXcg3bKggHX76V3F8M4\",\n" + + " \"LockingChainIssue\": {\n" + + " \"currency\": \"XRP\"\n" + + " },\n" + + " \"IssuingChainDoor\": \"rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh\",\n" + + " \"IssuingChainIssue\": {\n" + + " \"currency\": \"XRP\"\n" + + " }\n" + + " },\n" + + " \"Amount\": \"10000\",\n" + + " \"Fee\": \"10\",\n" + + " \"Sequence\": 1,\n" + + " \"SigningPubKey\": %s,\n" + + " \"Flags\": %s,\n" + + " \"XChainClaimID\": \"13f\"\n" + + "}", ED_PUBLIC_KEY.base16Value(), TransactionFlags.FULLY_CANONICAL_SIG); + + assertCanSerializeAndDeserialize(commit, json); + } + + private ImmutableXChainCommit.Builder baseBuilder() { + return XChainCommit.builder() + .fee(XrpCurrencyAmount.ofDrops(10)) + .sequence(UnsignedInteger.ONE) + .account(Address.of("rMTi57fNy2UkUb4RcdoUeJm7gjxVQvxzUo")) + .amount(XrpCurrencyAmount.ofDrops(10000)) + .xChainClaimId(XChainClaimId.of(UnsignedLong.valueOf(0x13f))) + .xChainBridge( + XChainBridge.builder() + .lockingChainDoor(Address.of("rMAXACCrp3Y8PpswXcg3bKggHX76V3F8M4")) + .lockingChainIssue(Issue.XRP) + .issuingChainDoor(Address.of("rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh")) + .issuingChainIssue(Issue.XRP) + .build() + ) + .signingPublicKey(ED_PUBLIC_KEY); + } +} \ No newline at end of file diff --git a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/XChainCountTest.java b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/XChainCountTest.java new file mode 100644 index 000000000..dffade43f --- /dev/null +++ b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/XChainCountTest.java @@ -0,0 +1,73 @@ +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.UnsignedLong; +import org.assertj.core.api.Assertions; +import org.immutables.value.Value; +import org.json.JSONException; +import org.junit.jupiter.api.Test; +import org.skyscreamer.jsonassert.JSONAssert; +import org.skyscreamer.jsonassert.JSONCompareMode; +import org.xrpl.xrpl4j.model.jackson.ObjectMapperFactory; + +public class XChainCountTest { + + ObjectMapper objectMapper = ObjectMapperFactory.create(); + + @Test + void testToString() { + XChainCount count = XChainCount.of(UnsignedLong.ZERO); + assertThat(count.toString()).isEqualTo("0"); + + XChainCount countMax = XChainCount.of(UnsignedLong.MAX_VALUE); + assertThat(countMax.toString()).isEqualTo("18446744073709551615"); + } + + @Test + void testJson() throws JsonProcessingException, JSONException { + XChainCount count = XChainCount.of(UnsignedLong.valueOf(1000)); + XChainCountWrapper wrapper = XChainCountWrapper.of(count); + + String json = "{\"count\": \"3e8\"}"; + assertSerializesAndDeserializes(wrapper, json); + } + + @Test + void testMaxJson() throws JSONException, JsonProcessingException { + XChainCount count = XChainCount.of(UnsignedLong.MAX_VALUE); + XChainCountWrapper wrapper = XChainCountWrapper.of(count); + + String json = "{\"count\": \"ffffffffffffffff\"}"; + assertSerializesAndDeserializes(wrapper, json); + } + + private void assertSerializesAndDeserializes( + XChainCountWrapper wrapper, + String json + ) throws JsonProcessingException, JSONException { + String serialized = objectMapper.writeValueAsString(wrapper); + JSONAssert.assertEquals(json, serialized, JSONCompareMode.STRICT); + XChainCountWrapper deserialized = objectMapper.readValue( + serialized, XChainCountWrapper.class + ); + Assertions.assertThat(deserialized).isEqualTo(wrapper); + } + + @Value.Immutable + @JsonSerialize(as = ImmutableXChainCountWrapper.class) + @JsonDeserialize(as = ImmutableXChainCountWrapper.class) + interface XChainCountWrapper { + + static XChainCountWrapper of(XChainCount count) { + return ImmutableXChainCountWrapper.builder().count(count).build(); + } + + XChainCount count(); + + } +} diff --git a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/XChainCreateBridgeTest.java b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/XChainCreateBridgeTest.java new file mode 100644 index 000000000..f4bc61714 --- /dev/null +++ b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/XChainCreateBridgeTest.java @@ -0,0 +1,123 @@ +package org.xrpl.xrpl4j.model.transactions; + +import static org.xrpl.xrpl4j.crypto.TestConstants.ED_PUBLIC_KEY; + +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.flags.TransactionFlags; +import org.xrpl.xrpl4j.model.ledger.Issue; + +class XChainCreateBridgeTest extends AbstractJsonTest { + + @Test + void testJsonWithMinAccountCreateAmount() throws JSONException, JsonProcessingException { + XChainCreateBridge createBridge = baseBuilder() + .minAccountCreateAmount(XrpCurrencyAmount.ofDrops(1000000)) + .build(); + + String json = String.format("\n" + + "{\n" + + " \"TransactionType\": \"XChainCreateBridge\",\n" + + " \"Account\": \"rhWQzvdmhf5vFS35vtKUSUwNZHGT53qQsg\",\n" + + " \"XChainBridge\": {\n" + + " \"LockingChainDoor\": \"rhWQzvdmhf5vFS35vtKUSUwNZHGT53qQsg\",\n" + + " \"LockingChainIssue\": {\n" + + " \"currency\": \"XRP\"\n" + + " },\n" + + " \"IssuingChainDoor\": \"rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh\",\n" + + " \"IssuingChainIssue\": {\n" + + " \"currency\": \"XRP\"\n" + + " }\n" + + " },\n" + + " \"SignatureReward\": \"200\",\n" + + " \"MinAccountCreateAmount\": \"1000000\",\n" + + " \"Fee\": \"10\",\n" + + " \"Sequence\": 1,\n" + + " \"SigningPubKey\": %s\n" + + "}", ED_PUBLIC_KEY.base16Value()); + + assertCanSerializeAndDeserialize(createBridge, json); + } + + @Test + void testJsonWithUnsetFlags() throws JSONException, JsonProcessingException { + XChainCreateBridge createBridge = baseBuilder() + .flags(TransactionFlags.UNSET) + .build(); + + String json = String.format("\n" + + "{\n" + + " \"TransactionType\": \"XChainCreateBridge\",\n" + + " \"Account\": \"rhWQzvdmhf5vFS35vtKUSUwNZHGT53qQsg\",\n" + + " \"XChainBridge\": {\n" + + " \"LockingChainDoor\": \"rhWQzvdmhf5vFS35vtKUSUwNZHGT53qQsg\",\n" + + " \"LockingChainIssue\": {\n" + + " \"currency\": \"XRP\"\n" + + " },\n" + + " \"IssuingChainDoor\": \"rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh\",\n" + + " \"IssuingChainIssue\": {\n" + + " \"currency\": \"XRP\"\n" + + " }\n" + + " },\n" + + " \"SignatureReward\": \"200\",\n" + + " \"Fee\": \"10\",\n" + + " \"Sequence\": 1,\n" + + " \"Flags\": 0,\n" + + " \"SigningPubKey\": %s\n" + + "}", ED_PUBLIC_KEY.base16Value()); + + assertCanSerializeAndDeserialize(createBridge, json); + } + + + @Test + void testJsonWithFullyCanonicalSigFlags() throws JSONException, JsonProcessingException { + XChainCreateBridge createBridge = baseBuilder() + .flags(TransactionFlags.FULLY_CANONICAL_SIG) + .build(); + + String json = String.format("\n" + + "{\n" + + " \"TransactionType\": \"XChainCreateBridge\",\n" + + " \"Account\": \"rhWQzvdmhf5vFS35vtKUSUwNZHGT53qQsg\",\n" + + " \"XChainBridge\": {\n" + + " \"LockingChainDoor\": \"rhWQzvdmhf5vFS35vtKUSUwNZHGT53qQsg\",\n" + + " \"LockingChainIssue\": {\n" + + " \"currency\": \"XRP\"\n" + + " },\n" + + " \"IssuingChainDoor\": \"rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh\",\n" + + " \"IssuingChainIssue\": {\n" + + " \"currency\": \"XRP\"\n" + + " }\n" + + " },\n" + + " \"SignatureReward\": \"200\",\n" + + " \"Fee\": \"10\",\n" + + " \"Sequence\": 1,\n" + + " \"Flags\": %s,\n" + + " \"SigningPubKey\": %s\n" + + "}", TransactionFlags.FULLY_CANONICAL_SIG, ED_PUBLIC_KEY.base16Value()); + + assertCanSerializeAndDeserialize(createBridge, json); + } + + private ImmutableXChainCreateBridge.Builder baseBuilder() { + return XChainCreateBridge.builder() + .fee(XrpCurrencyAmount.ofDrops(10)) + .sequence(UnsignedInteger.ONE) + .account(Address.of("rhWQzvdmhf5vFS35vtKUSUwNZHGT53qQsg")) + .signatureReward(XrpCurrencyAmount.ofDrops(200)) + .xChainBridge( + XChainBridge.builder() + .lockingChainDoor(Address.of("rhWQzvdmhf5vFS35vtKUSUwNZHGT53qQsg")) + .lockingChainIssue(Issue.XRP) + .issuingChainDoor(Address.of("rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh")) + .issuingChainIssue(Issue.XRP) + .build() + ) + .signingPublicKey(ED_PUBLIC_KEY); + } + +} \ No newline at end of file diff --git a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/XChainCreateClaimIdTest.java b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/XChainCreateClaimIdTest.java new file mode 100644 index 000000000..ec1824104 --- /dev/null +++ b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/XChainCreateClaimIdTest.java @@ -0,0 +1,123 @@ +package org.xrpl.xrpl4j.model.transactions; + +import static org.xrpl.xrpl4j.crypto.TestConstants.ED_PUBLIC_KEY; + +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.flags.TransactionFlags; +import org.xrpl.xrpl4j.model.ledger.Issue; + +class XChainCreateClaimIdTest extends AbstractJsonTest { + + @Test + void testJsonWithEmptyFlags() throws JSONException, JsonProcessingException { + XChainCreateClaimId claimId = baseBuilder().build(); + + String json = String.format("\n" + + "{\n" + + " \"Account\": \"rahDmoXrtPdh7sUdrPjini3gcnTVYjbjjw\",\n" + + " \"OtherChainSource\": \"rMTi57fNy2UkUb4RcdoUeJm7gjxVQvxzUo\",\n" + + " \"TransactionType\": \"XChainCreateClaimID\",\n" + + " \"SignatureReward\": \"100\",\n" + + " \"XChainBridge\": {\n" + + " \"LockingChainDoor\": \"rMAXACCrp3Y8PpswXcg3bKggHX76V3F8M4\",\n" + + " \"LockingChainIssue\": {\n" + + " \"currency\": \"XRP\"\n" + + " },\n" + + " \"IssuingChainDoor\": \"rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh\",\n" + + " \"IssuingChainIssue\": {\n" + + " \"currency\": \"XRP\"\n" + + " }\n" + + " },\n" + + " \"Fee\": \"10\",\n" + + " \"Sequence\": 1,\n" + + " \"SigningPubKey\": %s\n" + + "}", ED_PUBLIC_KEY.base16Value()); + + assertCanSerializeAndDeserialize(claimId, json); + } + + @Test + void testJsonWithUnsetFlags() throws JSONException, JsonProcessingException { + XChainCreateClaimId claimId = baseBuilder() + .flags(TransactionFlags.UNSET) + .build(); + + String json = String.format("\n" + + "{\n" + + " \"Account\": \"rahDmoXrtPdh7sUdrPjini3gcnTVYjbjjw\",\n" + + " \"OtherChainSource\": \"rMTi57fNy2UkUb4RcdoUeJm7gjxVQvxzUo\",\n" + + " \"TransactionType\": \"XChainCreateClaimID\",\n" + + " \"SignatureReward\": \"100\",\n" + + " \"XChainBridge\": {\n" + + " \"LockingChainDoor\": \"rMAXACCrp3Y8PpswXcg3bKggHX76V3F8M4\",\n" + + " \"LockingChainIssue\": {\n" + + " \"currency\": \"XRP\"\n" + + " },\n" + + " \"IssuingChainDoor\": \"rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh\",\n" + + " \"IssuingChainIssue\": {\n" + + " \"currency\": \"XRP\"\n" + + " }\n" + + " },\n" + + " \"Fee\": \"10\",\n" + + " \"Sequence\": 1,\n" + + " \"Flags\": 0,\n" + + " \"SigningPubKey\": %s\n" + + "}", ED_PUBLIC_KEY.base16Value()); + + assertCanSerializeAndDeserialize(claimId, json); + } + + @Test + void testJsonWithFullyCanonicalSigFlags() throws JSONException, JsonProcessingException { + XChainCreateClaimId claimId = baseBuilder() + .flags(TransactionFlags.FULLY_CANONICAL_SIG) + .build(); + + String json = String.format("\n" + + "{\n" + + " \"Account\": \"rahDmoXrtPdh7sUdrPjini3gcnTVYjbjjw\",\n" + + " \"OtherChainSource\": \"rMTi57fNy2UkUb4RcdoUeJm7gjxVQvxzUo\",\n" + + " \"TransactionType\": \"XChainCreateClaimID\",\n" + + " \"SignatureReward\": \"100\",\n" + + " \"XChainBridge\": {\n" + + " \"LockingChainDoor\": \"rMAXACCrp3Y8PpswXcg3bKggHX76V3F8M4\",\n" + + " \"LockingChainIssue\": {\n" + + " \"currency\": \"XRP\"\n" + + " },\n" + + " \"IssuingChainDoor\": \"rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh\",\n" + + " \"IssuingChainIssue\": {\n" + + " \"currency\": \"XRP\"\n" + + " }\n" + + " },\n" + + " \"Fee\": \"10\",\n" + + " \"Sequence\": 1,\n" + + " \"Flags\": %s,\n" + + " \"SigningPubKey\": %s\n" + + "}", TransactionFlags.FULLY_CANONICAL_SIG, ED_PUBLIC_KEY.base16Value()); + + assertCanSerializeAndDeserialize(claimId, json); + } + + private ImmutableXChainCreateClaimId.Builder baseBuilder() { + return XChainCreateClaimId.builder() + .fee(XrpCurrencyAmount.ofDrops(10)) + .sequence(UnsignedInteger.ONE) + .account(Address.of("rahDmoXrtPdh7sUdrPjini3gcnTVYjbjjw")) + .otherChainSource(Address.of("rMTi57fNy2UkUb4RcdoUeJm7gjxVQvxzUo")) + .signatureReward(XrpCurrencyAmount.ofDrops(100)) + .xChainBridge( + XChainBridge.builder() + .lockingChainDoor(Address.of("rMAXACCrp3Y8PpswXcg3bKggHX76V3F8M4")) + .lockingChainIssue(Issue.XRP) + .issuingChainDoor(Address.of("rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh")) + .issuingChainIssue(Issue.XRP) + .build() + ) + .signingPublicKey(ED_PUBLIC_KEY); + } + +} \ No newline at end of file diff --git a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/XChainModifyBridgeTest.java b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/XChainModifyBridgeTest.java new file mode 100644 index 000000000..caa0a86d3 --- /dev/null +++ b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/XChainModifyBridgeTest.java @@ -0,0 +1,146 @@ +package org.xrpl.xrpl4j.model.transactions; + +import static org.xrpl.xrpl4j.crypto.TestConstants.ED_PUBLIC_KEY; + +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.flags.XChainModifyBridgeFlags; +import org.xrpl.xrpl4j.model.ledger.Issue; + +class XChainModifyBridgeTest extends AbstractJsonTest { + + @Test + void testWithEmptySigRewardAndMinAccountCreateAmount() throws JSONException, JsonProcessingException { + XChainModifyBridge modify = baseBuilder().build(); + + String json = String.format("\n" + + "{\n" + + " \"TransactionType\": \"XChainModifyBridge\",\n" + + " \"Account\": \"rhWQzvdmhf5vFS35vtKUSUwNZHGT53qQsg\",\n" + + " \"XChainBridge\": {\n" + + " \"LockingChainDoor\": \"rhWQzvdmhf5vFS35vtKUSUwNZHGT53qQsg\",\n" + + " \"LockingChainIssue\": {\n" + + " \"currency\": \"XRP\"\n" + + " },\n" + + " \"IssuingChainDoor\": \"rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh\",\n" + + " \"IssuingChainIssue\": {\n" + + " \"currency\": \"XRP\"\n" + + " }\n" + + " },\n" + + " \"Fee\": \"10\",\n" + + " \"Sequence\": 1,\n" + + " \"SigningPubKey\": %s\n" + + "}", ED_PUBLIC_KEY.base16Value()); + + assertCanSerializeAndDeserialize(modify, json); + } + + @Test + void testWithUnsetFlags() throws JSONException, JsonProcessingException { + XChainModifyBridge modify = baseBuilder() + .flags(XChainModifyBridgeFlags.UNSET) + .build(); + + String json = String.format("\n" + + "{\n" + + " \"TransactionType\": \"XChainModifyBridge\",\n" + + " \"Account\": \"rhWQzvdmhf5vFS35vtKUSUwNZHGT53qQsg\",\n" + + " \"XChainBridge\": {\n" + + " \"LockingChainDoor\": \"rhWQzvdmhf5vFS35vtKUSUwNZHGT53qQsg\",\n" + + " \"LockingChainIssue\": {\n" + + " \"currency\": \"XRP\"\n" + + " },\n" + + " \"IssuingChainDoor\": \"rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh\",\n" + + " \"IssuingChainIssue\": {\n" + + " \"currency\": \"XRP\"\n" + + " }\n" + + " },\n" + + " \"Fee\": \"10\",\n" + + " \"Sequence\": 1,\n" + + " \"Flags\": 0,\n" + + " \"SigningPubKey\": %s\n" + + "}", ED_PUBLIC_KEY.base16Value()); + + assertCanSerializeAndDeserialize(modify, json); + } + + @Test + void testWithClearAccountCreateAmountFlags() throws JSONException, JsonProcessingException { + XChainModifyBridge modify = baseBuilder() + .flags(XChainModifyBridgeFlags.CLEAR_ACCOUNT_CREATE_AMOUNT) + .build(); + + String json = String.format("\n" + + "{\n" + + " \"TransactionType\": \"XChainModifyBridge\",\n" + + " \"Account\": \"rhWQzvdmhf5vFS35vtKUSUwNZHGT53qQsg\",\n" + + " \"XChainBridge\": {\n" + + " \"LockingChainDoor\": \"rhWQzvdmhf5vFS35vtKUSUwNZHGT53qQsg\",\n" + + " \"LockingChainIssue\": {\n" + + " \"currency\": \"XRP\"\n" + + " },\n" + + " \"IssuingChainDoor\": \"rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh\",\n" + + " \"IssuingChainIssue\": {\n" + + " \"currency\": \"XRP\"\n" + + " }\n" + + " },\n" + + " \"Fee\": \"10\",\n" + + " \"Sequence\": 1,\n" + + " \"Flags\": %s,\n" + + " \"SigningPubKey\": %s\n" + + "}", XChainModifyBridgeFlags.CLEAR_ACCOUNT_CREATE_AMOUNT, ED_PUBLIC_KEY.base16Value()); + + assertCanSerializeAndDeserialize(modify, json); + } + + @Test + void testWithSigRewardAndMinAccountCreateAmount() throws JSONException, JsonProcessingException { + XChainModifyBridge modify = baseBuilder() + .signatureReward(XrpCurrencyAmount.ofDrops(200)) + .minAccountCreateAmount(XrpCurrencyAmount.ofDrops(1000000)) + .build(); + + String json = String.format("\n" + + "{\n" + + " \"TransactionType\": \"XChainModifyBridge\",\n" + + " \"Account\": \"rhWQzvdmhf5vFS35vtKUSUwNZHGT53qQsg\",\n" + + " \"XChainBridge\": {\n" + + " \"LockingChainDoor\": \"rhWQzvdmhf5vFS35vtKUSUwNZHGT53qQsg\",\n" + + " \"LockingChainIssue\": {\n" + + " \"currency\": \"XRP\"\n" + + " },\n" + + " \"IssuingChainDoor\": \"rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh\",\n" + + " \"IssuingChainIssue\": {\n" + + " \"currency\": \"XRP\"\n" + + " }\n" + + " },\n" + + " \"Fee\": \"10\",\n" + + " \"Sequence\": 1,\n" + + " \"SignatureReward\": \"200\",\n" + + " \"MinAccountCreateAmount\": \"1000000\",\n" + + " \"SigningPubKey\": %s\n" + + "}", ED_PUBLIC_KEY.base16Value()); + + assertCanSerializeAndDeserialize(modify, json); + } + + private ImmutableXChainModifyBridge.Builder baseBuilder() { + return XChainModifyBridge.builder() + .fee(XrpCurrencyAmount.ofDrops(10)) + .sequence(UnsignedInteger.ONE) + .account(Address.of("rhWQzvdmhf5vFS35vtKUSUwNZHGT53qQsg")) + .xChainBridge( + XChainBridge.builder() + .lockingChainDoor(Address.of("rhWQzvdmhf5vFS35vtKUSUwNZHGT53qQsg")) + .lockingChainIssue(Issue.XRP) + .issuingChainDoor(Address.of("rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh")) + .issuingChainIssue(Issue.XRP) + .build() + ) + .signingPublicKey(ED_PUBLIC_KEY); + } + +} \ No newline at end of file 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 03ce8f461..b9c18e6dd 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 @@ -8,6 +8,7 @@ import org.immutables.value.Value; import org.junit.jupiter.api.Test; import org.xrpl.xrpl4j.model.AbstractJsonTest; +import org.xrpl.xrpl4j.model.ledger.AccountRootObject; class MetaLedgerEntryTypeTest extends AbstractJsonTest { @@ -30,6 +31,37 @@ void testConstants() { assertThat(MetaLedgerEntryType.TICKET.value()).isEqualTo("Ticket"); assertThat(MetaLedgerEntryType.NFTOKEN_PAGE.value()).isEqualTo("NFTokenPage"); assertThat(MetaLedgerEntryType.AMM.value()).isEqualTo("AMM"); + assertThat(MetaLedgerEntryType.BRIDGE.value()).isEqualTo("Bridge"); + assertThat(MetaLedgerEntryType.XCHAIN_OWNED_CREATE_ACCOUNT_CLAIM_ID.value()) + .isEqualTo("XChainOwnedCreateAccountClaimID"); + assertThat(MetaLedgerEntryType.XCHAIN_OWNED_CLAIM_ID.value()).isEqualTo("XChainOwnedClaimID"); + } + + @Test + void testLedgerObjectType() { + assertThat(MetaLedgerEntryType.ACCOUNT_ROOT.ledgerObjectType()).isEqualTo(MetaAccountRootObject.class); + assertThat(MetaLedgerEntryType.AMENDMENTS.ledgerObjectType()).isEqualTo(MetaUnknownObject.class); + assertThat(MetaLedgerEntryType.CHECK.ledgerObjectType()).isEqualTo(MetaCheckObject.class); + assertThat(MetaLedgerEntryType.DEPOSIT_PRE_AUTH.ledgerObjectType()).isEqualTo(MetaDepositPreAuthObject.class); + assertThat(MetaLedgerEntryType.DIRECTORY_NODE.ledgerObjectType()).isEqualTo(MetaUnknownObject.class); + assertThat(MetaLedgerEntryType.ESCROW.ledgerObjectType()).isEqualTo(MetaEscrowObject.class); + assertThat(MetaLedgerEntryType.FEE_SETTINGS.ledgerObjectType()).isEqualTo(MetaUnknownObject.class); + assertThat(MetaLedgerEntryType.LEDGER_HASHES.ledgerObjectType()).isEqualTo(MetaUnknownObject.class); + assertThat(MetaLedgerEntryType.NEGATIVE_UNL.ledgerObjectType()).isEqualTo(MetaUnknownObject.class); + assertThat(MetaLedgerEntryType.NFTOKEN_OFFER.ledgerObjectType()).isEqualTo(MetaNfTokenOfferObject.class); + assertThat(MetaLedgerEntryType.OFFER.ledgerObjectType()).isEqualTo(MetaOfferObject.class); + assertThat(MetaLedgerEntryType.PAY_CHANNEL.ledgerObjectType()).isEqualTo(MetaPayChannelObject.class); + assertThat(MetaLedgerEntryType.RIPPLE_STATE.ledgerObjectType()).isEqualTo(MetaRippleStateObject.class); + assertThat(MetaLedgerEntryType.SIGNER_LIST.ledgerObjectType()).isEqualTo(MetaSignerListObject.class); + assertThat(MetaLedgerEntryType.TICKET.ledgerObjectType()).isEqualTo(MetaTicketObject.class); + assertThat(MetaLedgerEntryType.NFTOKEN_PAGE.ledgerObjectType()).isEqualTo(MetaNfTokenPageObject.class); + assertThat(MetaLedgerEntryType.AMM.ledgerObjectType()).isEqualTo(MetaAmmObject.class); + assertThat(MetaLedgerEntryType.BRIDGE.ledgerObjectType()).isEqualTo(MetaBridgeObject.class); + assertThat(MetaLedgerEntryType.XCHAIN_OWNED_CREATE_ACCOUNT_CLAIM_ID.ledgerObjectType()) + .isEqualTo(MetaXChainOwnedCreateAccountClaimIdObject.class); + assertThat(MetaLedgerEntryType.XCHAIN_OWNED_CLAIM_ID.ledgerObjectType()).isEqualTo( + MetaXChainOwnedClaimIdObject.class + ); } @Test diff --git a/xrpl4j-core/src/test/resources/codec-fixtures.json b/xrpl4j-core/src/test/resources/codec-fixtures.json index e05cff35f..b9a43d13c 100644 --- a/xrpl4j-core/src/test/resources/codec-fixtures.json +++ b/xrpl4j-core/src/test/resources/codec-fixtures.json @@ -33,7 +33,9 @@ "LedgerEntryType": "DirectoryNode", "Flags": 0, "RootIndex": "059D1E86DE5DCCCF956BF4799675B2425AF9AD44FE4CCA6FEE1C812EEF6423E6", - "Indexes": ["908D554AA0D29F660716A3EE65C61DD886B744DDF60DE70E6B16EADB770635DB"] + "Indexes": [ + "908D554AA0D29F660716A3EE65C61DD886B744DDF60DE70E6B16EADB770635DB" + ] } }, { @@ -327,7 +329,9 @@ "LedgerEntryType": "DirectoryNode", "Flags": 0, "RootIndex": "17CC40C6872E0C0E658C49B75D0812A70D4161DDA53324DF51FA58D3819C814B", - "Indexes": ["571BF14F28C4D97871CDACD344A8CF57E6BA287BF0440B9E0D0683D02751CC7B"] + "Indexes": [ + "571BF14F28C4D97871CDACD344A8CF57E6BA287BF0440B9E0D0683D02751CC7B" + ] } }, { @@ -376,7 +380,9 @@ "LedgerEntryType": "DirectoryNode", "Flags": 0, "RootIndex": "1BCA9161A199AD5E907751CBF3FBA49689D517F0E8EE823AE17B737039B41DE1", - "Indexes": ["26B894EE68470AD5AEEB55D5EBF936E6397CEE6957B93C56A2E7882CA9082873"] + "Indexes": [ + "26B894EE68470AD5AEEB55D5EBF936E6397CEE6957B93C56A2E7882CA9082873" + ] } }, { @@ -440,7 +446,9 @@ "LedgerEntryType": "DirectoryNode", "Flags": 0, "RootIndex": "1F9FF48419CA69FDDCC294CCEEE608F5F8A8BE11E286AD5743ED2D457C5570C4", - "Indexes": ["7D4325BE338A40BBCBCC1F351B3272EB3E76305A878E76603DE206A795871619"] + "Indexes": [ + "7D4325BE338A40BBCBCC1F351B3272EB3E76305A878E76603DE206A795871619" + ] } }, { @@ -606,7 +614,9 @@ "LedgerEntryType": "DirectoryNode", "Flags": 0, "RootIndex": "289CFC476B5876F28C8A3B3C5B7058EC2BDF668C37B846EA7E5E1A73A4AA0816", - "Indexes": ["BC10E40AFB79298004CDE51CB065DBDCABA86EC406E3A1CF02CE5F8A9628A2BD"] + "Indexes": [ + "BC10E40AFB79298004CDE51CB065DBDCABA86EC406E3A1CF02CE5F8A9628A2BD" + ] } }, { @@ -709,7 +719,9 @@ "LedgerEntryType": "DirectoryNode", "Flags": 0, "RootIndex": "2C9F00EFA5CCBD43452EF364B12C8DFCEF2B910336E5EFCE3AA412A556991582", - "Indexes": ["F721E924498EE68BFF906CD856E8332073DD350BAC9E8977AC3F31860BA1E33A"] + "Indexes": [ + "F721E924498EE68BFF906CD856E8332073DD350BAC9E8977AC3F31860BA1E33A" + ] } }, { @@ -746,7 +758,9 @@ "LedgerEntryType": "DirectoryNode", "Flags": 0, "RootIndex": "2FB4904ACFB96228FC002335B1B5A4C5584D9D727BBE82144F0415EB4EA0C727", - "Indexes": ["5F22826818CC83448C9DF34939AB4019D3F80C70DEB8BDBDCF0496A36DC68719"], + "Indexes": [ + "5F22826818CC83448C9DF34939AB4019D3F80C70DEB8BDBDCF0496A36DC68719" + ], "TakerPaysIssuer": "2B6C42A95B3F7EE1971E4A10098E8F1B5F66AA08" } }, @@ -760,7 +774,9 @@ "LedgerEntryType": "DirectoryNode", "Flags": 0, "RootIndex": "2FB4904ACFB96228FC002335B1B5A4C5584D9D727BBE82145003BAF82D03A000", - "Indexes": ["5B7F148A8DDB4EB7386C9E75C4C1ED918DEDE5C52D5BA51B694D7271EF8BDB46"], + "Indexes": [ + "5B7F148A8DDB4EB7386C9E75C4C1ED918DEDE5C52D5BA51B694D7271EF8BDB46" + ], "TakerPaysIssuer": "2B6C42A95B3F7EE1971E4A10098E8F1B5F66AA08" } }, @@ -951,7 +967,9 @@ "LedgerEntryType": "DirectoryNode", "Flags": 0, "RootIndex": "3F2BADB38F12C87D111D3970CD1F05FE698DB86F14DC7C5FAEB05BFB6391B00E", - "Indexes": ["73E075E64CA5E7CE60FFCD5359C1D730EDFFEE7C4D992760A87DF7EA0A34E40F"] + "Indexes": [ + "73E075E64CA5E7CE60FFCD5359C1D730EDFFEE7C4D992760A87DF7EA0A34E40F" + ] } }, { @@ -976,7 +994,9 @@ "LedgerEntryType": "DirectoryNode", "Flags": 0, "RootIndex": "4235CD082112FB621C02D6DA2E4F4ACFAFC91CB0585E034B936C29ABF4A76B01", - "Indexes": ["6C4C3F1C6B9D76A6EF50F377E7C3991825694C604DBE0C1DD09362045EE41997"] + "Indexes": [ + "6C4C3F1C6B9D76A6EF50F377E7C3991825694C604DBE0C1DD09362045EE41997" + ] } }, { @@ -1075,7 +1095,9 @@ "LedgerEntryType": "DirectoryNode", "Flags": 0, "RootIndex": "48E91FD14597FB089654DADE7B70EB08CAF421EA611D703F3E871F7D4B5AAB5D", - "Indexes": ["25DCAC87FBE4C3B66A1AFDE3C3F98E5A16333975C4FD46682F7497F27DFB9766"] + "Indexes": [ + "25DCAC87FBE4C3B66A1AFDE3C3F98E5A16333975C4FD46682F7497F27DFB9766" + ] } }, { @@ -1137,7 +1159,9 @@ "LedgerEntryType": "DirectoryNode", "Flags": 0, "RootIndex": "4EFC0442D07AE681F7FDFAA89C75F06F8E28CFF888593440201B0320E8F2C7BD", - "Indexes": ["1595E5D5197330F58A479200A2FDD434D7A244BD1FFEC5E5EE8CF064AE77D3F5"] + "Indexes": [ + "1595E5D5197330F58A479200A2FDD434D7A244BD1FFEC5E5EE8CF064AE77D3F5" + ] } }, { @@ -1351,7 +1375,9 @@ "LedgerEntryType": "DirectoryNode", "Flags": 0, "RootIndex": "98082E695CAB618590BEEA0647A5F24D2B610A686ECD49310604FC7431FAAB0D", - "Indexes": ["9BF3216E42575CA5A3CB4D0F2021EE81D0F7835BA2EDD78E05CAB44B655962BB"] + "Indexes": [ + "9BF3216E42575CA5A3CB4D0F2021EE81D0F7835BA2EDD78E05CAB44B655962BB" + ] } }, { @@ -1556,7 +1582,9 @@ "LedgerEntryType": "DirectoryNode", "Flags": 0, "RootIndex": "62AE37A44FE44BDCFC2BA5DD14D74BEC0AC346DA2DC1F04756044364C5BB0000", - "Indexes": ["600A398F57CAE44461B4C8C25DE12AC289F87ED125438440B33B97417FE3D82C"], + "Indexes": [ + "600A398F57CAE44461B4C8C25DE12AC289F87ED125438440B33B97417FE3D82C" + ], "TakerPaysIssuer": "2B6C42A95B3F7EE1971E4A10098E8F1B5F66AA08" } }, @@ -1932,7 +1960,9 @@ "LedgerEntryType": "DirectoryNode", "Flags": 0, "RootIndex": "72D60CCD3905A3ABE19049B6EE76E8E0F3A2CBAC852625C757176F1B73EF617F", - "Indexes": ["AB124EEAB087452070EC70D9DEA1A22C9766FFBBEE1025FD46495CC74148CCA8"] + "Indexes": [ + "AB124EEAB087452070EC70D9DEA1A22C9766FFBBEE1025FD46495CC74148CCA8" + ] } }, { @@ -2061,7 +2091,9 @@ "LedgerEntryType": "DirectoryNode", "Flags": 0, "RootIndex": "80AB25842B230D48027800213EB86023A3EAF4430E22C092D333795FFF1E5219", - "Indexes": ["42E28285A82D01DCA856118A064C8AEEE1BF8167C08186DA5BFC678687E86F7C"] + "Indexes": [ + "42E28285A82D01DCA856118A064C8AEEE1BF8167C08186DA5BFC678687E86F7C" + ] } }, { @@ -2186,7 +2218,9 @@ "LedgerEntryType": "DirectoryNode", "Flags": 0, "RootIndex": "8ADF3C5527CCF6D0B5863365EF40254171536C3901F1CBD9E2BC5F918A7D492A", - "Indexes": ["BC10E40AFB79298004CDE51CB065DBDCABA86EC406E3A1CF02CE5F8A9628A2BD"] + "Indexes": [ + "BC10E40AFB79298004CDE51CB065DBDCABA86EC406E3A1CF02CE5F8A9628A2BD" + ] } }, { @@ -2587,7 +2621,9 @@ "LedgerEntryType": "DirectoryNode", "Flags": 0, "RootIndex": "A00CD19C13A5CFA3FECB409D42B38017C07A4AEAE05A7A00347DDA17199BA683", - "Indexes": ["E49318D6DF22411C3F35581B1D28297A36E47F68B45F36A587C156E6E43CE0A6"] + "Indexes": [ + "E49318D6DF22411C3F35581B1D28297A36E47F68B45F36A587C156E6E43CE0A6" + ] } }, { @@ -2634,7 +2670,9 @@ "LedgerEntryType": "DirectoryNode", "Flags": 0, "RootIndex": "A39F044D860C5B5846AA7E0FAAD44DC8897F0A62B2F628AA073B21B3EC146010", - "Indexes": ["CD34D8FF7C656B66E2298DB420C918FE27DFFF2186AC8D1785D8CBF2C6BC3488"] + "Indexes": [ + "CD34D8FF7C656B66E2298DB420C918FE27DFFF2186AC8D1785D8CBF2C6BC3488" + ] } }, { @@ -2683,7 +2721,9 @@ "LedgerEntryType": "DirectoryNode", "Flags": 0, "RootIndex": "A7E461C6DC98F472991FDE51FADDC0082D755F553F5849875D554B52624EF1C3", - "Indexes": ["116C6D5E5C6C59C9C5362B84CB9DD30BD3D4B7CB98CE993D49C068323BF19747"] + "Indexes": [ + "116C6D5E5C6C59C9C5362B84CB9DD30BD3D4B7CB98CE993D49C068323BF19747" + ] } }, { @@ -2717,7 +2757,9 @@ "LedgerEntryType": "DirectoryNode", "Flags": 0, "RootIndex": "AA539C8EECE0A0CFF0DBF3BFACD6B42CD4421715428AD90B034091BD3C721038", - "Indexes": ["72307CB57E53604A0C50E653AB10E386F3835460B5585B70CB7F668C1E04AC8B"] + "Indexes": [ + "72307CB57E53604A0C50E653AB10E386F3835460B5585B70CB7F668C1E04AC8B" + ] } }, { @@ -3777,7 +3819,9 @@ "LedgerEntryType": "DirectoryNode", "Flags": 0, "RootIndex": "D4A00D9B3452C7F93C5F0531FA8FFB4599FEEC405CA803FBEFE0FA22137D863D", - "Indexes": ["C1C5FB39D6C15C581D822DBAF725EF7EDE40BEC9F93C52398CF5CE9F64154D6C"] + "Indexes": [ + "C1C5FB39D6C15C581D822DBAF725EF7EDE40BEC9F93C52398CF5CE9F64154D6C" + ] } }, { @@ -3787,7 +3831,9 @@ "LedgerEntryType": "DirectoryNode", "Flags": 0, "RootIndex": "D4B68B54869E428428078E1045B8BB66C24DD101DB3FCCBB099929B3B63BCB40", - "Indexes": ["9A551971E78FE2FB80D930A77EA0BAC2139A49D6BEB98406427C79F52A347A09"] + "Indexes": [ + "9A551971E78FE2FB80D930A77EA0BAC2139A49D6BEB98406427C79F52A347A09" + ] } }, { @@ -3862,7 +3908,9 @@ "LedgerEntryType": "DirectoryNode", "Flags": 0, "RootIndex": "DD23E2C60C9BC58180AC6EA7C668233EC51A0947E42FD1FAD4F5FBAED9698D95", - "Indexes": ["908D554AA0D29F660716A3EE65C61DD886B744DDF60DE70E6B16EADB770635DB"] + "Indexes": [ + "908D554AA0D29F660716A3EE65C61DD886B744DDF60DE70E6B16EADB770635DB" + ] } }, { @@ -4013,7 +4061,9 @@ "LedgerEntryType": "DirectoryNode", "Flags": 0, "RootIndex": "E2EC9E1BC7B4667B7A5F2F68857F6E6A478A09B5BB4F99E09F694437C4152DED", - "Indexes": ["65492B9F30F1CBEA168509128EB8619BAE02A7A7A4725FF3F8DAA70FA707A26E"] + "Indexes": [ + "65492B9F30F1CBEA168509128EB8619BAE02A7A7A4725FF3F8DAA70FA707A26E" + ] } }, { @@ -4190,7 +4240,9 @@ "IndexPrevious": "0000000000000002", "Flags": 0, "RootIndex": "8E92E688A132410427806A734DF6154B7535E439B72DECA5E4BC7CE17135C5A4", - "Indexes": ["73E075E64CA5E7CE60FFCD5359C1D730EDFFEE7C4D992760A87DF7EA0A34E40F"] + "Indexes": [ + "73E075E64CA5E7CE60FFCD5359C1D730EDFFEE7C4D992760A87DF7EA0A34E40F" + ] } }, { @@ -4294,7 +4346,9 @@ "LedgerEntryType": "DirectoryNode", "Flags": 0, "RootIndex": "F774E0321809251174AC85531606FB46B75EEF9F842F9697531AA535D3D0C000", - "Indexes": ["D1CB738BD08AC36DCB77191DB87C6E40FA478B86503371ED497F30931D7F4F52"], + "Indexes": [ + "D1CB738BD08AC36DCB77191DB87C6E40FA478B86503371ED497F30931D7F4F52" + ], "TakerPaysIssuer": "E8ACFC6B5EF4EA0601241525375162F43C2FF285" } }, @@ -4355,7 +4409,9 @@ "LedgerEntryType": "DirectoryNode", "Flags": 0, "RootIndex": "F95F6D3A1EF7981E5CA4D5AEC4DA63392B126C76469735BCCA26150A1AF6D9C3", - "Indexes": ["CAD951AB279A749AE648FD1DFF56C021BD66E36187022E772C31FE52106CB13B"] + "Indexes": [ + "CAD951AB279A749AE648FD1DFF56C021BD66E36187022E772C31FE52106CB13B" + ] } }, { @@ -4435,20 +4491,230 @@ } } ], - "transactions": [{ - "binary": "1200002200000000240000003E6140000002540BE40068400000000000000A7321034AADB09CFF4A4804073701EC53C3510CDC95917C2BB0150FB742D0C66E6CEE9E74473045022022EB32AECEF7C644C891C19F87966DF9C62B1F34BABA6BE774325E4BB8E2DD62022100A51437898C28C2B297112DF8131F2BB39EA5FE613487DDD611525F17962646398114550FC62003E785DC231A1058A05E56E3F09CF4E68314D4CC8AB5B21D86A82C3E9E8D0ECF2404B77FECBA", - "json": { - "Account": "r3kmLJN5D28dHuH8vZNUZpMC43pEHpaocV", - "Destination": "rLQBHVhFnaC5gLEkgr6HgBJJ3bgeZHg9cj", - "TransactionType": "Payment", - "TxnSignature": "3045022022EB32AECEF7C644C891C19F87966DF9C62B1F34BABA6BE774325E4BB8E2DD62022100A51437898C28C2B297112DF8131F2BB39EA5FE613487DDD611525F1796264639", - "SigningPubKey": "034AADB09CFF4A4804073701EC53C3510CDC95917C2BB0150FB742D0C66E6CEE9E", - "Amount": "10000000000", - "Fee": "10", - "Flags": 0, - "Sequence": 62 - } - }, + "transactions": [ + { + "binary": "1200002200000000240000003E6140000002540BE40068400000000000000A7321034AADB09CFF4A4804073701EC53C3510CDC95917C2BB0150FB742D0C66E6CEE9E74473045022022EB32AECEF7C644C891C19F87966DF9C62B1F34BABA6BE774325E4BB8E2DD62022100A51437898C28C2B297112DF8131F2BB39EA5FE613487DDD611525F17962646398114550FC62003E785DC231A1058A05E56E3F09CF4E68314D4CC8AB5B21D86A82C3E9E8D0ECF2404B77FECBA", + "json": { + "Account": "r3kmLJN5D28dHuH8vZNUZpMC43pEHpaocV", + "Destination": "rLQBHVhFnaC5gLEkgr6HgBJJ3bgeZHg9cj", + "TransactionType": "Payment", + "TxnSignature": "3045022022EB32AECEF7C644C891C19F87966DF9C62B1F34BABA6BE774325E4BB8E2DD62022100A51437898C28C2B297112DF8131F2BB39EA5FE613487DDD611525F1796264639", + "SigningPubKey": "034AADB09CFF4A4804073701EC53C3510CDC95917C2BB0150FB742D0C66E6CEE9E", + "Amount": "10000000000", + "Fee": "10", + "Flags": 0, + "Sequence": 62 + } + }, + { + "binary": "1200302200000000240000000168400000000000000A601D40000000000003E8601E400000000000271073210330E7FC9D56BB25D6893BA3F317AE5BCF33B3291BD63DB32654A313222F7FD020744630440220101BCA4B5B5A37C6F44480F9A34752C9AA8B2CDF5AD47E3CB424DEDC21C06DB702206EEB257E82A89B1F46A0A2C7F070B0BD181D980FF86FE4269E369F6FC7A270918114B5F762798A53D543A014CAF8B297CFF8F2F937E8011914AF80285F637EE4AF3C20378F9DFB12511ACB8D27000000000000000000000000000000000000000014550FC62003E785DC231A1058A05E56E3F09CF4E60000000000000000000000000000000000000000", + "json": { + "Account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", + "XChainBridge": { + "LockingChainDoor": "rGzx83BVoqTYbGn7tiVAnFw7cbxjin13jL", + "LockingChainIssue": { + "currency": "XRP" + }, + "IssuingChainDoor": "r3kmLJN5D28dHuH8vZNUZpMC43pEHpaocV", + "IssuingChainIssue": { + "currency": "XRP" + } + }, + "Fee": "10", + "Flags": 0, + "MinAccountCreateAmount": "10000", + "Sequence": 1, + "SignatureReward": "1000", + "SigningPubKey": "0330E7FC9D56BB25D6893BA3F317AE5BCF33B3291BD63DB32654A313222F7FD020", + "TransactionType": "XChainCreateBridge", + "TxnSignature": "30440220101BCA4B5B5A37C6F44480F9A34752C9AA8B2CDF5AD47E3CB424DEDC21C06DB702206EEB257E82A89B1F46A0A2C7F070B0BD181D980FF86FE4269E369F6FC7A27091" + } + }, + { + "binary": "12002F2200000000240000000168400000000000000A601D40000000000003E8601E400000000000271073210330E7FC9D56BB25D6893BA3F317AE5BCF33B3291BD63DB32654A313222F7FD02074473045022100D2CABC1B0E0635A8EE2E6554F6D474C49BC292C995C5C9F83179F4A60634B04C02205D1DB569D9593136F2FBEA7140010C8F46794D653AFDBEA8D30B8750BA4805E58114B5F762798A53D543A014CAF8B297CFF8F2F937E8011914AF80285F637EE4AF3C20378F9DFB12511ACB8D27000000000000000000000000000000000000000014550FC62003E785DC231A1058A05E56E3F09CF4E60000000000000000000000000000000000000000", + "json": { + "Account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", + "XChainBridge": { + "LockingChainDoor": "rGzx83BVoqTYbGn7tiVAnFw7cbxjin13jL", + "LockingChainIssue": { + "currency": "XRP" + }, + "IssuingChainDoor": "r3kmLJN5D28dHuH8vZNUZpMC43pEHpaocV", + "IssuingChainIssue": { + "currency": "XRP" + } + }, + "Fee": "10", + "Flags": 0, + "MinAccountCreateAmount": "10000", + "Sequence": 1, + "SignatureReward": "1000", + "SigningPubKey": "0330E7FC9D56BB25D6893BA3F317AE5BCF33B3291BD63DB32654A313222F7FD020", + "TransactionType": "XChainModifyBridge", + "TxnSignature": "3045022100D2CABC1B0E0635A8EE2E6554F6D474C49BC292C995C5C9F83179F4A60634B04C02205D1DB569D9593136F2FBEA7140010C8F46794D653AFDBEA8D30B8750BA4805E5" + } + }, + { + "binary": "1200292280000000240000000168400000000000000A601D400000000000271073210330E7FC9D56BB25D6893BA3F317AE5BCF33B3291BD63DB32654A313222F7FD020744630440220247B20A1B9C48E21A374CB9B3E1FE2A7C528151868DF8D307E9FBE15237E531A02207C20C092DDCC525E583EF4AB7CB91E862A6DED19426997D3F0A2C84E2BE8C5DD8114B5F762798A53D543A014CAF8B297CFF8F2F937E8801214AF80285F637EE4AF3C20378F9DFB12511ACB8D27011914AF80285F637EE4AF3C20378F9DFB12511ACB8D27000000000000000000000000000000000000000014550FC62003E785DC231A1058A05E56E3F09CF4E60000000000000000000000000000000000000000", + "json": { + "Account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", + "XChainBridge": { + "LockingChainDoor": "rGzx83BVoqTYbGn7tiVAnFw7cbxjin13jL", + "LockingChainIssue": { + "currency": "XRP" + }, + "IssuingChainDoor": "r3kmLJN5D28dHuH8vZNUZpMC43pEHpaocV", + "IssuingChainIssue": { + "currency": "XRP" + } + }, + "Fee": "10", + "Flags": 2147483648, + "OtherChainSource": "rGzx83BVoqTYbGn7tiVAnFw7cbxjin13jL", + "Sequence": 1, + "SignatureReward": "10000", + "SigningPubKey": "0330E7FC9D56BB25D6893BA3F317AE5BCF33B3291BD63DB32654A313222F7FD020", + "TransactionType": "XChainCreateClaimID", + "TxnSignature": "30440220247B20A1B9C48E21A374CB9B3E1FE2A7C528151868DF8D307E9FBE15237E531A02207C20C092DDCC525E583EF4AB7CB91E862A6DED19426997D3F0A2C84E2BE8C5DD" + } + }, + { + "binary": "12002A228000000024000000013014000000000000000161400000000000271068400000000000000A73210330E7FC9D56BB25D6893BA3F317AE5BCF33B3291BD63DB32654A313222F7FD02074453043021F177323F0D93612C82A4393A99B23905A7E675753FD80C52997AFAB13F5F9D002203BFFAF457E90BDA65AABE8F8762BD96162FAD98A0C030CCD69B06EE9B12BBFFE8114B5F762798A53D543A014CAF8B297CFF8F2F937E8011914AF80285F637EE4AF3C20378F9DFB12511ACB8D27000000000000000000000000000000000000000014550FC62003E785DC231A1058A05E56E3F09CF4E60000000000000000000000000000000000000000", + "json": { + "Account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", + "Amount": "10000", + "XChainBridge": { + "LockingChainDoor": "rGzx83BVoqTYbGn7tiVAnFw7cbxjin13jL", + "LockingChainIssue": { + "currency": "XRP" + }, + "IssuingChainDoor": "r3kmLJN5D28dHuH8vZNUZpMC43pEHpaocV", + "IssuingChainIssue": { + "currency": "XRP" + } + }, + "Fee": "10", + "Flags": 2147483648, + "Sequence": 1, + "SigningPubKey": "0330E7FC9D56BB25D6893BA3F317AE5BCF33B3291BD63DB32654A313222F7FD020", + "TransactionType": "XChainCommit", + "TxnSignature": "3043021F177323F0D93612C82A4393A99B23905A7E675753FD80C52997AFAB13F5F9D002203BFFAF457E90BDA65AABE8F8762BD96162FAD98A0C030CCD69B06EE9B12BBFFE", + "XChainClaimID": "1" + } + }, + { + "binary": "12002B228000000024000000013014000000000000000161400000000000271068400000000000000A73210330E7FC9D56BB25D6893BA3F317AE5BCF33B3291BD63DB32654A313222F7FD020744630440220445F7469FDA401787D9EE8A9B6E24DFF81E94F4C09FD311D2C0A58FCC02C684A022029E2EF34A5EA35F50D5BB57AC6320AD3AE12C13C8D1379B255A486D72CED142E8114B5F762798A53D543A014CAF8B297CFF8F2F937E88314550FC62003E785DC231A1058A05E56E3F09CF4E6011914AF80285F637EE4AF3C20378F9DFB12511ACB8D27000000000000000000000000000000000000000014550FC62003E785DC231A1058A05E56E3F09CF4E60000000000000000000000000000000000000000", + "json": { + "Account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", + "Amount": "10000", + "XChainBridge": { + "LockingChainDoor": "rGzx83BVoqTYbGn7tiVAnFw7cbxjin13jL", + "LockingChainIssue": { + "currency": "XRP" + }, + "IssuingChainDoor": "r3kmLJN5D28dHuH8vZNUZpMC43pEHpaocV", + "IssuingChainIssue": { + "currency": "XRP" + } + }, + "Destination": "r3kmLJN5D28dHuH8vZNUZpMC43pEHpaocV", + "Fee": "10", + "Flags": 2147483648, + "Sequence": 1, + "SigningPubKey": "0330E7FC9D56BB25D6893BA3F317AE5BCF33B3291BD63DB32654A313222F7FD020", + "TransactionType": "XChainClaim", + "TxnSignature": "30440220445F7469FDA401787D9EE8A9B6E24DFF81E94F4C09FD311D2C0A58FCC02C684A022029E2EF34A5EA35F50D5BB57AC6320AD3AE12C13C8D1379B255A486D72CED142E", + "XChainClaimID": "1" + } + }, + { + "binary": "12002C228000000024000000016140000000000F424068400000000000000A601D400000000000271073210330E7FC9D56BB25D6893BA3F317AE5BCF33B3291BD63DB32654A313222F7FD0207446304402202984DDE7F0B566F081F7953D7212BF031ACBF8860FE114102E9512C4C8768C77022070113F4630B1DC3045E4A98DDD648CEBC31B12774F7B44A1B8123CD2C9F5CF188114B5F762798A53D543A014CAF8B297CFF8F2F937E88314AF80285F637EE4AF3C20378F9DFB12511ACB8D27011914AF80285F637EE4AF3C20378F9DFB12511ACB8D27000000000000000000000000000000000000000014550FC62003E785DC231A1058A05E56E3F09CF4E60000000000000000000000000000000000000000", + "json": { + "Account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", + "XChainBridge": { + "LockingChainDoor": "rGzx83BVoqTYbGn7tiVAnFw7cbxjin13jL", + "LockingChainIssue": { + "currency": "XRP" + }, + "IssuingChainDoor": "r3kmLJN5D28dHuH8vZNUZpMC43pEHpaocV", + "IssuingChainIssue": { + "currency": "XRP" + } + }, + "Amount": "1000000", + "Fee": "10", + "Flags": 2147483648, + "Destination": "rGzx83BVoqTYbGn7tiVAnFw7cbxjin13jL", + "Sequence": 1, + "SignatureReward": "10000", + "SigningPubKey": "0330E7FC9D56BB25D6893BA3F317AE5BCF33B3291BD63DB32654A313222F7FD020", + "TransactionType": "XChainAccountCreateCommit", + "TxnSignature": "304402202984DDE7F0B566F081F7953D7212BF031ACBF8860FE114102E9512C4C8768C77022070113F4630B1DC3045E4A98DDD648CEBC31B12774F7B44A1B8123CD2C9F5CF18" + } + }, + { + "binary": "12002E2400000005201B0000000D30150000000000000006614000000000989680684000000000000014601D40000000000000647121ED1F4A024ACFEBDB6C7AA88DEDE3364E060487EA31B14CC9E0D610D152B31AADC27321EDF54108BA2E0A0D3DC2AE3897F8BE0EFE776AE8D0F9FB0D0B9D64233084A8DDD1744003E74AEF1F585F156786429D2FC87A89E5C6B5A56D68BFC9A6A329F3AC67CBF2B6958283C663A4522278CA162C69B23CF75149AF022B410EA0508C16F42058007640EEFCFA3DC2AB4AB7C4D2EBBC168CB621A11B82BABD86534DFC8EFA72439A49662D744073CD848E7A587A95B35162CDF9A69BB237E72C9537A987F5B8C394F30D81145E7A3E3D7200A794FA801C66CE3775B6416EE4128314C15F113E49BCC4B9FFF43CD0366C23ACD82F75638012143FD9ED9A79DEA67CB5D585111FEF0A29203FA0408014145E7A3E3D7200A794FA801C66CE3775B6416EE4128015145E7A3E3D7200A794FA801C66CE3775B6416EE4120010130101191486F0B1126CE1205E59FDFDD2661A9FB7505CA70F000000000000000000000000000000000000000014B5F762798A53D543A014CAF8B297CFF8F2F937E80000000000000000000000000000000000000000", + "json": { + "Account": "r9cYxdjQsoXAEz3qQJc961SNLaXRkWXCvT", + "Amount": "10000000", + "AttestationRewardAccount": "r9cYxdjQsoXAEz3qQJc961SNLaXRkWXCvT", + "AttestationSignerAccount": "r9cYxdjQsoXAEz3qQJc961SNLaXRkWXCvT", + "Destination": "rJdTJRJZ6GXCCRaamHJgEqVzB7Zy4557Pi", + "Fee": "20", + "LastLedgerSequence": 13, + "OtherChainSource": "raFcdz1g8LWJDJWJE2ZKLRGdmUmsTyxaym", + "PublicKey": "ED1F4A024ACFEBDB6C7AA88DEDE3364E060487EA31B14CC9E0D610D152B31AADC2", + "Sequence": 5, + "Signature": "EEFCFA3DC2AB4AB7C4D2EBBC168CB621A11B82BABD86534DFC8EFA72439A49662D744073CD848E7A587A95B35162CDF9A69BB237E72C9537A987F5B8C394F30D", + "SignatureReward": "100", + "SigningPubKey": "EDF54108BA2E0A0D3DC2AE3897F8BE0EFE776AE8D0F9FB0D0B9D64233084A8DDD1", + "TransactionType": "XChainAddAccountCreateAttestation", + "TxnSignature": "03E74AEF1F585F156786429D2FC87A89E5C6B5A56D68BFC9A6A329F3AC67CBF2B6958283C663A4522278CA162C69B23CF75149AF022B410EA0508C16F4205800", + "WasLockingChainSend": 1, + "XChainAccountCreateCount": "6", + "XChainBridge": { + "IssuingChainDoor": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", + "IssuingChainIssue": { + "currency": "XRP" + }, + "LockingChainDoor": "rDJVtEuDKr4rj1B3qtW7R5TVWdXV2DY7Qg", + "LockingChainIssue": { + "currency": "XRP" + } + } + } + }, + { + "binary": "12002D2400000009201B00000013301400000000000000016140000000009896806840000000000000147121ED7541DEC700470F54276C90C333A13CDBB5D341FD43C60CEA12170F6D6D4E11367321ED0406B134786FE0751717226657F7BF8AFE96442C05D28ACEC66FB64852BA604C7440D0423649E48A44F181262CF5FC08A68E7FA5CD9E55843E4F09014B76E602574741E8553383A4B43CABD194BB96713647FC0B885BE248E4FFA068FA3E6994CF0476407C175050B08000AD35EEB2D87E16CD3F95A0AEEBF2A049474275153D9D4DD44528FE99AA50E71660A15B0B768E1B90E609BBD5DC7AFAFD45D9705D72D40EA10C81141F30A4D728AB98B0950EC3B9815E6C8D43A7D5598314C15F113E49BCC4B9FFF43CD0366C23ACD82F75638012143FD9ED9A79DEA67CB5D585111FEF0A29203FA0408014141F30A4D728AB98B0950EC3B9815E6C8D43A7D5598015141F30A4D728AB98B0950EC3B9815E6C8D43A7D5590010130101191486F0B1126CE1205E59FDFDD2661A9FB7505CA70F000000000000000000000000000000000000000014B5F762798A53D543A014CAF8B297CFF8F2F937E80000000000000000000000000000000000000000", + "json": { + "Account": "rsqvD8WFFEBBv4nztpoW9YYXJ7eRzLrtc3", + "Amount": "10000000", + "AttestationRewardAccount": "rsqvD8WFFEBBv4nztpoW9YYXJ7eRzLrtc3", + "AttestationSignerAccount": "rsqvD8WFFEBBv4nztpoW9YYXJ7eRzLrtc3", + "Destination": "rJdTJRJZ6GXCCRaamHJgEqVzB7Zy4557Pi", + "Fee": "20", + "LastLedgerSequence": 19, + "OtherChainSource": "raFcdz1g8LWJDJWJE2ZKLRGdmUmsTyxaym", + "PublicKey": "ED7541DEC700470F54276C90C333A13CDBB5D341FD43C60CEA12170F6D6D4E1136", + "Sequence": 9, + "Signature": "7C175050B08000AD35EEB2D87E16CD3F95A0AEEBF2A049474275153D9D4DD44528FE99AA50E71660A15B0B768E1B90E609BBD5DC7AFAFD45D9705D72D40EA10C", + "SigningPubKey": "ED0406B134786FE0751717226657F7BF8AFE96442C05D28ACEC66FB64852BA604C", + "TransactionType": "XChainAddClaimAttestation", + "TxnSignature": "D0423649E48A44F181262CF5FC08A68E7FA5CD9E55843E4F09014B76E602574741E8553383A4B43CABD194BB96713647FC0B885BE248E4FFA068FA3E6994CF04", + "WasLockingChainSend": 1, + "XChainBridge": { + "IssuingChainDoor": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", + "IssuingChainIssue": { + "currency": "XRP" + }, + "LockingChainDoor": "rDJVtEuDKr4rj1B3qtW7R5TVWdXV2DY7Qg", + "LockingChainIssue": { + "currency": "XRP" + } + }, + "XChainClaimID": "1" + } + }, { "binary": "12002315000A2200000000240015DAE161400000000000271068400000000000000A6BD5838D7EA4C680000000000000000000000000004554480000000000FBEF9A3A2B814E807745FA3D9C32FFD155FA2E8C7321ED7453D2572A2104E7B266A45888C53F503CEB1F11DC4BB3710EB2995238EC65B87440B3154D968314FCEB58001E1B0C3A4CFB33DF9FF6C73207E5EAEB9BD07E2747672168E1A2786D950495C38BD8DEE3391BF45F3008DD36F4B12E7C07D82CA5250E8114F92F27CC5EE2F2760278FE096D0CBE32BDD3653A", "json": { @@ -4474,9 +4740,18 @@ "Account": "rP5ZkB5RZQaECsSVR4DeSFK4fAw52BYtbw", "TransactionType": "AMMDeposit", "TxnSignature": "8073C588E7EF672DD171E414638D9AF8DBE9A1359E030DE3E1C9AA6A38A2CE9E138CB56482BB844F7228D48B1E4AD7D09BB7E9F639C115958EEEA374749CA00B", - "Asset": {"currency": "XRP"}, - "Asset2": {"currency": "ETH", "issuer": "rPyfep3gcLzkosKC9XiE77Y8DZWG6iWDT9"}, - "LPTokenOut": {"currency": "B3813FCAB4EE68B3D0D735D6849465A9113EE048", "issuer": "rH438jEAzTs5PYtV6CHZqpDpwCKQmPW9Cg", "value": "1000"}, + "Asset": { + "currency": "XRP" + }, + "Asset2": { + "currency": "ETH", + "issuer": "rPyfep3gcLzkosKC9XiE77Y8DZWG6iWDT9" + }, + "LPTokenOut": { + "currency": "B3813FCAB4EE68B3D0D735D6849465A9113EE048", + "issuer": "rH438jEAzTs5PYtV6CHZqpDpwCKQmPW9Cg", + "value": "1000" + }, "Fee": "10", "Flags": 65536, "Sequence": 1432289, @@ -4488,8 +4763,13 @@ "json": { "Account": "rP5ZkB5RZQaECsSVR4DeSFK4fAw52BYtbw", "TransactionType": "AMMDeposit", - "Asset": {"currency": "XRP"}, - "Asset2": {"currency": "ETH", "issuer": "rPyfep3gcLzkosKC9XiE77Y8DZWG6iWDT9"}, + "Asset": { + "currency": "XRP" + }, + "Asset2": { + "currency": "ETH", + "issuer": "rPyfep3gcLzkosKC9XiE77Y8DZWG6iWDT9" + }, "Amount": "1000", "Fee": "10", "Flags": 524288, @@ -4503,10 +4783,19 @@ "json": { "Account": "rP5ZkB5RZQaECsSVR4DeSFK4fAw52BYtbw", "TransactionType": "AMMDeposit", - "Asset": {"currency": "XRP"}, - "Asset2": {"currency": "ETH", "issuer": "rPyfep3gcLzkosKC9XiE77Y8DZWG6iWDT9"}, + "Asset": { + "currency": "XRP" + }, + "Asset2": { + "currency": "ETH", + "issuer": "rPyfep3gcLzkosKC9XiE77Y8DZWG6iWDT9" + }, "Amount": "1000", - "Amount2": {"currency": "ETH", "issuer": "rPyfep3gcLzkosKC9XiE77Y8DZWG6iWDT9", "value": "500"}, + "Amount2": { + "currency": "ETH", + "issuer": "rPyfep3gcLzkosKC9XiE77Y8DZWG6iWDT9", + "value": "500" + }, "Fee": "10", "Flags": 1048576, "Sequence": 1432289, @@ -4519,10 +4808,19 @@ "json": { "Account": "rP5ZkB5RZQaECsSVR4DeSFK4fAw52BYtbw", "TransactionType": "AMMDeposit", - "Asset": {"currency": "XRP"}, - "Asset2": {"currency": "ETH", "issuer": "rPyfep3gcLzkosKC9XiE77Y8DZWG6iWDT9"}, + "Asset": { + "currency": "XRP" + }, + "Asset2": { + "currency": "ETH", + "issuer": "rPyfep3gcLzkosKC9XiE77Y8DZWG6iWDT9" + }, "Amount": "1000", - "LPTokenOut": {"currency": "B3813FCAB4EE68B3D0D735D6849465A9113EE048", "issuer": "rH438jEAzTs5PYtV6CHZqpDpwCKQmPW9Cg", "value": "1000"}, + "LPTokenOut": { + "currency": "B3813FCAB4EE68B3D0D735D6849465A9113EE048", + "issuer": "rH438jEAzTs5PYtV6CHZqpDpwCKQmPW9Cg", + "value": "1000" + }, "Fee": "10", "Flags": 2097152, "Sequence": 1432289, @@ -4535,8 +4833,13 @@ "json": { "Account": "rP5ZkB5RZQaECsSVR4DeSFK4fAw52BYtbw", "TransactionType": "AMMDeposit", - "Asset": {"currency": "XRP"}, - "Asset2": {"currency": "ETH", "issuer": "rPyfep3gcLzkosKC9XiE77Y8DZWG6iWDT9"}, + "Asset": { + "currency": "XRP" + }, + "Asset2": { + "currency": "ETH", + "issuer": "rPyfep3gcLzkosKC9XiE77Y8DZWG6iWDT9" + }, "Amount": "1000", "EPrice": "25", "Fee": "10", @@ -4551,9 +4854,18 @@ "json": { "Account": "rP5ZkB5RZQaECsSVR4DeSFK4fAw52BYtbw", "TransactionType": "AMMWithdraw", - "Asset": {"currency": "XRP"}, - "Asset2": {"currency": "ETH", "issuer": "rPyfep3gcLzkosKC9XiE77Y8DZWG6iWDT9"}, - "LPTokenIn": {"currency": "B3813FCAB4EE68B3D0D735D6849465A9113EE048", "issuer": "rH438jEAzTs5PYtV6CHZqpDpwCKQmPW9Cg", "value": "1000"}, + "Asset": { + "currency": "XRP" + }, + "Asset2": { + "currency": "ETH", + "issuer": "rPyfep3gcLzkosKC9XiE77Y8DZWG6iWDT9" + }, + "LPTokenIn": { + "currency": "B3813FCAB4EE68B3D0D735D6849465A9113EE048", + "issuer": "rH438jEAzTs5PYtV6CHZqpDpwCKQmPW9Cg", + "value": "1000" + }, "Fee": "10", "Flags": 65536, "Sequence": 1432289, @@ -4566,8 +4878,13 @@ "json": { "Account": "rP5ZkB5RZQaECsSVR4DeSFK4fAw52BYtbw", "TransactionType": "AMMWithdraw", - "Asset": {"currency": "XRP"}, - "Asset2": {"currency": "ETH", "issuer": "rPyfep3gcLzkosKC9XiE77Y8DZWG6iWDT9"}, + "Asset": { + "currency": "XRP" + }, + "Asset2": { + "currency": "ETH", + "issuer": "rPyfep3gcLzkosKC9XiE77Y8DZWG6iWDT9" + }, "Amount": "1000", "Fee": "10", "Flags": 524288, @@ -4581,10 +4898,19 @@ "json": { "Account": "rP5ZkB5RZQaECsSVR4DeSFK4fAw52BYtbw", "TransactionType": "AMMWithdraw", - "Asset": {"currency": "XRP"}, - "Asset2": {"currency": "ETH", "issuer": "rPyfep3gcLzkosKC9XiE77Y8DZWG6iWDT9"}, + "Asset": { + "currency": "XRP" + }, + "Asset2": { + "currency": "ETH", + "issuer": "rPyfep3gcLzkosKC9XiE77Y8DZWG6iWDT9" + }, "Amount": "1000", - "Amount2": {"currency": "ETH", "issuer": "rPyfep3gcLzkosKC9XiE77Y8DZWG6iWDT9", "value": "500"}, + "Amount2": { + "currency": "ETH", + "issuer": "rPyfep3gcLzkosKC9XiE77Y8DZWG6iWDT9", + "value": "500" + }, "Fee": "10", "Flags": 1048576, "Sequence": 1432289, @@ -4597,10 +4923,19 @@ "json": { "Account": "rP5ZkB5RZQaECsSVR4DeSFK4fAw52BYtbw", "TransactionType": "AMMWithdraw", - "Asset": {"currency": "XRP"}, - "Asset2": {"currency": "ETH", "issuer": "rPyfep3gcLzkosKC9XiE77Y8DZWG6iWDT9"}, + "Asset": { + "currency": "XRP" + }, + "Asset2": { + "currency": "ETH", + "issuer": "rPyfep3gcLzkosKC9XiE77Y8DZWG6iWDT9" + }, "Amount": "1000", - "LPTokenIn": {"currency": "B3813FCAB4EE68B3D0D735D6849465A9113EE048", "issuer": "rH438jEAzTs5PYtV6CHZqpDpwCKQmPW9Cg", "value": "1000"}, + "LPTokenIn": { + "currency": "B3813FCAB4EE68B3D0D735D6849465A9113EE048", + "issuer": "rH438jEAzTs5PYtV6CHZqpDpwCKQmPW9Cg", + "value": "1000" + }, "Fee": "10", "Flags": 2097152, "Sequence": 1432289, @@ -4613,8 +4948,13 @@ "json": { "Account": "rP5ZkB5RZQaECsSVR4DeSFK4fAw52BYtbw", "TransactionType": "AMMWithdraw", - "Asset": {"currency": "XRP"}, - "Asset2": {"currency": "ETH", "issuer": "rPyfep3gcLzkosKC9XiE77Y8DZWG6iWDT9"}, + "Asset": { + "currency": "XRP" + }, + "Asset2": { + "currency": "ETH", + "issuer": "rPyfep3gcLzkosKC9XiE77Y8DZWG6iWDT9" + }, "Amount": "1000", "EPrice": "25", "Fee": "10", @@ -4629,11 +4969,30 @@ "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"}, + "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, @@ -4646,8 +5005,13 @@ "json": { "Account": "rP5ZkB5RZQaECsSVR4DeSFK4fAw52BYtbw", "TransactionType": "AMMVote", - "Asset": {"currency": "XRP"}, - "Asset2": {"currency": "ETH", "issuer": "rPyfep3gcLzkosKC9XiE77Y8DZWG6iWDT9"}, + "Asset": { + "currency": "XRP" + }, + "Asset2": { + "currency": "ETH", + "issuer": "rPyfep3gcLzkosKC9XiE77Y8DZWG6iWDT9" + }, "TradingFee": 234, "Fee": "10", "Flags": 0, @@ -4655,19 +5019,22 @@ "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" } - }] + ], + "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-integration-tests/pom.xml b/xrpl4j-integration-tests/pom.xml index ffa4fb1c2..eefdeccd5 100644 --- a/xrpl4j-integration-tests/pom.xml +++ b/xrpl4j-integration-tests/pom.xml @@ -28,6 +28,16 @@ jackson-core test + + com.fasterxml.jackson.core + jackson-annotations + test + + + org.immutables + value + test + com.fasterxml.jackson.core jackson-databind 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 53307bcba..261e938de 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 @@ -27,6 +27,7 @@ import static org.hamcrest.core.Is.is; import com.fasterxml.jackson.core.JsonProcessingException; +import com.google.common.base.Preconditions; import com.google.common.primitives.UnsignedInteger; import com.google.common.primitives.UnsignedLong; import org.awaitility.Durations; @@ -76,6 +77,7 @@ import org.xrpl.xrpl4j.model.transactions.TransactionResultCodes; import org.xrpl.xrpl4j.model.transactions.TransactionType; import org.xrpl.xrpl4j.model.transactions.TrustSet; +import org.xrpl.xrpl4j.model.transactions.XChainCreateBridge; import org.xrpl.xrpl4j.model.transactions.XrpCurrencyAmount; import org.xrpl.xrpl4j.tests.environment.XrplEnvironment; @@ -205,6 +207,35 @@ protected void fundAccount(final Address address) { xrplEnvironment.fundAccount(address); } + protected TransactionResult signSubmitAndWait( + T transaction, + KeyPair keyPair, + Class transactionType + ) + throws JsonRpcClientErrorException, JsonProcessingException { + Preconditions.checkArgument(transaction.lastLedgerSequence().isPresent()); + + SingleSignedTransaction signedTransaction = signatureService.sign( + keyPair.privateKey(), + transaction + ); + + SubmitResult voteSubmitResult = xrplClient.submit(signedTransaction); + assertThat(voteSubmitResult.engineResult()).isEqualTo(TransactionResultCodes.TES_SUCCESS); + + Finality finality = scanForFinality( + signedTransaction.hash(), + voteSubmitResult.validatedLedgerIndex(), + transaction.lastLedgerSequence().get(), + transaction.sequence(), + keyPair.publicKey().deriveAddress() + ); + + assertThat(finality.finalityStatus()).isEqualTo(FinalityStatus.VALIDATED_SUCCESS); + + return this.getValidatedTransaction(signedTransaction.hash(), transactionType); + } + ////////////////////// // Ledger Helpers ////////////////////// diff --git a/xrpl4j-integration-tests/src/test/java/org/xrpl/xrpl4j/tests/XChainIT.java b/xrpl4j-integration-tests/src/test/java/org/xrpl/xrpl4j/tests/XChainIT.java new file mode 100644 index 000000000..f98919765 --- /dev/null +++ b/xrpl4j-integration-tests/src/test/java/org/xrpl/xrpl4j/tests/XChainIT.java @@ -0,0 +1,1040 @@ +package org.xrpl.xrpl4j.tests; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatNoException; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.google.common.primitives.UnsignedInteger; +import com.google.common.primitives.UnsignedLong; +import org.immutables.value.Value; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.DisabledIf; +import org.xrpl.xrpl4j.client.JsonRpcClientErrorException; +import org.xrpl.xrpl4j.crypto.keys.KeyPair; +import org.xrpl.xrpl4j.crypto.keys.Seed; +import org.xrpl.xrpl4j.crypto.signing.Signature; +import org.xrpl.xrpl4j.model.AddressConstants; +import org.xrpl.xrpl4j.model.client.accounts.AccountInfoResult; +import org.xrpl.xrpl4j.model.client.accounts.AccountObjectsResult; +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.TransactionResult; +import org.xrpl.xrpl4j.model.flags.XChainModifyBridgeFlags; +import org.xrpl.xrpl4j.model.jackson.ObjectMapperFactory; +import org.xrpl.xrpl4j.model.ledger.AttestationClaim; +import org.xrpl.xrpl4j.model.ledger.AttestationCreateAccount; +import org.xrpl.xrpl4j.model.ledger.BridgeObject; +import org.xrpl.xrpl4j.model.ledger.Issue; +import org.xrpl.xrpl4j.model.ledger.LedgerObject; +import org.xrpl.xrpl4j.model.ledger.SignerEntry; +import org.xrpl.xrpl4j.model.ledger.SignerEntryWrapper; +import org.xrpl.xrpl4j.model.ledger.XChainClaimAttestation; +import org.xrpl.xrpl4j.model.ledger.XChainClaimProofSig; +import org.xrpl.xrpl4j.model.ledger.XChainCreateAccountAttestation; +import org.xrpl.xrpl4j.model.ledger.XChainCreateAccountProofSig; +import org.xrpl.xrpl4j.model.ledger.XChainOwnedClaimIdObject; +import org.xrpl.xrpl4j.model.ledger.XChainOwnedCreateAccountClaimIdObject; +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.CurrencyAmount; +import org.xrpl.xrpl4j.model.transactions.Hash256; +import org.xrpl.xrpl4j.model.transactions.IssuedCurrencyAmount; +import org.xrpl.xrpl4j.model.transactions.SignerListSet; +import org.xrpl.xrpl4j.model.transactions.TransactionMetadata; +import org.xrpl.xrpl4j.model.transactions.XChainAccountCreateCommit; +import org.xrpl.xrpl4j.model.transactions.XChainAddAccountCreateAttestation; +import org.xrpl.xrpl4j.model.transactions.XChainAddClaimAttestation; +import org.xrpl.xrpl4j.model.transactions.XChainBridge; +import org.xrpl.xrpl4j.model.transactions.XChainClaim; +import org.xrpl.xrpl4j.model.transactions.XChainClaimId; +import org.xrpl.xrpl4j.model.transactions.XChainCommit; +import org.xrpl.xrpl4j.model.transactions.XChainCount; +import org.xrpl.xrpl4j.model.transactions.XChainCreateBridge; +import org.xrpl.xrpl4j.model.transactions.XChainCreateClaimId; +import org.xrpl.xrpl4j.model.transactions.XChainModifyBridge; +import org.xrpl.xrpl4j.model.transactions.XrpCurrencyAmount; +import org.xrpl.xrpl4j.model.transactions.metadata.AffectedNode; +import org.xrpl.xrpl4j.model.transactions.metadata.CreatedNode; +import org.xrpl.xrpl4j.model.transactions.metadata.DeletedNode; +import org.xrpl.xrpl4j.model.transactions.metadata.ImmutableMetaXChainCreateAccountAttestation; +import org.xrpl.xrpl4j.model.transactions.metadata.ImmutableMetaXChainCreateAccountProofSig; +import org.xrpl.xrpl4j.model.transactions.metadata.MetaLedgerEntryType; +import org.xrpl.xrpl4j.model.transactions.metadata.MetaXChainOwnedCreateAccountClaimIdObject; + +import java.math.BigDecimal; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + + +@DisabledIf(value = "shouldRun", disabledReason = "XChainIT only runs with local rippled nodes.") +public class XChainIT extends AbstractIT { + + static boolean shouldRun() { + return System.getProperty("useTestnet") != null || + System.getProperty("useDevnet") != null || + System.getProperty("useClioTestnet") != null; + } + + TestBridge testBridge; + + { + try { + testBridge = setupXrpToXrpBridge(); + } catch (JsonRpcClientErrorException | JsonProcessingException e) { + throw new RuntimeException(e); + } + } + + @Test + void testXChainAccountCreateCommit() throws JsonRpcClientErrorException, JsonProcessingException { + XrpCurrencyAmount initialDoorBalance = this.getValidatedAccountInfo(testBridge.bridge().lockingChainDoor()) + .accountData() + .balance(); + + KeyPair sender = this.createRandomAccountEd25519(); + KeyPair destination = Seed.ed25519Seed().deriveKeyPair(); + XrpCurrencyAmount amount = XrpCurrencyAmount.ofDrops(10000000); + + AccountInfoResult senderAccountInfo = this.scanForResult( + () -> getValidatedAccountInfo(sender.publicKey().deriveAddress()) + ); + + FeeResult feeResult = xrplClient.fee(); + XChainAccountCreateCommit transaction = XChainAccountCreateCommit.builder() + .account(sender.publicKey().deriveAddress()) + .sequence(senderAccountInfo.accountData().sequence()) + .fee(FeeUtils.computeNetworkFees(feeResult).recommendedFee()) + .lastLedgerSequence(senderAccountInfo.ledgerIndexSafe().plus(UnsignedInteger.valueOf(4)).unsignedIntegerValue()) + .signingPublicKey(sender.publicKey()) + .xChainBridge(testBridge.bridge()) + .amount(amount) + .signatureReward(testBridge.signatureReward()) + .destination(destination.publicKey().deriveAddress()) + .build(); + + this.signSubmitAndWait(transaction, sender, XChainAccountCreateCommit.class); + + XrpCurrencyAmount finalBalance = this.getValidatedAccountInfo(testBridge.bridge().lockingChainDoor()) + .accountData() + .balance(); + + assertThat(finalBalance).isEqualTo(initialDoorBalance.plus(amount).plus(testBridge.signatureReward())); + } + + @Test + void testXChainAddAccountCreateAttestation() throws JsonProcessingException, JsonRpcClientErrorException { + KeyPair source = this.createRandomAccountEd25519(); + KeyPair destination = this.createRandomAccountEd25519(); + KeyPair otherChainSource = this.createRandomAccountEd25519(); + + // Create an attestation for witness 1 to sign + AttestationCreateAccount attestation = AttestationCreateAccount.builder() + .xChainBridge(testBridge.bridge()) + .otherChainSource(otherChainSource.publicKey().deriveAddress()) + .amount(XrpCurrencyAmount.ofXrp(BigDecimal.valueOf(300))) + .attestationRewardAccount(testBridge.witnessKeyPair().publicKey().deriveAddress()) + .wasLockingChainSend(false) + .destination(destination.publicKey().deriveAddress()) + .signatureReward(testBridge.signatureReward()) + .xChainAccountCreateCount(XChainCount.of(UnsignedLong.ONE)) + .build(); + + // Witness 1 signs the attestation + Signature attestationSignature = signatureService.sign(testBridge.witnessKeyPair().privateKey(), attestation); + + AccountInfoResult sourceAccountInfo = this.scanForResult( + () -> getValidatedAccountInfo(source.publicKey().deriveAddress()) + ); + FeeResult feeResult = xrplClient.fee(); + // Add the attestation from Witness 1 + XChainAddAccountCreateAttestation transaction = XChainAddAccountCreateAttestation.builder() + .account(source.publicKey().deriveAddress()) + .xChainBridge(testBridge.bridge()) + .otherChainSource(otherChainSource.publicKey().deriveAddress()) + .amount(XrpCurrencyAmount.ofXrp(BigDecimal.valueOf(300))) + .wasLockingChainSend(false) + .xChainAccountCreateCount(XChainCount.of(UnsignedLong.ONE)) + .destination(destination.publicKey().deriveAddress()) + .signatureReward(testBridge.signatureReward()) + .publicKey(testBridge.witnessKeyPair().publicKey()) + .signature(attestationSignature) + .attestationRewardAccount(testBridge.witnessKeyPair().publicKey().deriveAddress()) + .attestationSignerAccount(testBridge.witnessKeyPair().publicKey().deriveAddress()) + .sequence(sourceAccountInfo.accountData().sequence()) + .fee(FeeUtils.computeNetworkFees(feeResult).recommendedFee()) + .lastLedgerSequence(sourceAccountInfo.ledgerIndexSafe().unsignedIntegerValue().plus(UnsignedInteger.valueOf(4))) + .signingPublicKey(source.publicKey()) + .build(); + + TransactionResult validatedTransaction = this.signSubmitAndWait( + transaction, + source, + XChainAddAccountCreateAttestation.class + ); + + assertThat(validatedTransaction.metadata()).isPresent(); + TransactionMetadata metadata = validatedTransaction.metadata().get(); + Optional maybeCreatedClaimId = metadata.affectedNodes().stream() + .filter(node -> node.ledgerEntryType().equals(MetaLedgerEntryType.XCHAIN_OWNED_CREATE_ACCOUNT_CLAIM_ID)) + .findFirst(); + + assertThat(maybeCreatedClaimId).isNotEmpty(); + AffectedNode createdClaimId = maybeCreatedClaimId.get(); + assertThat(createdClaimId).isInstanceOf(CreatedNode.class); + MetaXChainOwnedCreateAccountClaimIdObject newFields = + ((CreatedNode) createdClaimId) + .newFields(); + assertThat(newFields.account()).isNotEmpty().get().isEqualTo(testBridge.bridge().lockingChainDoor()); + assertThat(newFields.xChainBridge()).isNotEmpty().get().isEqualTo(testBridge.bridge()); + assertThat(newFields.xChainAccountCreateCount()).isNotEmpty().get().isEqualTo(XChainCount.of(UnsignedLong.ONE)); + assertThat(newFields.xChainCreateAccountAttestations()).containsExactly( + ImmutableMetaXChainCreateAccountAttestation.builder() + .xChainCreateAccountProofSig( + ImmutableMetaXChainCreateAccountProofSig.builder() + .amount(transaction.amount()) + .signatureReward(transaction.signatureReward()) + .attestationRewardAccount(transaction.attestationRewardAccount()) + .attestationSignerAccount(transaction.attestationSignerAccount()) + .destination(transaction.destination()) + .publicKey(testBridge.witnessKeyPair().publicKey()) + .wasLockingChainSend(false) + .build() + ) + .build() + ); + + XChainOwnedCreateAccountClaimIdObject claimIdObject = xrplClient.ledgerEntry( + LedgerEntryRequestParams.index( + createdClaimId.ledgerIndex(), XChainOwnedCreateAccountClaimIdObject.class, LedgerSpecifier.VALIDATED + ) + ).node(); + + assertThat(claimIdObject.account()).isEqualTo(testBridge.bridge().lockingChainDoor()); + assertThat(claimIdObject.xChainAccountCreateCount()).isEqualTo(XChainCount.of(UnsignedLong.ONE)); + assertThat(claimIdObject.xChainBridge()).isEqualTo(testBridge.bridge()); + assertThat(claimIdObject.xChainCreateAccountAttestations()).containsExactly( + XChainCreateAccountAttestation.of( + XChainCreateAccountProofSig.builder() + .amount(transaction.amount()) + .signatureReward(transaction.signatureReward().get()) + .attestationRewardAccount(transaction.attestationRewardAccount()) + .attestationSignerAccount(transaction.attestationSignerAccount()) + .destination(transaction.destination()) + .publicKey(testBridge.witnessKeyPair().publicKey()) + .wasLockingChainSend(false) + .build() + ) + ); + + // Create an attestation for witness 2 to sign + AttestationCreateAccount attestation2 = AttestationCreateAccount.builder() + .xChainBridge(testBridge.bridge()) + .otherChainSource(otherChainSource.publicKey().deriveAddress()) + .amount(XrpCurrencyAmount.ofXrp(BigDecimal.valueOf(300))) + .attestationRewardAccount(testBridge.witnessKeyPair2().publicKey().deriveAddress()) + .wasLockingChainSend(false) + .destination(destination.publicKey().deriveAddress()) + .signatureReward(testBridge.signatureReward()) + .xChainAccountCreateCount(XChainCount.of(UnsignedLong.ONE)) + .build(); + + // Witness 2 signs the attestation + Signature attestationSignature2 = signatureService.sign(testBridge.witnessKeyPair2().privateKey(), attestation2); + + AccountInfoResult sourceAccountInfo2 = this.scanForResult( + () -> getValidatedAccountInfo(source.publicKey().deriveAddress()) + ); + // Add witness 2's attestation + XChainAddAccountCreateAttestation transaction2 = XChainAddAccountCreateAttestation.builder() + .account(source.publicKey().deriveAddress()) + .xChainBridge(testBridge.bridge()) + .otherChainSource(otherChainSource.publicKey().deriveAddress()) + .amount(XrpCurrencyAmount.ofXrp(BigDecimal.valueOf(300))) + .wasLockingChainSend(false) + .xChainAccountCreateCount(XChainCount.of(UnsignedLong.ONE)) + .destination(destination.publicKey().deriveAddress()) + .signatureReward(testBridge.signatureReward()) + .publicKey(testBridge.witnessKeyPair2().publicKey()) + .signature(attestationSignature2) + .attestationRewardAccount(testBridge.witnessKeyPair2().publicKey().deriveAddress()) + .attestationSignerAccount(testBridge.witnessKeyPair2().publicKey().deriveAddress()) + .sequence(sourceAccountInfo2.accountData().sequence()) + .fee(FeeUtils.computeNetworkFees(feeResult).recommendedFee()) + .lastLedgerSequence(sourceAccountInfo2.ledgerIndexSafe().unsignedIntegerValue().plus(UnsignedInteger.valueOf(4))) + .signingPublicKey(source.publicKey()) + .build(); + + TransactionResult validatedTransaction2 = this.signSubmitAndWait( + transaction2, + source, + XChainAddAccountCreateAttestation.class + ); + + // The XChainOwnedCreateAccountClaimID should have gotten deleted + assertThat(validatedTransaction2.metadata()).isNotEmpty(); + TransactionMetadata metadata2 = validatedTransaction2.metadata().get(); + Optional maybeDeletedClaimId = metadata2.affectedNodes().stream() + .filter(node -> node.ledgerEntryType().equals(MetaLedgerEntryType.XCHAIN_OWNED_CREATE_ACCOUNT_CLAIM_ID)) + .findFirst(); + + assertThat(maybeDeletedClaimId).isNotEmpty(); + AffectedNode deletedClaimId = maybeDeletedClaimId.get(); + assertThat(deletedClaimId).isInstanceOf(DeletedNode.class); + + // And the destination account should exist now. + assertThatNoException().isThrownBy(() -> this.getValidatedAccountInfo(destination.publicKey().deriveAddress())); + } + + @Test + void testXChainAddClaimAttestationXrpToXrpBridge() throws JsonRpcClientErrorException, JsonProcessingException { + KeyPair otherChainSource = Seed.ed25519Seed().deriveKeyPair(); + KeyPair source = this.createRandomAccountEd25519(); + XrpCurrencyAmount amount = XrpCurrencyAmount.ofXrp(BigDecimal.valueOf(10)); + + AccountInfoResult sourceAccountInfo = this.scanForResult( + () -> this.getValidatedAccountInfo(source.publicKey().deriveAddress()) + ); + XrpCurrencyAmount fee = FeeUtils.computeNetworkFees(xrplClient.fee()).recommendedFee(); + XChainOwnedClaimIdObject claimIdObject = createClaimId( + sourceAccountInfo, + fee, + source, + testBridge, + otherChainSource + ); + + XChainAddClaimAttestation addAttestation = addClaimAttestation( + otherChainSource, + amount, + source, + fee, + testBridge.bridge(), + testBridge.witnessKeyPair(), + false).transaction(); + + XChainOwnedClaimIdObject claimIdObjectAfterAdd = xrplClient.ledgerEntry( + LedgerEntryRequestParams.index( + claimIdObject.index(), + XChainOwnedClaimIdObject.class, + LedgerSpecifier.VALIDATED + )).node(); + + assertThat(claimIdObjectAfterAdd.xChainClaimAttestations()).containsExactly( + XChainClaimAttestation.of( + XChainClaimProofSig.builder() + .amount(amount) + .publicKey(addAttestation.publicKey()) + .wasLockingChainSend(false) + .attestationSignerAccount(addAttestation.attestationSignerAccount()) + .attestationRewardAccount(addAttestation.attestationRewardAccount()) + .destination(source.publicKey().deriveAddress()) + .build() + ) + ); + + XrpCurrencyAmount initialBalance = this.getValidatedAccountInfo(source.publicKey().deriveAddress()) + .accountData().balance(); + + addClaimAttestation( + otherChainSource, + amount, + source, + fee, + testBridge.bridge(), + testBridge.witnessKeyPair2(), + false).transaction(); + + XrpCurrencyAmount finalBalance = this.getValidatedAccountInfo(source.publicKey().deriveAddress()) + .accountData().balance(); + + assertThat(finalBalance).isEqualTo( + initialBalance.plus(amount).minus(testBridge.signatureReward()) + ); + } + + @Test + void testAddClaimAttestationIouToIouBridge() throws JsonRpcClientErrorException, JsonProcessingException { + KeyPair lockingDoor = Seed.ed25519Seed().deriveKeyPair(); + KeyPair issuer = Seed.ed25519Seed().deriveKeyPair(); + + KeyPair source = this.createRandomAccountEd25519(); + XrpCurrencyAmount fee = FeeUtils.computeNetworkFees(xrplClient.fee()).recommendedFee(); + enableRippling(source, fee); + + KeyPair destination = this.createRandomAccountEd25519(); + + this.createTrustLine( + "USD", + "1000000000", + source, + destination, + fee + ); + + TestBridge iouBridge = setupBridge( + source, + XChainBridge.builder() + .lockingChainDoor(lockingDoor.publicKey().deriveAddress()) + .lockingChainIssue( + Issue.builder() + .issuer(issuer.publicKey().deriveAddress()) + .currency("USD") + .build() + ) + .issuingChainDoor(source.publicKey().deriveAddress()) + .issuingChainIssue( + Issue.builder() + .issuer(source.publicKey().deriveAddress()) + .currency("USD") + .build() + ) + .build(), + Optional.empty() + ); + + KeyPair otherChainSource = this.createRandomAccountEd25519(); + IssuedCurrencyAmount amount = IssuedCurrencyAmount.builder() + .currency("USD") + .issuer(issuer.publicKey().deriveAddress()) + .value("10") + .build(); + + AccountInfoResult sourceAccountInfo = this.scanForResult( + () -> this.getValidatedAccountInfo(source.publicKey().deriveAddress())); + + XChainOwnedClaimIdObject claimIdObject = createClaimId(sourceAccountInfo, fee, source, iouBridge, otherChainSource); + + XChainAddClaimAttestation addAttestation = addClaimAttestation( + otherChainSource, + amount, + destination, + fee, + iouBridge.bridge(), + iouBridge.witnessKeyPair(), + true + ).transaction(); + + XChainOwnedClaimIdObject claimIdObjectAfterAdd = xrplClient.ledgerEntry( + LedgerEntryRequestParams.index( + claimIdObject.index(), + XChainOwnedClaimIdObject.class, + LedgerSpecifier.VALIDATED + )).node(); + + assertThat(claimIdObjectAfterAdd.xChainClaimAttestations()).containsExactly( + XChainClaimAttestation.of( + XChainClaimProofSig.builder() + .amount(amount) + .publicKey(addAttestation.publicKey()) + .wasLockingChainSend(true) + .attestationSignerAccount(addAttestation.attestationSignerAccount()) + .attestationRewardAccount(addAttestation.attestationRewardAccount()) + .destination(destination.publicKey().deriveAddress()) + .build() + ) + ); + + BigDecimal initialBalance = new BigDecimal(this.getValidatedAccountLines( + destination.publicKey().deriveAddress(), + iouBridge.bridge().issuingChainIssue().issuer().get() + ).lines().get(0).balance()); + + addClaimAttestation( + otherChainSource, + amount, + destination, + fee, + iouBridge.bridge(), + iouBridge.witnessKeyPair2(), + true + ).transaction(); + + BigDecimal finalBalance = new BigDecimal(this.getValidatedAccountLines( + destination.publicKey().deriveAddress(), + iouBridge.bridge().issuingChainIssue().issuer().get() + ).lines().get(0).balance()); + + assertThat(finalBalance).isEqualTo( + initialBalance.add(new BigDecimal(amount.value())) + ); + } + + @Test + void testXChainClaim() throws JsonRpcClientErrorException, JsonProcessingException { + KeyPair destination = this.createRandomAccountEd25519(); + KeyPair otherChainSource = Seed.ed25519Seed().deriveKeyPair(); + XrpCurrencyAmount amount = XrpCurrencyAmount.ofXrp(BigDecimal.valueOf(10)); + + AccountInfoResult destinationAccountInfo = this.scanForResult( + () -> this.getValidatedAccountInfo(destination.publicKey().deriveAddress()) + ); + XrpCurrencyAmount fee = FeeUtils.computeNetworkFees(xrplClient.fee()).recommendedFee(); + + XChainOwnedClaimIdObject claimIdObject = createClaimId( + destinationAccountInfo, + fee, + destination, + testBridge, + otherChainSource + ); + + XChainAddClaimAttestation addAttestation = addClaimAttestationForClaim( + otherChainSource, + amount, + fee, + testBridge.bridge(), + testBridge.witnessKeyPair(), + false + ).transaction(); + + XChainOwnedClaimIdObject claimIdObjectAfterAdd = xrplClient.ledgerEntry( + LedgerEntryRequestParams.index( + claimIdObject.index(), + XChainOwnedClaimIdObject.class, + LedgerSpecifier.VALIDATED + )).node(); + + assertThat(claimIdObjectAfterAdd.xChainClaimAttestations()).containsExactly( + XChainClaimAttestation.of( + XChainClaimProofSig.builder() + .amount(amount) + .publicKey(addAttestation.publicKey()) + .wasLockingChainSend(false) + .attestationSignerAccount(addAttestation.attestationSignerAccount()) + .attestationRewardAccount(addAttestation.attestationRewardAccount()) + .build() + ) + ); + + XChainAddClaimAttestation addAttestation2 = addClaimAttestationForClaim( + otherChainSource, + amount, + fee, + testBridge.bridge(), + testBridge.witnessKeyPair2(), + false + ).transaction(); + + XChainOwnedClaimIdObject claimIdObjectAfterAdd2 = xrplClient.ledgerEntry( + LedgerEntryRequestParams.index( + claimIdObject.index(), + XChainOwnedClaimIdObject.class, + LedgerSpecifier.VALIDATED + )).node(); + + assertThat(claimIdObjectAfterAdd2.xChainClaimAttestations()).containsExactlyInAnyOrder( + XChainClaimAttestation.of( + XChainClaimProofSig.builder() + .amount(amount) + .publicKey(addAttestation.publicKey()) + .wasLockingChainSend(false) + .attestationSignerAccount(addAttestation.attestationSignerAccount()) + .attestationRewardAccount(addAttestation.attestationRewardAccount()) + .build() + ), + XChainClaimAttestation.of( + XChainClaimProofSig.builder() + .amount(amount) + .publicKey(addAttestation2.publicKey()) + .wasLockingChainSend(false) + .attestationSignerAccount(addAttestation2.attestationSignerAccount()) + .attestationRewardAccount(addAttestation2.attestationRewardAccount()) + .build() + ) + ); + + XrpCurrencyAmount initialBalance = this.getValidatedAccountInfo(destination.publicKey().deriveAddress()) + .accountData() + .balance(); + XChainClaim chainClaim = XChainClaim.builder() + .account(destinationAccountInfo.accountData().account()) + .sequence(destinationAccountInfo.accountData().sequence().plus(UnsignedInteger.ONE)) + .fee(fee) + .lastLedgerSequence( + destinationAccountInfo.ledgerIndexSafe().unsignedIntegerValue().plus(UnsignedInteger.valueOf(10)) + ) + .signingPublicKey(destination.publicKey()) + .xChainBridge(testBridge.bridge()) + .xChainClaimId(XChainClaimId.of(UnsignedLong.ONE)) + .amount(amount) + .destination(destination.publicKey().deriveAddress()) + .build(); + + this.signSubmitAndWait(chainClaim, destination, XChainClaim.class); + + XrpCurrencyAmount finalBalance = this.getValidatedAccountInfo(destination.publicKey().deriveAddress()) + .accountData() + .balance(); + + assertThat(finalBalance).isEqualTo( + initialBalance.plus(amount).minus(testBridge.signatureReward()).minus(fee) + ); + } + + @Test + void testXChainCommit() throws JsonRpcClientErrorException, JsonProcessingException { + KeyPair source = this.createRandomAccountEd25519(); + KeyPair destination = this.createRandomAccountEd25519(); + AccountInfoResult sourceAccountInfo = this.scanForResult( + () -> this.getValidatedAccountInfo(source.publicKey().deriveAddress())); + this.scanForResult(() -> this.getValidatedAccountInfo(destination.publicKey().deriveAddress())); + + XrpCurrencyAmount initialDoorBalance = this.getValidatedAccountInfo(testBridge.bridge().lockingChainDoor()) + .accountData() + .balance(); + + XrpCurrencyAmount initialSourceBalance = sourceAccountInfo.accountData().balance(); + XrpCurrencyAmount amount = XrpCurrencyAmount.ofDrops(10000000); + XrpCurrencyAmount fee = FeeUtils.computeNetworkFees(xrplClient.fee()).recommendedFee(); + XChainCommit chainCommit = XChainCommit.builder() + .account(source.publicKey().deriveAddress()) + .sequence(sourceAccountInfo.accountData().sequence()) + .fee(fee) + .lastLedgerSequence(sourceAccountInfo.ledgerIndexSafe().unsignedIntegerValue().plus(UnsignedInteger.valueOf(4))) + .signingPublicKey(source.publicKey()) + .xChainBridge(testBridge.bridge()) + .xChainClaimId(XChainClaimId.of(UnsignedLong.ONE)) + .amount(amount) + .otherChainDestination(destination.publicKey().deriveAddress()) + .build(); + + this.signSubmitAndWait(chainCommit, source, XChainCommit.class); + + XrpCurrencyAmount finalDoorBalance = this.getValidatedAccountInfo(testBridge.bridge().lockingChainDoor()) + .accountData() + .balance(); + + XrpCurrencyAmount finalSourceBalance = this.getValidatedAccountInfo(source.publicKey().deriveAddress()) + .accountData() + .balance(); + + assertThat(finalDoorBalance).isEqualTo( + initialDoorBalance.plus(amount) + ); + + assertThat(finalSourceBalance).isEqualTo( + initialSourceBalance.minus(amount).minus(chainCommit.fee()) + ); + } + + @Test + void testXChainModifyBridge() throws JsonRpcClientErrorException, JsonProcessingException { + KeyPair source = this.createRandomAccountEd25519(); + AccountInfoResult sourceAccountInfo = this.scanForResult( + () -> this.getValidatedAccountInfo(source.publicKey().deriveAddress()) + ); + + XrpCurrencyAmount fee = FeeUtils.computeNetworkFees(xrplClient.fee()).recommendedFee(); + + XChainCreateBridge createBridge = XChainCreateBridge.builder() + .account(sourceAccountInfo.accountData().account()) + .sequence(sourceAccountInfo.accountData().sequence()) + .fee(fee) + .lastLedgerSequence(sourceAccountInfo.ledgerIndexSafe().unsignedIntegerValue().plus(UnsignedInteger.valueOf(4))) + .signingPublicKey(source.publicKey()) + .xChainBridge( + XChainBridge.builder() + .lockingChainDoor(source.publicKey().deriveAddress()) + .lockingChainIssue(Issue.XRP) + .issuingChainDoor(AddressConstants.GENESIS_ACCOUNT) + .issuingChainIssue(Issue.XRP) + .build() + ) + .signatureReward(XrpCurrencyAmount.ofDrops(200)) + .minAccountCreateAmount(XrpCurrencyAmount.ofDrops(10000000)) + .build(); + + Hash256 bridgeId = this.signSubmitAndWait(createBridge, source, XChainCreateBridge.class) + .metadata() + .flatMap(transactionMetadata -> transactionMetadata.affectedNodes().stream() + .filter(node -> node.ledgerEntryType().equals(MetaLedgerEntryType.BRIDGE)) + .findFirst() + ).map(AffectedNode::ledgerIndex) + .get(); + + BridgeObject bridgeObject = xrplClient.ledgerEntry( + LedgerEntryRequestParams.index( + bridgeId, + BridgeObject.class, + LedgerSpecifier.VALIDATED + ) + ).node(); + + assertThat(bridgeObject.signatureReward()).isEqualTo(XrpCurrencyAmount.ofDrops(200)); + assertThat(bridgeObject.minAccountCreateAmount()).isNotEmpty().get().isEqualTo(XrpCurrencyAmount.ofDrops(10000000)); + + XChainModifyBridge modifySignerReward = XChainModifyBridge.builder() + .account(sourceAccountInfo.accountData().account()) + .sequence(sourceAccountInfo.accountData().sequence().plus(UnsignedInteger.ONE)) + .fee(fee) + .lastLedgerSequence(sourceAccountInfo.ledgerIndexSafe().unsignedIntegerValue().plus(UnsignedInteger.valueOf(10))) + .signingPublicKey(source.publicKey()) + .xChainBridge(bridgeObject.xChainBridge()) + .signatureReward(XrpCurrencyAmount.ofDrops(300)) + .build(); + + this.signSubmitAndWait( + modifySignerReward, + source, + XChainModifyBridge.class + ); + + BridgeObject bridgeAfterModify = xrplClient.ledgerEntry( + LedgerEntryRequestParams.index( + bridgeId, + BridgeObject.class, + LedgerSpecifier.VALIDATED + ) + ).node(); + + assertThat(bridgeAfterModify.signatureReward()).isEqualTo(XrpCurrencyAmount.ofDrops(300)); + assertThat(bridgeAfterModify.minAccountCreateAmount()).isNotEmpty().get() + .isEqualTo(XrpCurrencyAmount.ofDrops(10000000)); + + XChainModifyBridge deleteMinCreateAmount = XChainModifyBridge.builder() + .account(sourceAccountInfo.accountData().account()) + .sequence(sourceAccountInfo.accountData().sequence().plus(UnsignedInteger.valueOf(2))) + .fee(fee) + .lastLedgerSequence(sourceAccountInfo.ledgerIndexSafe().unsignedIntegerValue().plus(UnsignedInteger.valueOf(14))) + .signingPublicKey(source.publicKey()) + .xChainBridge(bridgeObject.xChainBridge()) + .flags(XChainModifyBridgeFlags.CLEAR_ACCOUNT_CREATE_AMOUNT) + .build(); + + this.signSubmitAndWait( + deleteMinCreateAmount, + source, + XChainModifyBridge.class + ); + + bridgeAfterModify = xrplClient.ledgerEntry( + LedgerEntryRequestParams.index( + bridgeId, + BridgeObject.class, + LedgerSpecifier.VALIDATED + ) + ).node(); + + assertThat(bridgeAfterModify.signatureReward()).isEqualTo(XrpCurrencyAmount.ofDrops(300)); + assertThat(bridgeAfterModify.minAccountCreateAmount()).isEmpty(); + } + + private XChainOwnedClaimIdObject createClaimId( + AccountInfoResult sourceAccountInfo, + XrpCurrencyAmount fee, + KeyPair source, + TestBridge bridge, + KeyPair otherChainSource + ) throws JsonRpcClientErrorException, JsonProcessingException { + XChainCreateClaimId chainCreateClaimId = XChainCreateClaimId.builder() + .account(sourceAccountInfo.accountData().account()) + .sequence(sourceAccountInfo.accountData().sequence()) + .fee(fee) + .signingPublicKey(source.publicKey()) + .lastLedgerSequence(sourceAccountInfo.ledgerIndexSafe().plus(UnsignedInteger.valueOf(4)).unsignedIntegerValue()) + .xChainBridge(bridge.bridge()) + .signatureReward(bridge.signatureReward()) + .otherChainSource(otherChainSource.publicKey().deriveAddress()) + .build(); + + TransactionResult validatedCreateClaimId = this.signSubmitAndWait( + chainCreateClaimId, source, XChainCreateClaimId.class + ); + + assertThat(validatedCreateClaimId.metadata()).isNotEmpty(); + TransactionMetadata createClaimIdMetadata = validatedCreateClaimId.metadata().get(); + Optional maybeAffectedNode = createClaimIdMetadata.affectedNodes().stream() + .filter(node -> node.ledgerEntryType().equals(MetaLedgerEntryType.XCHAIN_OWNED_CLAIM_ID)) + .findFirst(); + assertThat(maybeAffectedNode).isNotEmpty(); + + XChainOwnedClaimIdObject claimIdObject = xrplClient.ledgerEntry( + LedgerEntryRequestParams.index(maybeAffectedNode.get().ledgerIndex(), + XChainOwnedClaimIdObject.class, LedgerSpecifier.VALIDATED + )).node(); + + assertThat(claimIdObject.xChainBridge()).isEqualTo(bridge.bridge()); + assertThat(claimIdObject.xChainClaimId()).isEqualTo(XChainClaimId.of(UnsignedLong.ONE)); + assertThat(claimIdObject.otherChainSource()).isEqualTo(otherChainSource.publicKey().deriveAddress()); + assertThat(claimIdObject.signatureReward()).isEqualTo(bridge.signatureReward()); + assertThat(claimIdObject.xChainClaimAttestations()).isEmpty(); + + return claimIdObject; + } + + private void enableRippling(KeyPair source, XrpCurrencyAmount fee) + throws JsonRpcClientErrorException, JsonProcessingException { + AccountInfoResult sourceAccountInfo = this.scanForResult( + () -> this.getValidatedAccountInfo(source.publicKey().deriveAddress()) + ); + AccountSet accountSet = AccountSet.builder() + .account(sourceAccountInfo.accountData().account()) + .sequence(sourceAccountInfo.accountData().sequence()) + .fee(fee) + .lastLedgerSequence(sourceAccountInfo.ledgerIndexSafe().unsignedIntegerValue().plus(UnsignedInteger.valueOf(4))) + .signingPublicKey(source.publicKey()) + .setFlag(AccountSetFlag.DEFAULT_RIPPLE) + .build(); + + this.signSubmitAndWait(accountSet, source, AccountSet.class); + } + + private TransactionResult addClaimAttestation( + KeyPair otherChainSource, + CurrencyAmount amount, + KeyPair destination, + XrpCurrencyAmount fee, + XChainBridge bridge, + KeyPair witnessKeyPair, + boolean wasLockingChainSend + ) throws JsonRpcClientErrorException, JsonProcessingException { + AttestationClaim attestation = AttestationClaim.builder() + .xChainBridge(bridge) + .otherChainSource(otherChainSource.publicKey().deriveAddress()) + .amount(amount) + .attestationRewardAccount(witnessKeyPair.publicKey().deriveAddress()) + .wasLockingChainSend(wasLockingChainSend) + .xChainClaimId(XChainClaimId.of(UnsignedLong.ONE)) + .destination(destination.publicKey().deriveAddress()) + .build(); + + Signature attestationSignature = signatureService.sign(witnessKeyPair.privateKey(), attestation); + + AccountInfoResult witnessAccountInfo = this.getValidatedAccountInfo( + witnessKeyPair.publicKey().deriveAddress() + ); + XChainAddClaimAttestation addAttestation = XChainAddClaimAttestation.builder() + .account(witnessAccountInfo.accountData().account()) + .sequence(witnessAccountInfo.accountData().sequence()) + .fee(fee) + .lastLedgerSequence(witnessAccountInfo.ledgerIndexSafe().unsignedIntegerValue().plus(UnsignedInteger.valueOf(4))) + .signingPublicKey(witnessKeyPair.publicKey()) + .xChainBridge(bridge) + .otherChainSource(otherChainSource.publicKey().deriveAddress()) + .amount(amount) + .wasLockingChainSend(wasLockingChainSend) + .xChainClaimId(XChainClaimId.of(UnsignedLong.ONE)) + .destination(destination.publicKey().deriveAddress()) + .publicKey(witnessKeyPair.publicKey()) + .signature(attestationSignature) + .attestationSignerAccount(witnessAccountInfo.accountData().account()) + .attestationRewardAccount(witnessAccountInfo.accountData().account()) + .build(); + + return this.signSubmitAndWait( + addAttestation, witnessKeyPair, XChainAddClaimAttestation.class + ); + } + + private TransactionResult addClaimAttestationForClaim( + KeyPair otherChainSource, + CurrencyAmount amount, + XrpCurrencyAmount fee, + XChainBridge bridge, + KeyPair witnessKeyPair, + boolean wasLockingChainSend + ) throws JsonRpcClientErrorException, JsonProcessingException { + AttestationClaim attestation = AttestationClaim.builder() + .xChainBridge(bridge) + .otherChainSource(otherChainSource.publicKey().deriveAddress()) + .amount(amount) + .attestationRewardAccount(witnessKeyPair.publicKey().deriveAddress()) + .wasLockingChainSend(wasLockingChainSend) + .xChainClaimId(XChainClaimId.of(UnsignedLong.ONE)) + .build(); + + Signature attestationSignature = signatureService.sign(witnessKeyPair.privateKey(), attestation); + + AccountInfoResult witnessAccountInfo = this.getValidatedAccountInfo( + witnessKeyPair.publicKey().deriveAddress() + ); + XChainAddClaimAttestation addAttestation = XChainAddClaimAttestation.builder() + .account(witnessAccountInfo.accountData().account()) + .sequence(witnessAccountInfo.accountData().sequence()) + .fee(fee) + .lastLedgerSequence(witnessAccountInfo.ledgerIndexSafe().unsignedIntegerValue().plus(UnsignedInteger.valueOf(4))) + .signingPublicKey(witnessKeyPair.publicKey()) + .xChainBridge(bridge) + .otherChainSource(otherChainSource.publicKey().deriveAddress()) + .amount(amount) + .wasLockingChainSend(wasLockingChainSend) + .xChainClaimId(XChainClaimId.of(UnsignedLong.ONE)) + .publicKey(witnessKeyPair.publicKey()) + .signature(attestationSignature) + .attestationSignerAccount(witnessAccountInfo.accountData().account()) + .attestationRewardAccount(witnessAccountInfo.accountData().account()) + .build(); + + TransactionResult result = this.signSubmitAndWait( + addAttestation, witnessKeyPair, XChainAddClaimAttestation.class + ); + logger.info(ObjectMapperFactory.create().writerWithDefaultPrettyPrinter().writeValueAsString(result)); + return result; + } + + private TestBridge setupBridge( + KeyPair doorKeyPair, + XChainBridge bridge, + Optional minAccountCreateAmount + ) throws JsonRpcClientErrorException, JsonProcessingException { + XrpCurrencyAmount signatureReward = XrpCurrencyAmount.ofDrops(200); + Address doorAddress = doorKeyPair.publicKey().deriveAddress(); + + AccountInfoResult doorAccountInfo = this.scanForResult(() -> this.getValidatedAccountInfo(doorAddress)); + XChainCreateBridge createBridge = XChainCreateBridge.builder() + .account(doorAddress) + .xChainBridge(bridge) + .signatureReward(signatureReward) + .minAccountCreateAmount(minAccountCreateAmount) + .sequence(doorAccountInfo.accountData().sequence()) + .fee(XrpCurrencyAmount.ofDrops(100)) + .signingPublicKey(doorKeyPair.publicKey()) + .lastLedgerSequence(doorAccountInfo.ledgerIndexSafe().plus(UnsignedInteger.valueOf(4)).unsignedIntegerValue()) + .build(); + + signSubmitAndWait(createBridge, doorKeyPair, XChainCreateBridge.class); + + AccountObjectsResult doorObjects = this.getValidatedAccountObjects(doorAddress); + List bridgeObjects = doorObjects.accountObjects().stream() + .filter(object -> BridgeObject.class.isAssignableFrom(object.getClass())) + .collect(Collectors.toList()); + assertThat(bridgeObjects).hasSize(1); + assertThat(bridgeObjects.get(0)).isInstanceOf(BridgeObject.class); + BridgeObject bridgeObject = (BridgeObject) bridgeObjects.get(0); + assertThat(bridgeObject.account()).isEqualTo(doorAddress); + assertThat(bridgeObject.minAccountCreateAmount()).isEqualTo(createBridge.minAccountCreateAmount()); + assertThat(bridgeObject.signatureReward()).isEqualTo(signatureReward); + assertThat(bridgeObject.xChainAccountClaimCount()).isEqualTo(XChainCount.of(UnsignedLong.ZERO)); + assertThat(bridgeObject.xChainAccountCreateCount()).isEqualTo(XChainCount.of(UnsignedLong.ZERO)); + assertThat(bridgeObject.xChainBridge()).isEqualTo(bridge); + assertThat(bridgeObject.xChainClaimId()).isEqualTo(XChainClaimId.of(UnsignedLong.ZERO)); + + LedgerEntryResult bridgeLedgerEntry = xrplClient.ledgerEntry( + LedgerEntryRequestParams.index( + bridgeObject.index(), BridgeObject.class, LedgerSpecifier.of(doorObjects.ledgerIndexSafe()) + ) + ); + + assertThat(bridgeLedgerEntry.node()).isEqualTo(bridgeObject); + + LedgerEntryResult bridgeLedgerEntryTyped = xrplClient.ledgerEntry( + LedgerEntryRequestParams.bridge( + bridgeObject.account(), bridgeObject.xChainBridge(), LedgerSpecifier.of(doorObjects.ledgerIndexSafe()) + ) + ); + assertThat(bridgeLedgerEntryTyped).isEqualTo(bridgeLedgerEntry); + + KeyPair witnessKeyPair = this.createRandomAccountEd25519(); + Address witnessAddress = witnessKeyPair.publicKey().deriveAddress(); + KeyPair witnessKeyPair2 = this.createRandomAccountEd25519(); + Address witnessAddress2 = witnessKeyPair2.publicKey().deriveAddress(); + AccountInfoResult witnessAccountInfo = this.scanForResult(() -> this.getValidatedAccountInfo(witnessAddress)); + SignerListSet signerListSet = SignerListSet.builder() + .account(doorAddress) + .sequence(doorAccountInfo.accountData().sequence().plus(UnsignedInteger.ONE)) + .fee(XrpCurrencyAmount.ofDrops(100)) + .signingPublicKey(doorKeyPair.publicKey()) + .signerQuorum(UnsignedInteger.valueOf(2)) + .addSignerEntries( + SignerEntryWrapper.of( + SignerEntry.builder() + .account(witnessAddress) + .signerWeight(UnsignedInteger.ONE) + .build() + ), + SignerEntryWrapper.of( + SignerEntry.builder() + .account(witnessAddress2) + .signerWeight(UnsignedInteger.ONE) + .build() + ) + ) + .lastLedgerSequence(witnessAccountInfo.ledgerIndexSafe().plus(UnsignedInteger.valueOf(4)).unsignedIntegerValue()) + .build(); + + this.signSubmitAndWait(signerListSet, doorKeyPair, SignerListSet.class); + return TestBridge.builder() + .bridgeId(bridgeObject.index()) + .bridge(bridge) + .witnessKeyPair(witnessKeyPair) + .witnessKeyPair2(witnessKeyPair2) + .signatureReward(signatureReward) + .build(); + } + + private TestBridge setupXrpToXrpBridge() throws JsonRpcClientErrorException, JsonProcessingException { + KeyPair doorKeyPair = this.createRandomAccountEd25519(); + return setupBridge( + doorKeyPair, + XChainBridge.builder() + .lockingChainDoor(doorKeyPair.publicKey().deriveAddress()) + .lockingChainIssue(Issue.XRP) + .issuingChainDoor(AddressConstants.GENESIS_ACCOUNT) + .issuingChainIssue(Issue.XRP) + .build(), + Optional.of(XrpCurrencyAmount.ofDrops(10000000)) + ); + } + + + /** + * A cross chain bridge that can be used in integration tests. + */ + @Value.Immutable + @JsonSerialize(as = ImmutableTestBridge.class) + @JsonDeserialize(as = ImmutableTestBridge.class) + interface TestBridge { + + /** + * Construct a {@code TestBridge} builder. + * + * @return An {@link ImmutableTestBridge.Builder}. + */ + static ImmutableTestBridge.Builder builder() { + return ImmutableTestBridge.builder(); + } + + /** + * The ID of the {@link BridgeObject}. + * + * @return A {@link Hash256}. + */ + Hash256 bridgeId(); + + /** + * The {@link XChainBridge} spec. + * + * @return An {@link XChainBridge}. + */ + XChainBridge bridge(); + + /** + * The {@link KeyPair} of the first witness. + * + * @return A {@link KeyPair}. + */ + KeyPair witnessKeyPair(); + + /** + * The {@link KeyPair} of the second witness. + * + * @return A {@link KeyPair}. + */ + KeyPair witnessKeyPair2(); + + /** + * The signature reward of the bridge. + * + * @return An {@link XrpCurrencyAmount}. + */ + XrpCurrencyAmount signatureReward(); + + } +} diff --git a/xrpl4j-integration-tests/src/test/java/org/xrpl/xrpl4j/tests/environment/RippledContainer.java b/xrpl4j-integration-tests/src/test/java/org/xrpl/xrpl4j/tests/environment/RippledContainer.java index d5f5484b9..8ed24e93a 100644 --- a/xrpl4j-integration-tests/src/test/java/org/xrpl/xrpl4j/tests/environment/RippledContainer.java +++ b/xrpl4j-integration-tests/src/test/java/org/xrpl/xrpl4j/tests/environment/RippledContainer.java @@ -69,7 +69,7 @@ public class RippledContainer { * No-args constructor. */ public RippledContainer() { - rippledContainer = new GenericContainer("xrpllabsofficial/xrpld:latest") + rippledContainer = new GenericContainer("rippleci/rippled:2.0.0-b4") .withCreateContainerCmdModifier((Consumer) (cmd) -> cmd.withEntrypoint("/opt/ripple/bin/rippled")) .withCommand("-a --start --conf /config/rippled.cfg") diff --git a/xrpl4j-integration-tests/src/test/resources/rippled/rippled.cfg b/xrpl4j-integration-tests/src/test/resources/rippled/rippled.cfg index 8b9565494..8f909bae9 100644 --- a/xrpl4j-integration-tests/src/test/resources/rippled/rippled.cfg +++ b/xrpl4j-integration-tests/src/test/resources/rippled/rippled.cfg @@ -1,701 +1,11 @@ -#------------------------------------------------------------------------------- -# -# -#------------------------------------------------------------------------------- -# -# Contents -# -# 1. Server -# -# 2. Peer Protocol -# -# 3. Ripple Protocol -# -# 4. HTTPS Client -# -# 5. Database -# -# 6. Diagnostics -# -# 7. Voting -# -# 8. Misc Settings -# -# 9. Example Settings -# -#------------------------------------------------------------------------------- -# -# Purpose -# -# This file documents and provides examples of all rippled server process -# configuration options. When the rippled server instance is launched, it -# looks for a file with the following name: -# -# rippled.cfg -# -# For more information on where the rippled server instance searches for the -# file, visit: -# -# https://developers.ripple.com/commandline-usage.html#generic-options -# -# This file should be named rippled.cfg. This file is UTF-8 with DOS, UNIX, -# or Mac style end of lines. Blank lines and lines beginning with '#' are -# ignored. Undefined sections are reserved. No escapes are currently defined. -# -# Notation -# -# In this document a simple BNF notation is used. Angle brackets denote -# required elements, square brackets denote optional elements, and single -# quotes indicate string literals. A vertical bar separating 1 or more -# elements is a logical "or"; any one of the elements may be chosen. -# Parentheses are notational only, and used to group elements; they are not -# part of the syntax unless they appear in quotes. White space may always -# appear between elements, it has no effect on values. -# -# A required identifier -# '=' The equals sign character -# | Logical "or" -# ( ) Used for grouping -# -# -# An identifier is a string of upper or lower case letters, digits, or -# underscores subject to the requirement that the first character of an -# identifier must be a letter. Identifiers are not case sensitive (but -# values may be). -# -# Some configuration sections contain key/value pairs. A line containing -# a key/value pair has this syntax: -# -# '=' -# -# Depending on the section and key, different value types are possible: -# -# A signed integer -# An unsigned integer -# A boolean. 1 = true/yes/on, 0 = false/no/off. -# -# Consult the documentation on the key in question to determine the possible -# value types. -# -# -# -#------------------------------------------------------------------------------- -# -# 1. Server -# -#---------- -# -# -# -# rippled offers various server protocols to clients making inbound -# connections. The listening ports rippled uses are "universal" ports -# which may be configured to handshake in one or more of the available -# supported protocols. These universal ports simplify administration: -# A single open port can be used for multiple protocols. -# -# NOTE At least one server port must be defined in order -# to accept incoming network connections. -# -# -# [server] -# -# A list of port names and key/value pairs. A port name must start with a -# letter and contain only letters and numbers. The name is not case-sensitive. -# For each name in this list, rippled will look for a configuration file -# section with the same name and use it to create a listening port. The -# name is informational only; the choice of name does not affect the function -# of the listening port. -# -# Key/value pairs specified in this section are optional, and apply to all -# listening ports unless the port overrides the value in its section. They -# may be considered default values. -# -# Suggestion: -# -# To avoid a conflict with port names and future configuration sections, -# we recommend prepending "port_" to the port name. This prefix is not -# required, but suggested. -# -# This example defines two ports with different port numbers and settings: -# -# [server] -# port_public -# port_private -# port = 80 -# -# [port_public] -# ip = 0.0.0.0 -# port = 443 -# protocol = peer,https -# -# [port_private] -# ip = 0.0.0.0 -# protocol = http -# -# When rippled is used as a command line client (for example, issuing a -# server stop command), the first port advertising the http or https -# protocol will be used to make the connection. -# -# -# -# [] -# -# A series of key/value pairs that define the settings for the port with -# the corresponding name. These keys are possible: -# -# ip = -# -# Required. Determines the IP address of the network interface to bind -# to. To bind to all available IPv4 interfaces, use 0.0.0.0 -# To binding to all IPv4 and IPv6 interfaces, use :: -# -# NOTE if the ip value is ::, then any incoming IPv4 connections will -# be made as mapped IPv4 addresses. -# -# port = -# -# Required. Sets the port number to use for this port. -# -# protocol = [ http, https, peer ] -# -# Required. A comma-separated list of protocols to support: -# -# http JSON-RPC over HTTP -# https JSON-RPC over HTTPS -# ws Websockets -# wss Secure Websockets -# peer Peer Protocol -# -# Restrictions: -# -# Only one port may be configured to support the peer protocol. -# A port cannot have websocket and non websocket protocols at the -# same time. It is possible have both Websockets and Secure Websockets -# together in one port. -# -# NOTE If no ports support the peer protocol, rippled cannot -# receive incoming peer connections or become a superpeer. -# -# limit = -# -# Optional. An integer value that will limit the number of connected -# clients that the port will accept. Once the limit is reached, new -# connections will be refused until other clients disconnect. -# Omit or set to 0 to allow unlimited numbers of clients. -# -# user = -# password = -# -# When set, these credentials will be required on HTTP/S requests. -# The credentials must be provided using HTTP's Basic Authentication -# headers. If either or both fields are empty, then no credentials are -# required. IP address restrictions, if any, will be checked in addition -# to the credentials specified here. -# -# When acting in the client role, rippled will supply these credentials -# using HTTP's Basic Authentication headers when making outbound HTTP/S -# requests. -# -# admin = [ IP, IP, IP, ... ] -# -# A comma-separated list of IP addresses. -# -# When set, grants administrative command access to the specified IP -# addresses. These commands may be issued over http, https, ws, or wss -# if configured on the port. If not provided, the default is to not allow -# administrative commands. -# -# NOTE A common configuration value for the admin field is "localhost". -# If you are listening on all IPv4/IPv6 addresses by specifing -# ip = :: then you can use admin = ::ffff:0.0.0.0,::1 to allow -# administrative access from both IPv4 and IPv6 localhost -# connections. -# -# *SECURITY WARNING* -# 0.0.0.0 or :: may be used to allow access from any IP address. It must -# be the only address specified and cannot be combined with other IPs. -# Use of this address can compromise server security, please consider its -# use carefully. -# -# admin_user = -# admin_password = -# -# When set, clients must provide these credentials in the submitted -# JSON for any administrative command requests submitted to the HTTP/S, -# WS, or WSS protocol interfaces. If administrative commands are -# disabled for a port, these credentials have no effect. -# -# When acting in the client role, rippled will supply these credentials -# in the submitted JSON for any administrative command requests when -# invoking JSON-RPC commands on remote servers. -# -# secure_gateway = [ IP, IP, IP, ... ] -# -# A comma-separated list of IP addresses. -# -# When set, allows the specified IP addresses to pass HTTP headers -# containing username and remote IP address for each session. If a -# non-empty username is passed in this way, then resource controls -# such as often resulting in "tooBusy" errors will be lifted. However, -# administrative RPC commands such as "stop" will not be allowed. -# The HTTP headers that secure_gateway hosts can set are X-User and -# X-Forwarded-For. Only the X-User header affects resource controls. -# However, both header values are logged to help identify user activity. -# If no X-User header is passed, or if its value is empty, then -# resource controls will default to those for non-administrative users. -# -# The secure_gateway IP addresses are intended to represent -# proxies. Since rippled trusts these hosts, they must be -# responsible for properly authenticating the remote user. -# -# The same IP address cannot be used in both "admin" and "secure_gateway" -# lists for the same port. In this case, rippled will abort with an error -# message to the console shortly after startup -# -# ssl_key = -# ssl_cert = -# ssl_chain = -# -# Use the specified files when configuring SSL on the port. -# -# NOTE If no files are specified and secure protocols are selected, -# rippled will generate an internal self-signed certificate. -# -# The files have these meanings: -# -# ssl_key -# -# Specifies the filename holding the SSL key in PEM format. -# -# ssl_cert -# -# Specifies the path to the SSL certificate file in PEM format. -# This is not needed if the chain includes it. -# -# ssl_chain -# -# If you need a certificate chain, specify the path to the -# certificate chain here. The chain may include the end certificate. -# -# ssl_ciphers = -# -# Control the ciphers which the server will support over SSL on the port, -# specified using the OpenSSL "cipher list format". -# -# NOTE If unspecified, rippled will automatically configure a modern -# cipher suite. This default suite should be widely supported. -# -# You should not modify this string unless you have a specific -# reason and cryptographic expertise. Incorrect modification may -# keep rippled from connecting to other instances of rippled or -# prevent RPC and WebSocket clients from connecting. -# -# send_queue_limit = [1..65535] -# -# A Websocket will disconnect when its send queue exceeds this limit. -# The default is 100. A larger value may help with erratic disconnects but -# may adversely affect server performance. -# -# WebSocket permessage-deflate extension options -# -# These settings configure the optional permessage-deflate extension -# options and may appear on any port configuration entry. They are meaningful -# only to ports which have enabled a WebSocket protocol. -# -# permessage_deflate = -# -# Determines if permessage_deflate extension negotiations are enabled. -# When enabled, clients may request the extension and the server will -# offer the enabled extension in response. -# -# client_max_window_bits = [9..15] -# server_max_window_bits = [9..15] -# client_no_context_takeover = -# server_no_context_takeover = -# -# These optional settings control options related to the permessage-deflate -# extension negotiation. For precise definitions of these fields please see -# the RFC 7692, "Compression Extensions for WebSocket": -# https://tools.ietf.org/html/rfc7692 -# -# compress_level = [0..9] -# -# When set, determines the amount of compression attempted, where 0 is -# the least amount and 9 is the most amount. Higher levels require more -# CPU resources. Levels 1 through 3 use a fast compression algorithm, -# while levels 4 through 9 use a more compact algorithm which uses more -# CPU resources. If unspecified, a default of 3 is used. -# -# memory_level = [1..9] -# -# When set, determines the relative amount of memory used to hold -# intermediate compression data. Higher numbers can give better compression -# ratios at the cost of higher memory and CPU resources. -# -# [rpc_startup] -# -# Specify a list of RPC commands to run at startup. -# -# Examples: -# { "command" : "server_info" } -# { "command" : "log_level", "partition" : "ripplecalc", "severity" : "trace" } -# -# -# -# [websocket_ping_frequency] -# -# -# -# The amount of time to wait in seconds, before sending a websocket 'ping' -# message. Ping messages are used to determine if the remote end of the -# connection is no longer available. -# -# -# -#------------------------------------------------------------------------------- -# -# 2. Peer Protocol -# -#----------------- -# -# These settings control security and access attributes of the Peer to Peer -# server section of the rippled process. Peer Protocol implements the -# Ripple Payment protocol. It is over peer connections that transactions -# and validations are passed from to machine to machine, to determine the -# contents of validated ledgers. -# -# -# -# [ips] -# -# List of hostnames or ips where the Ripple protocol is served. A default -# starter list is included in the code and used if no other hostnames are -# available. -# -# One address or domain name per line is allowed. A port may must be -# specified after adding a space to the address. The ordering of entries -# does not generally matter. -# -# The default list of entries is: -# - r.ripple.com 51235 -# - zaphod.alloy.ee 51235 -# - sahyadri.isrdc.in 51235 -# -# Examples: -# -# [ips] -# 192.168.0.1 -# 192.168.0.1 2459 -# r.ripple.com 51235 -# -# -# [ips_fixed] -# -# List of IP addresses or hostnames to which rippled should always attempt to -# maintain peer connections with. This is useful for manually forming private -# networks, for example to configure a validation server that connects to the -# Ripple network through a public-facing server, or for building a set -# of cluster peers. -# -# One address or domain names per line is allowed. A port must be specified -# after adding a space to the address. -# -# -# -# [peer_private] -# -# 0 or 1. -# -# 0: Request peers to broadcast your address. Normal outbound peer connections [default] -# 1: Request peers not broadcast your address. Only connect to configured peers. -# -# -# -# [peers_max] -# -# The largest number of desired peer connections (incoming or outgoing). -# Cluster and fixed peers do not count towards this total. There are -# implementation-defined lower limits imposed on this value for security -# purposes. -# -# -# -# [node_seed] -# -# This is used for clustering. To force a particular node seed or key, the -# key can be set here. The format is the same as the validation_seed field. -# To obtain a validation seed, use the validation_create command. -# -# Examples: RASH BUSH MILK LOOK BAD BRIM AVID GAFF BAIT ROT POD LOVE -# shfArahZT9Q9ckTf3s1psJ7C7qzVN -# -# -# -# [cluster_nodes] -# -# To extend full trust to other nodes, place their node public keys here. -# Generally, you should only do this for nodes under common administration. -# Node public keys start with an 'n'. To give a node a name for identification -# place a space after the public key and then the name. -# -# -# -# [sntp_servers] -# -# IP address or domain of NTP servers to use for time synchronization. -# -# These NTP servers are suitable for rippled servers located in the United -# States: -# time.windows.com -# time.apple.com -# time.nist.gov -# pool.ntp.org -# -# -# -# [overlay] -# -# Controls settings related to the peer to peer overlay. -# -# A set of key/value pair parameters to configure the overlay. -# -# public_ip = -# -# If the server has a known, fixed public IPv4 address, -# specify that IP address here in dotted decimal notation. -# Peers will use this information to reject attempt to proxy -# connections to or from this server. -# -# ip_limit = -# -# The maximum number of incoming peer connections allowed by a single -# IP that isn't classified as "private" in RFC1918. The implementation -# imposes some hard and soft upper limits on this value to prevent a -# single host from consuming all inbound slots. If the value is not -# present the server will autoconfigure an appropriate limit. -# -# -# -# [transaction_queue] EXPERIMENTAL -# -# This section is EXPERIMENTAL, and should not be -# present for production configuration settings. -# -# A set of key/value pair parameters to tune the performance of the -# transaction queue. -# -# ledgers_in_queue = -# -# The queue will be limited to this of average ledgers' -# worth of transactions. If the queue fills up, the transactions -# with the lowest fee levels will be dropped from the queue any -# time a transaction with a higher fee level is added. -# Default: 20. -# -# minimum_queue_size = -# -# The queue will always be able to hold at least this of -# transactions, regardless of recent ledger sizes or the value of -# ledgers_in_queue. Default: 2000. -# -# retry_sequence_percent = -# -# If a client replaces a transaction in the queue (same sequence -# number as a transaction already in the queue), the new -# transaction's fee must be more than percent higher -# than the original transaction's fee, or meet the current open -# ledger fee to be considered. Default: 25. -# -# multi_txn_percent = -# -# If a client submits multiple transactions (different sequence -# numbers), later transactions must pay a fee at least -# percent higher than the transaction with the previous sequence -# number. -# Default: -90. -# -# minimum_escalation_multiplier = -# -# At ledger close time, the median fee level of the transactions -# in that ledger is used as a multiplier in escalation -# calculations of the next ledger. This minimum value ensures that -# the escalation is significant. Default: 500. -# -# minimum_txn_in_ledger = -# -# Minimum number of transactions that must be allowed into the -# ledger at the minimum required fee before the required fee -# escalates. Default: 5. -# -# minimum_txn_in_ledger_standalone = -# -# Like minimum_txn_in_ledger when rippled is running in standalone -# mode. Default: 1000. -# -# target_txn_in_ledger = -# -# Number of transactions allowed into the ledger at the minimum -# required fee that the queue will "work toward" as long as -# consensus stays healthy. The limit will grow quickly until it -# reaches or exceeds this number. After that the limit may still -# change, but will stay above the target. If consensus is not -# healthy, the limit will be clamped to this value or lower. -# Default: 50. -# -# maximum_txn_in_ledger = -# -# (Optional) Maximum number of transactions that will be allowed -# into the ledger at the minimum required fee before the required -# fee escalates. Default: no maximum. -# -# normal_consensus_increase_percent = -# -# (Optional) When the ledger has more transactions than "expected", -# and performance is humming along nicely, the expected ledger size -# is updated to the previous ledger size plus this percentage. -# Default: 20 -# -# slow_consensus_decrease_percent = -# -# (Optional) When consensus takes longer than appropriate, the -# expected ledger size is updated to the minimum of the previous -# ledger size or the "expected" ledger size minus this percentage. -# Default: 50 -# -# maximum_txn_per_account = -# -# Maximum number of transactions that one account can have in the -# queue at any given time. Default: 10. -# -# minimum_last_ledger_buffer = -# -# If a transaction has a LastLedgerSequence, it must be at least -# this much larger than the current open ledger sequence number. -# Default: 2. -# -# zero_basefee_transaction_feelevel = -# -# So we don't deal with infinite fee levels, treat any transaction -# with a 0 base fee (ie. SetRegularKey password recovery) as -# having this fee level. -# Default: 256000. -# -# -#------------------------------------------------------------------------------- -# -# 3. Ripple Protocol -# -#------------------- -# -# These settings affect the behavior of the server instance with respect -# to Ripple payment protocol level activities such as validating and -# closing ledgers or adjusting fees in response to server overloads. -# -# -# -# [node_size] -# -# Tunes the servers based on the expected load and available memory. Legal -# sizes are "tiny", "small", "medium", "large", and "huge". We recommend -# you start at the default and raise the setting if you have extra memory. -# The default is "tiny". -# -# -# -# [ledger_history] -# -# The number of past ledgers to acquire on server startup and the minimum to -# maintain while running. -# -# To serve clients, servers need historical ledger data. Servers that don't -# need to serve clients can set this to "none". Servers that want complete -# history can set this to "full". -# -# This must be less than or equal to online_delete (if online_delete is used) -# -# The default is: 256 -# -# -# -# [fetch_depth] -# -# The number of past ledgers to serve to other peers that request historical -# ledger data (or "full" for no limit). -# -# Servers that require low latency and high local performance may wish to -# restrict the historical ledgers they are willing to serve. Setting this -# below 32 can harm network stability as servers require easy access to -# recent history to stay in sync. Values below 128 are not recommended. -# -# The default is: full -# -# -# -# [validation_seed] -# -# To perform validation, this section should contain either a validation seed -# or key. The validation seed is used to generate the validation -# public/private key pair. To obtain a validation seed, use the -# validation_create command. -# -# Examples: RASH BUSH MILK LOOK BAD BRIM AVID GAFF BAIT ROT POD LOVE -# shfArahZT9Q9ckTf3s1psJ7C7qzVN -# -# -# -# [validator_token] -# -# This is an alternative to [validation_seed] that allows rippled to perform -# validation without having to store the validator keys on the network -# connected server. The field should contain a single token in the form of a -# base64-encoded blob. -# An external tool is available for generating validator keys and tokens. -# -# -# -# [validator_key_revocation] -# -# If a validator's secret key has been compromised, a revocation must be -# generated and added to this field. The revocation notifies peers that it is -# no longer safe to trust the revoked key. The field should contain a single -# revocation in the form of a base64-encoded blob. -# An external tool is available for generating and revoking validator keys. -# -# -# -# [validators_file] -# -# Path or name of a file that determines the nodes to always accept as validators. -# -# The contents of the file should include a [validators] and/or -# [validator_list_sites] and [validator_list_keys] entries. -# [validators] should be followed by a list of validation public keys of -# nodes, one per line. -# [validator_list_sites] should be followed by a list of URIs each serving a -# list of recommended validators. -# [validator_list_keys] should be followed by a list of keys belonging to -# trusted validator list publishers. Validator lists fetched from configured -# sites will only be considered if the list is accompanied by a valid -# signature from a trusted publisher key. -# -# Specify the file by its name or path. -# Unless an absolute path is specified, it will be considered relative to -# the folder in which the rippled.cfg file is located. -# -# Examples: -# /home/ripple/validators.txt -# C:/home/ripple/validators.txt -# -# Example content: -# [validators] -# n949f75evCHwgyP4fPVgaHqNHxUVN15PsJEZ3B3HnXPcPjcZAoy7 -# n9MD5h24qrQqiyBC8aeqqCWvpiBiYQ3jxSr91uiDvmrkyHRdYLUj -# n9L81uNCaPgtUJfaHh89gmdvXKAmSt5Gdsw2g1iPWaPkAHW5Nm4C -# n9KiYM9CgngLvtRCQHZwgC2gjpdaZcCcbt3VboxiNFcKuwFVujzS -# n9LdgEtkmGB9E2h3K4Vp7iGUaKuq23Zr32ehxiU8FWY7xoxbWTSA -# -# -# +[server] +port_rpc_admin_local +port_peer +port_ws_admin_local +port_ws_public +#ssl_key = /etc/ssl/private/server.key +#ssl_cert = /etc/ssl/certs/server.crt + [path_search] 10 # When searching for paths, the default search aggressiveness. This can take @@ -716,453 +26,6 @@ # [path_search_old] 10 -# -# For clients that use the legacy path finding interfaces, the search -# aggressiveness to use. The default is 7. -# -# -# -# [fee_default] -# -# Sets the base cost of a transaction in drops. Used when the server has -# no other source of fee information, such as signing transactions offline. -# -# -# -# [workers] -# -# Configures the number of threads for processing work submitted by peers -# and clients. If not specified, then the value is automatically determined -# by factors including the number of system processors and whether this -# node is a validator. -# -# -# -[network_id] -255 -# -# Specify the network which this server is configured to connect to and -# track. If set, the server will not establish connections with servers -# that are explicitly configured to track another network. -# -# Network identifiers are usually unsigned integers in the range 0 to -# 4294967295 inclusive. The server also maps the following well-known -# names to the corresponding numerical identifier: -# -# main -> 0 -# testnet -> 1 -# -# If this value is not specified the server is not explicitly configured -# to track a particular network. -# -# -#------------------------------------------------------------------------------- -# -# 4. HTTPS Client -# -#---------------- -# -# The rippled server instance uses HTTPS GET requests in a variety of -# circumstances, including but not limited to contacting trusted domains to -# fetch information such as mapping an email address to a Ripple Payment -# Network address. -# -# [ssl_verify] -# -# 0 or 1. -# -# 0. HTTPS client connections will not verify certificates. -# 1. Certificates will be checked for HTTPS client connections. -# -# If not specified, this parameter defaults to 1. -# -# -# -# [ssl_verify_file] -# -# -# -# A file system path leading to the certificate verification file for -# HTTPS client requests. -# -# -# -# [ssl_verify_dir] -# -# -# -# -# A file system path leading to a file or directory containing the root -# certificates that the server will accept for verifying HTTP servers. -# Used only for outbound HTTPS client connections. -# -# -# -#------------------------------------------------------------------------------- -# -# 5. Database -# -#------------ -# -# rippled creates 4 SQLite database to hold bookkeeping information -# about transactions, local credentials, and various other things. -# It also creates the NodeDB, which holds all the objects that -# make up the current and historical ledgers. -# -# The size of the NodeDB grows in proportion to the amount of new data and the -# amount of historical data (a configurable setting) so the performance of the -# underlying storage media where the NodeDB is placed can significantly affect -# the performance of the server. -# -# Partial pathnames will be considered relative to the location of -# the rippled.cfg file. -# -# [node_db] Settings for the Node Database (required) -# -# Format (without spaces): -# One or more lines of case-insensitive key / value pairs: -# '=' -# ... -# -# Example: -# type=nudb -# path=db/nudb -# -# The "type" field must be present and controls the choice of backend: -# -# type = NuDB -# -# NuDB is a high-performance database written by Ripple Labs and optimized -# for rippled and solid-state drives. -# -# NuDB maintains its high speed regardless of the amount of history -# stored. Online delete may be selected, but is not required. NuDB is -# available on all platforms that rippled runs on. -# -# type = RocksDB -# -# RocksDB is an open-source, general-purpose key/value store - see -# http://rocksdb.org/ for more details. -# -# RocksDB is an alternative backend for systems that don't use solid-state -# drives. Because RocksDB's performance degrades as it stores more data, -# keeping full history is not advised, and using online delete is -# recommended. -# -# Required keys: -# path Location to store the database (all types) -# -# Optional keys: -# -# These keys are possible for any type of backend: -# -# online_delete Minimum value of 256. Enable automatic purging -# of older ledger information. Maintain at least this -# number of ledger records online. Must be greater -# than or equal to ledger_history. -# -# advisory_delete 0 for disabled, 1 for enabled. If set, then -# require administrative RPC call "can_delete" -# to enable online deletion of ledger records. -# -# earliest_seq The default is 32570 to match the XRP ledger -# network's earliest allowed sequence. Alternate -# networks may set this value. Minimum value of 1. -# -# Notes: -# The 'node_db' entry configures the primary, persistent storage. -# -# The 'import_db' is used with the '--import' command line option to -# migrate the specified database into the current database given -# in the [node_db] section. -# -# [import_db] Settings for performing a one-time import (optional) -# [database_path] Path to the book-keeping databases. -# -# [shard_db] Settings for the Shard Database (optional) -# -# Format (without spaces): -# One or more lines of case-insensitive key / value pairs: -# '=' -# ... -# -# Example: -# path=db/shards/nudb -# -# Required keys: -# path Location to store the database -# -# max_size_gb Maximum disk space the database will utilize (in gigabytes) -# -# -# There are 4 bookkeeping SQLite database that the server creates and -# maintains. If you omit this configuration setting, it will default to -# creating a directory called "db" located in the same place as your -# rippled.cfg file. Partial pathnames will be considered relative to -# the location of the rippled executable. -# -# -# -# -#------------------------------------------------------------------------------- -# -# 6. Diagnostics -# -#--------------- -# -# These settings are designed to help server administrators diagnose -# problems, and obtain detailed information about the activities being -# performed by the rippled process. -# -# -# -# [debug_logfile] -# -# Specifies where a debug logfile is kept. By default, no debug log is kept. -# Unless absolute, the path is relative the directory containing this file. -# -# Example: debug.log -# -# -# -# [insight] -# -# Configuration parameters for the Beast. Insight stats collection module. -# -# Insight is a module that collects information from the areas of rippled -# that have instrumentation. The configuration parameters control where the -# collection metrics are sent. The parameters are expressed as key = value -# pairs with no white space. The main parameter is the choice of server: -# -# "server" -# -# Choice of server to send metrics to. Currently the only choice is -# "statsd" which sends UDP packets to a StatsD daemon, which must be -# running while rippled is running. More information on StatsD is -# available here: -# https://github.com/b/statsd_spec -# -# When server=statsd, these additional keys are used: -# -# "address" The UDP address and port of the listening StatsD server, -# in the format, n.n.n.n:port. -# -# "prefix" A string prepended to each collected metric. This is used -# to distinguish between different running instances of rippled. -# -# If this section is missing, or the server type is unspecified or unknown, -# statistics are not collected or reported. -# -# Example: -# -# [insight] -# server=statsd -# address=192.168.0.95:4201 -# prefix=my_validator -# -# [perf] -# -# Configuration of performance logging. If enabled, write Json-formatted -# performance-oriented data periodically to a distinct log file. -# -# "perf_log" A string specifying the pathname of the performance log -# file. A relative pathname will log relative to the -# configuration directory. Required to enable -# performance logging. -# -# "log_interval" Integer value for number of seconds between writing -# to performance log. Default 1. -# -# Example: -# [perf] -# perf_log=/var/log/rippled/perf.log -# log_interval=2 -# -#------------------------------------------------------------------------------- -# -# 7. Voting -# -#---------- -# -# The vote settings configure settings for the entire Ripple network. -# While a single instance of rippled cannot unilaterally enforce network-wide -# settings, these choices become part of the instance's vote during the -# consensus process for each voting ledger. -# -# [voting] -# -# A set of key/value pair parameters used during voting ledgers. -# -# reference_fee = -# -# The cost of the reference transaction fee, specified in drops. -# The reference transaction is the simplest form of transaction. -# It represents an XRP payment between two parties. -# -# If this parameter is unspecified, rippled will use an internal -# default. Don't change this without understanding the consequences. -# -# Example: -# reference_fee = 10 # 10 drops -# -# account_reserve = -# -# The account reserve requirement is specified in drops. The portion of an -# account's XRP balance that is at or below the reserve may only be -# spent on transaction fees, and not transferred out of the account. -# -# If this parameter is unspecified, rippled will use an internal -# default. Don't change this without understanding the consequences. -# -# Example: -# account_reserve = 20000000 # 20 XRP -# -# owner_reserve = -# -# The owner reserve is the amount of XRP reserved in the account for -# each ledger item owned by the account. Ledger items an account may -# own include trust lines, open orders, and tickets. -# -# If this parameter is unspecified, rippled will use an internal -# default. Don't change this without understanding the consequences. -# -# Example: -# owner_reserve = 5000000 # 5 XRP -# -#------------------------------------------------------------------------------- -# -# 8. Misc Settings -# -#----------------- -# -# [signing_support] -# -# Specifies whether the server will accept "sign" and "sign_for" commands -# from remote users. Even if the commands are sent over a secure protocol -# like secure websocket, this should generally be discouraged, because it -# requires sending the secret to use for signing to the server. In order -# to sign transactions, users should prefer to use a standalone signing -# tool instead. -# -# This flag has no effect on the "sign" and "sign_for" command line options -# that rippled makes available. -# -# The default value of this field is "false" -# -# Example: -# -# [signing_support] -# true -# -# [crawl] -# -# List of options to control what data is reported through the /crawl endpoint -# See https://xrpl.org/peer-crawler.html -# -# -# -# Enable or disable access to /crawl requests. Default is '1' which -# enables access. -# -# overlay = -# -# Report information about peers this server is connected to, similar -# to the "peers" RPC API. Default is '1' which means to report peer -# overlay info. -# -# server = -# -# Report information about the local server, similar to the "server_state" -# RPC API. Default is '1' which means to report local server info. -# -# counts = -# -# Report information about the local server health counters, similar to -# the "get_counts" RPC API. Default is '0' which means not to report -# server counts. -# -# unl = -# -# Report information about the local server's validator lists, similar to -# the "validators" and "validator_list_sites" RPC APIs. Default is '1' -# which means to report server validator lists. -# -# Examples: -# -# [crawl] -# 0 -# -# [crawl] -# overlay = 1 -# server = 1 -# counts = 0 -# unl = 1 -# -# [vl] -# -# Options to control what data is reported through the /vl endpoint -# See [...] -# -# enable = -# -# Enable or disable access to /vl requests. Default is '1' which -# enables access. -# -#------------------------------------------------------------------------------- -# -# 9. Example Settings -# -#-------------------- -# -# Administrators can use these values as a starting point for configuring -# their instance of rippled, but each value should be checked to make sure -# it meets the business requirements for the organization. -# -# Server -# -# These example configuration settings create these ports: -# -# "peer" -# -# Peer protocol open to everyone. This is required to accept -# incoming rippled connections. This does not affect automatic -# or manual outgoing Peer protocol connections. -# -# "rpc" -# -# Administrative RPC commands over HTTPS, when originating from -# the same machine (via the loopback adapter at 0.0.0.0). -# -# "wss_admin" -# -# Admin level API commands over Secure Websockets, when originating -# from the same machine (via the loopback adapter at 0.0.0.0). -# -# This port is commented out but can be enabled by removing -# the '#' from each corresponding line including the entry under [server] -# -# "wss_public" -# -# Guest level API commands over Secure Websockets, open to everyone. -# -# For HTTPS and Secure Websockets ports, if no certificate and key file -# are specified then a self-signed certificate will be generated on startup. -# If you have a certificate and key file, uncomment the corresponding lines -# and ensure the paths to the files are correct. -# -# NOTE -# -# To accept connections on well known ports such as 80 (HTTP) or -# 443 (HTTPS), most operating systems will require rippled to -# run with administrator privileges, or else rippled will not start. - -[server] -port_rpc_admin_local -port_peer -port_ws_admin_local -port_ws_public -#ssl_key = /etc/ssl/private/server.key -#ssl_cert = /etc/ssl/certs/server.crt [port_rpc_admin_local] port = 5005 @@ -1192,40 +55,34 @@ port = 6006 ip = 0.0.0.0 protocol = ws -#------------------------------------------------------------------------------- - [node_size] -tiny +small + +# tiny +# small +# medium +# large +# huge -# This is primary persistent datastore for rippled. This includes transaction -# metadata, account states, and ledger headers. Helpful information can be -# found here: https://ripple.com/wiki/NodeBackEnd -# delete old ledgers while maintaining at least 2000. Do not require an -# external administrative command to initiate deletion. [node_db] -type=RocksDB -path=/var/lib/rippled/db/rocksdb -open_files=2000 -filter_bits=12 -cache_mb=256 -file_size_mb=8 -file_size_mult=2 -online_delete=2000 +type=NuDB +path=/var/lib/rippled/db/nudb advisory_delete=0 -# This is the persistent datastore for shards. It is important for the health -# of the ripple network that rippled operators shard as much as practical. -# NuDB requires SSD storage. Helpful information can be found here -# https://ripple.com/build/history-sharding -#[shard_db] -#path=/var/lib/rippled/db/shards/nudb -#max_size_gb=500 +# How many ledgers do we want to keep (history)? +# Integer value that defines the number of ledgers +# between online deletion events +online_delete=2000 + +#[ledger_history] +# How many ledgers do we want to keep (history)? +# Integer value (ledger count) +# or (if you have lots of TB SSD storage): 'full' +#256 [database_path] /var/lib/rippled/db -# This needs to be an absolute directory reference, not a relative one. -# Modify this value as required. [debug_logfile] /var/log/rippled/debug.log @@ -1235,12 +92,8 @@ time.apple.com time.nist.gov pool.ntp.org -# To use the XRP test network (see https://ripple.com/build/xrp-test-net/), -# use the following [ips] section: -# [ips] -# r.altnet.rippletest.net 51235 - -# validator public key: nHBP8eqEWPnExo6ubTAV5QhUA3ZXVQYyT2qtKqxczFyoaKVhFh8M +#[ips] +#r.ripple.com 51235 [validator_token] eyJtYW5pZmVzdCI6IkpBQUFBQUZ4SWUwRlZqOEdmME84OG8xYlFmRk42dWJkV05LQlh3S1VH @@ -1258,33 +111,96 @@ RkI0OTE5RDU0Njg0QjYifQ== [validators_file] validators.txt - - -# Turn down default logging to save disk space in the long run. -# Valid values here are trace, debug, info, warning, error, and fatal [rpc_startup] -{ "command": "log_level", "severity": "trace" } +{ "command": "log_level", "severity": "info" } + +# severity (order: lots of information .. only errors) +# debug +# info +# warn +# error +# fatal -# If ssl_verify is 1, certificates will be validated. -# To allow the use of self-signed certificates for development or internal use, -# set to ssl_verify to 0. [ssl_verify] 0 -[voting] -account_reserve = 20000000 # 20 XRP -owner_reserve = 5000000 # 5 XRP -reference_fee = 10 # 10 drops + +# In order to enable an amendment which by default would vote "No", you must include its amendment id and name here. +# To add amendments specifically from the latest releases of rippled: +# 1. Go to https://xrpl.org/known-amendments.html +# 2. Find the first amendment in the latest releases of rippled which are not already in the list below +# 3. Click on each amendment to get their Amendment ID and name to add to this list manually. +# You will likely update the list with all amendments from a new release of rippled all at once. + +# To get the list of amendments on a network (e.g. devnet) follow the steps in xrpl.js's CONTRIBUTING.md for "Updating the Docker container". +# https://github.com/XRPLF/xrpl.js/blob/main/CONTRIBUTING.md +# (Running the script `getNewAmendments.js` should help you identify any new amendments that should be added.) +# +# Note: The version of rippled you use this config with must have an implementation for the amendments you attempt to enable or it will crash. +# If you need the version of rippled to be more up to date, you may need to make a comment on this repo: https://github.com/WietseWind/docker-rippled [features] +# Devnet amendments as of June 28th, 2023 +NegativeUNL +fixRemoveNFTokenAutoTrustLine +NonFungibleTokensV1 +CheckCashMakesTrustLine +fixRmSmallIncreasedQOffers +fixSTAmountCanonicalize +FlowSortStrands +TicketBatch +fix1201 +fixQualityUpperBound +FlowCross +EnforceInvariants +fix1523 +HardenedValidations +DepositPreauth +MultiSignReserve +fix1623 +FeeEscalation +PayChan +fix1513 +RequireFullyCanonicalSig +fix1543 +TickSize +fix1781 +fixCheckThreading +fix1515 +CryptoConditions +fix1528 +fixPayChanRecipientOwnerDir +SortedDirectories +fix1578 +fix1571 +fixAmendmentMajorityCalc +fixTakerDryOfferRemoval +fixMasterKeyAsRegularKey +Flow +Escrow +TrustSetAuth +DeletableAccounts +DepositAuth +fix1368 +fix1512 +fix1373 +MultiSign +Checks NonFungibleTokensV1_1 +# 1.10.0 Amendments DisallowIncoming fixNonFungibleTokensV1_2 fixTrustLinesToSelf fixUniversalNumber ImmediateOfferKilled XRPFees -fixNFTokenRemint -fixReducedOffersV1 +# 1.11.0 Amendments +ExpandedSignerList +# 1.12.0 Amendments +AMM Clawback -AMM \ No newline at end of file +fixReducedOffersV1 +fixNFTokenRemint +# 2.0.0 Amendments +XChainBridge +DID \ No newline at end of file From 774d60ff3c9aef1851407bb1117d9e2cbad2471e Mon Sep 17 00:00:00 2001 From: nkramer44 Date: Mon, 4 Dec 2023 11:07:34 -0500 Subject: [PATCH 2/4] DID Support (#498) * Add XChainAccountCreateCommit transaction, as well as new XChainBridge serialized type * Add XChainAddAccountCreateAttestation * Add XChainAddClaimAttestation * Fix bug in binary codec's UInt64Type where it expected JSON values to be base 10, not base 16. Also serialize XChainClaimIds as hex in JSON, and add a new XChainCount type that also serializes to hex in JSON * add XChainClaim * add XChainClaim * add XChainCommit * Add XChainCreateBridge * Add XChainCreateClaimId * add XChainModifyBridge * Add Bridge object * Add XChainOwnedCreateAccountClaimIdObject * rename XChainCreateAccountProofSigWrapper to XChainCreateAccountAttestation * add XChainOwnedClaimIdObject * fix checkstyle and javadoc * migrate to rippleci/rippled from xrpllabsofficial/xrpld. Add new transactions to SignatureUtils switches * write ITs for xchain stuff. Add attestations that can be signed. Add ability to sign attestations to signature services. * fix checkstyle * fix tests * add DidSet transaction * add DidDelete transaction * add DidObject and MetaDidObject * add ledger_entry support for DID * add bridge ledger entry lookup * add DID ITs * PR feedback * uncomment test * fix checkstyle --- .../xrpl4j/crypto/signing/SignatureUtils.java | 18 ++ .../ledger/LedgerEntryRequestParams.java | 30 ++ .../jackson/modules/DidDataDeserializer.java | 46 +++ .../jackson/modules/DidDataSerializer.java | 46 +++ .../modules/DidDocumentDeserializer.java | 47 +++ .../modules/DidDocumentSerializer.java | 46 +++ .../jackson/modules/DidUriDeserializer.java | 46 +++ .../jackson/modules/DidUriSerializer.java | 46 +++ .../xrpl/xrpl4j/model/ledger/DidObject.java | 135 +++++++++ .../xrpl4j/model/ledger/LedgerObject.java | 12 +- .../xrpl4j/model/transactions/DidDelete.java | 50 ++++ .../xrpl4j/model/transactions/DidSet.java | 75 +++++ .../model/transactions/Transaction.java | 2 + .../model/transactions/TransactionType.java | 20 +- .../xrpl4j/model/transactions/Wrappers.java | 66 +++++ .../transactions/metadata/MetaDidObject.java | 108 +++++++ .../metadata/MetaLedgerEntryType.java | 5 + .../src/main/resources/definitions.json | 47 ++- .../binary/BinarySerializationTests.java | 84 ++++++ .../crypto/signing/SignatureUtilsTest.java | 51 ++++ .../ledger/LedgerEntryRequestParamsTest.java | 46 +++ .../xrpl4j/model/ledger/DidObjectTest.java | 67 +++++ .../model/transactions/DidDataTest.java | 72 +++++ .../model/transactions/DidDeleteTest.java | 72 +++++ .../model/transactions/DidDocumentTest.java | 73 +++++ .../xrpl4j/model/transactions/DidSetTest.java | 116 ++++++++ .../xrpl4j/model/transactions/DidUriTest.java | 72 +++++ .../metadata/MetaLedgerEntryTypeTest.java | 2 + .../src/test/resources/codec-fixtures.json | 27 ++ .../java/org/xrpl/xrpl4j/tests/DidIT.java | 270 ++++++++++++++++++ 30 files changed, 1784 insertions(+), 13 deletions(-) create mode 100644 xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/jackson/modules/DidDataDeserializer.java create mode 100644 xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/jackson/modules/DidDataSerializer.java create mode 100644 xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/jackson/modules/DidDocumentDeserializer.java create mode 100644 xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/jackson/modules/DidDocumentSerializer.java create mode 100644 xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/jackson/modules/DidUriDeserializer.java create mode 100644 xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/jackson/modules/DidUriSerializer.java create mode 100644 xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/ledger/DidObject.java create mode 100644 xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/DidDelete.java create mode 100644 xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/DidSet.java create mode 100644 xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/metadata/MetaDidObject.java create mode 100644 xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/ledger/DidObjectTest.java create mode 100644 xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/DidDataTest.java create mode 100644 xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/DidDeleteTest.java create mode 100644 xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/DidDocumentTest.java create mode 100644 xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/DidSetTest.java create mode 100644 xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/DidUriTest.java create mode 100644 xrpl4j-integration-tests/src/test/java/org/xrpl/xrpl4j/tests/DidIT.java 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 9d4fb28c2..c8edb3b0c 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 @@ -44,6 +44,8 @@ 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.DidDelete; +import org.xrpl.xrpl4j.model.transactions.DidSet; import org.xrpl.xrpl4j.model.transactions.EscrowCancel; import org.xrpl.xrpl4j.model.transactions.EscrowCreate; import org.xrpl.xrpl4j.model.transactions.EscrowFinish; @@ -371,6 +373,14 @@ public SingleSignedTransaction addSignatureToTransact transactionWithSignature = XChainModifyBridge.builder().from((XChainModifyBridge) transaction) .transactionSignature(signature) .build(); + } else if (DidSet.class.isAssignableFrom(transaction.getClass())) { + transactionWithSignature = DidSet.builder().from((DidSet) transaction) + .transactionSignature(signature) + .build(); + } else if (DidDelete.class.isAssignableFrom(transaction.getClass())) { + transactionWithSignature = DidDelete.builder().from((DidDelete) 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."); @@ -566,6 +576,14 @@ public T addMultiSignaturesToTransaction(T transaction, transactionWithSignatures = XChainModifyBridge.builder().from((XChainModifyBridge) transaction) .signers(signers) .build(); + } else if (DidSet.class.isAssignableFrom(transaction.getClass())) { + transactionWithSignatures = DidSet.builder().from((DidSet) transaction) + .signers(signers) + .build(); + } else if (DidDelete.class.isAssignableFrom(transaction.getClass())) { + transactionWithSignatures = DidDelete.builder().from((DidDelete) 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/model/client/ledger/LedgerEntryRequestParams.java b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/client/ledger/LedgerEntryRequestParams.java index 11e675428..b850bcbfb 100644 --- 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 @@ -14,6 +14,7 @@ import org.xrpl.xrpl4j.model.ledger.BridgeObject; import org.xrpl.xrpl4j.model.ledger.CheckObject; import org.xrpl.xrpl4j.model.ledger.DepositPreAuthObject; +import org.xrpl.xrpl4j.model.ledger.DidObject; import org.xrpl.xrpl4j.model.ledger.EscrowObject; import org.xrpl.xrpl4j.model.ledger.LedgerObject; import org.xrpl.xrpl4j.model.ledger.NfTokenPageObject; @@ -323,6 +324,24 @@ static LedgerEntryRequestParams bridge( .build(); } + /** + * Construct a {@link LedgerEntryRequestParams} that requests a {@link DidObject} ledger entry. + * + * @param address The address of the owner of the {@link DidObject}. + * @param ledgerSpecifier A {@link LedgerSpecifier} indicating the ledger to query data from. + * + * @return A {@link LedgerEntryRequestParams} for {@link DidObject}. + */ + static LedgerEntryRequestParams did( + Address address, + LedgerSpecifier ledgerSpecifier + ) { + return ImmutableLedgerEntryRequestParams.builder() + .did(address) + .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. @@ -425,6 +444,13 @@ default boolean binary() { */ Optional ticket(); + /** + * Loop up a {@link org.xrpl.xrpl4j.model.ledger.DidObject} by {@link Address}. + * + * @return An optionally-present {@link Address}. + */ + Optional

did(); + /** * Look up a {@link org.xrpl.xrpl4j.model.ledger.BridgeObject} by {@link Address}. The {@link #bridge()} field must * also be present. @@ -495,6 +521,10 @@ default Class ledgerObjectClass() { return (Class) BridgeObject.class; } + if (did().isPresent()) { + return (Class) DidObject.class; + } + return (Class) LedgerObject.class; } } diff --git a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/jackson/modules/DidDataDeserializer.java b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/jackson/modules/DidDataDeserializer.java new file mode 100644 index 000000000..0cef2de06 --- /dev/null +++ b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/jackson/modules/DidDataDeserializer.java @@ -0,0 +1,46 @@ +package org.xrpl.xrpl4j.model.jackson.modules; + +/*- + * ========================LICENSE_START================================= + * xrpl4j :: model + * %% + * Copyright (C) 2020 - 2022 XRPL Foundation and its contributors + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * =========================LICENSE_END================================== + */ + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.deser.std.StdDeserializer; +import org.xrpl.xrpl4j.model.transactions.DidData; + +import java.io.IOException; + +/** + * Custom Jackson deserializer for {@link DidData}s. + */ +public class DidDataDeserializer extends StdDeserializer { + + /** + * No-args constructor. + */ + public DidDataDeserializer() { + super(DidData.class); + } + + @Override + public DidData deserialize(JsonParser jsonParser, DeserializationContext ctxt) throws IOException { + return DidData.of(jsonParser.getText()); + } +} diff --git a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/jackson/modules/DidDataSerializer.java b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/jackson/modules/DidDataSerializer.java new file mode 100644 index 000000000..13118f096 --- /dev/null +++ b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/jackson/modules/DidDataSerializer.java @@ -0,0 +1,46 @@ +package org.xrpl.xrpl4j.model.jackson.modules; + +/*- + * ========================LICENSE_START================================= + * xrpl4j :: model + * %% + * Copyright (C) 2020 - 2022 XRPL Foundation and its contributors + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * =========================LICENSE_END================================== + */ + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.ser.std.StdScalarSerializer; +import org.xrpl.xrpl4j.model.transactions.DidData; + +import java.io.IOException; + +/** + * Custom Jackson serializer for {@link DidData}s. + */ +public class DidDataSerializer extends StdScalarSerializer { + + /** + * No-args constructor. + */ + public DidDataSerializer() { + super(DidData.class, false); + } + + @Override + public void serialize(DidData didData, JsonGenerator gen, SerializerProvider provider) throws IOException { + gen.writeString(didData.value()); + } +} diff --git a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/jackson/modules/DidDocumentDeserializer.java b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/jackson/modules/DidDocumentDeserializer.java new file mode 100644 index 000000000..82d66db52 --- /dev/null +++ b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/jackson/modules/DidDocumentDeserializer.java @@ -0,0 +1,47 @@ +package org.xrpl.xrpl4j.model.jackson.modules; + +/*- + * ========================LICENSE_START================================= + * xrpl4j :: model + * %% + * Copyright (C) 2020 - 2022 XRPL Foundation and its contributors + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * =========================LICENSE_END================================== + */ + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.deser.std.StdDeserializer; +import org.xrpl.xrpl4j.model.transactions.Address; +import org.xrpl.xrpl4j.model.transactions.DidDocument; + +import java.io.IOException; + +/** + * Custom Jackson deserializer for {@link DidDocument}s. + */ +public class DidDocumentDeserializer extends StdDeserializer { + + /** + * No-args constructor. + */ + public DidDocumentDeserializer() { + super(DidDocument.class); + } + + @Override + public DidDocument deserialize(JsonParser jsonParser, DeserializationContext ctxt) throws IOException { + return DidDocument.of(jsonParser.getText()); + } +} diff --git a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/jackson/modules/DidDocumentSerializer.java b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/jackson/modules/DidDocumentSerializer.java new file mode 100644 index 000000000..d501a689b --- /dev/null +++ b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/jackson/modules/DidDocumentSerializer.java @@ -0,0 +1,46 @@ +package org.xrpl.xrpl4j.model.jackson.modules; + +/*- + * ========================LICENSE_START================================= + * xrpl4j :: model + * %% + * Copyright (C) 2020 - 2022 XRPL Foundation and its contributors + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * =========================LICENSE_END================================== + */ + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.ser.std.StdScalarSerializer; +import org.xrpl.xrpl4j.model.transactions.DidDocument; + +import java.io.IOException; + +/** + * Custom Jackson serializer for {@link DidDocument}s. + */ +public class DidDocumentSerializer extends StdScalarSerializer { + + /** + * No-args constructor. + */ + public DidDocumentSerializer() { + super(DidDocument.class, false); + } + + @Override + public void serialize(DidDocument didDocument, JsonGenerator gen, SerializerProvider provider) throws IOException { + gen.writeString(didDocument.value()); + } +} diff --git a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/jackson/modules/DidUriDeserializer.java b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/jackson/modules/DidUriDeserializer.java new file mode 100644 index 000000000..f7fd538cf --- /dev/null +++ b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/jackson/modules/DidUriDeserializer.java @@ -0,0 +1,46 @@ +package org.xrpl.xrpl4j.model.jackson.modules; + +/*- + * ========================LICENSE_START================================= + * xrpl4j :: model + * %% + * Copyright (C) 2020 - 2022 XRPL Foundation and its contributors + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * =========================LICENSE_END================================== + */ + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.deser.std.StdDeserializer; +import org.xrpl.xrpl4j.model.transactions.DidUri; + +import java.io.IOException; + +/** + * Custom Jackson deserializer for {@link DidUri}s. + */ +public class DidUriDeserializer extends StdDeserializer { + + /** + * No-args constructor. + */ + public DidUriDeserializer() { + super(DidUri.class); + } + + @Override + public DidUri deserialize(JsonParser jsonParser, DeserializationContext ctxt) throws IOException { + return DidUri.of(jsonParser.getText()); + } +} diff --git a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/jackson/modules/DidUriSerializer.java b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/jackson/modules/DidUriSerializer.java new file mode 100644 index 000000000..6322e5e50 --- /dev/null +++ b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/jackson/modules/DidUriSerializer.java @@ -0,0 +1,46 @@ +package org.xrpl.xrpl4j.model.jackson.modules; + +/*- + * ========================LICENSE_START================================= + * xrpl4j :: model + * %% + * Copyright (C) 2020 - 2022 XRPL Foundation and its contributors + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * =========================LICENSE_END================================== + */ + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.ser.std.StdScalarSerializer; +import org.xrpl.xrpl4j.model.transactions.DidUri; + +import java.io.IOException; + +/** + * Custom Jackson serializer for {@link DidUri}s. + */ +public class DidUriSerializer extends StdScalarSerializer { + + /** + * No-args constructor. + */ + public DidUriSerializer() { + super(DidUri.class, false); + } + + @Override + public void serialize(DidUri didUri, JsonGenerator gen, SerializerProvider provider) throws IOException { + gen.writeString(didUri.value()); + } +} diff --git a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/ledger/DidObject.java b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/ledger/DidObject.java new file mode 100644 index 000000000..af408be19 --- /dev/null +++ b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/ledger/DidObject.java @@ -0,0 +1,135 @@ +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 com.google.common.primitives.UnsignedInteger; +import org.immutables.value.Value; +import org.immutables.value.Value.Immutable; +import org.xrpl.xrpl4j.model.flags.Flags; +import org.xrpl.xrpl4j.model.transactions.Address; +import org.xrpl.xrpl4j.model.transactions.DidData; +import org.xrpl.xrpl4j.model.transactions.DidDocument; +import org.xrpl.xrpl4j.model.transactions.DidUri; +import org.xrpl.xrpl4j.model.transactions.Hash256; +import org.xrpl.xrpl4j.model.transactions.XChainBridge; +import org.xrpl.xrpl4j.model.transactions.XChainCount; + +import java.util.List; +import java.util.Optional; + +/** + * A {@code DID} ledger entry holds references to, or data associated with a single DID. + * + *

This interface will be marked {@link Beta} until the featureDID amendment is enabled on mainnet. Its API + * is subject to change.

+ */ +@Beta +@Immutable +@JsonSerialize(as = ImmutableDidObject.class) +@JsonDeserialize(as = ImmutableDidObject.class) +public interface DidObject extends LedgerObject { + + /** + * Construct a {@code DidObject} builder. + * + * @return An {@link ImmutableDidObject.Builder}. + */ + static ImmutableDidObject.Builder builder() { + return ImmutableDidObject.builder(); + } + + /** + * The type of ledger object, which will always be "DID" in this case. + * + * @return Always returns {@link LedgerEntryType#DID}. + */ + @JsonProperty("LedgerEntryType") + @Value.Derived + default LedgerEntryType ledgerEntryType() { + return LedgerEntryType.DID; + } + + /** + * A bit-map of boolean flags. No flags are defined for {@link DidObject}, so this value is always 0. + * + * @return Always {@link Flags#UNSET}. + */ + @JsonProperty("Flags") + @Value.Derived + default Flags flags() { + return Flags.UNSET; + } + + /** + * The account that controls the DID. + * + * @return An {@link Address}. + */ + @JsonProperty("Account") + Address account(); + + /** + * The W3C standard DID document associated with the DID. This field isn't checked for validity and is limited to a + * maximum length of 256 bytes. + * + * @return An optionally-present {@link DidDocument}. + */ + @JsonProperty("DIDDocument") + Optional didDocument(); + + /** + * The public attestations of identity credentials associated with the DID. This field isn't checked for validity and + * is limited to a maximum length of 256 bytes. + * + * @return An optionally-present {@link DidData}. + */ + @JsonProperty("Data") + Optional data(); + + /** + * The Universal Resource Identifier that points to the corresponding DID document or the data associated with the + * DID. This field can be an HTTP(S) URL or IPFS URI. This field isn't checked for validity and is limited to a + * maximum length of 256 bytes. + * + * @return An optionally-present {@link DidUri}. + */ + @JsonProperty("URI") + Optional uri(); + + /** + * 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(); + + /** + * The identifying hash of the transaction that most recently modified this object. + * + * @return A {@link Hash256} containing the previous transaction hash. + */ + @JsonProperty("PreviousTxnID") + Hash256 previousTransactionId(); + + /** + * The index of the ledger that contains the transaction that most recently modified this object. + * + * @return An {@link UnsignedInteger} representing the previous transaction ledger sequence. + */ + @JsonProperty("PreviousTxnLgrSeq") + UnsignedInteger previousTransactionLedgerSequence(); + + /** + * The unique ID of the {@link DidObject}. + * + * @return A {@link Hash256} containing the ID. + */ + Hash256 index(); +} 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 ecbd3d316..e463234d5 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 @@ -61,6 +61,7 @@ name = "XChainOwnedCreateAccountClaimID" ), @JsonSubTypes.Type(value = ImmutableXChainOwnedClaimIdObject.class, name = "XChainOwnedClaimID"), + @JsonSubTypes.Type(value = ImmutableDidObject.class, name = "DID"), }) // TODO: Uncomment subtypes as we implement public interface LedgerObject { @@ -184,7 +185,16 @@ enum LedgerEntryType { * Its API is subject to change.

*/ @Beta - XCHAIN_OWNED_CLAIM_ID("XChainOwnedClaimID"); + XCHAIN_OWNED_CLAIM_ID("XChainOwnedClaimID"), + + /** + * The {@link LedgerEntryType} for {@code DID} ledger objects. + * + *

This constant will be marked {@link Beta} until the featureDID amendment is enabled on mainnet. + * Its API is subject to change.

+ */ + @Beta + DID("DID"); private final String value; diff --git a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/DidDelete.java b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/DidDelete.java new file mode 100644 index 000000000..91b27ff6b --- /dev/null +++ b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/DidDelete.java @@ -0,0 +1,50 @@ +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 java.util.Optional; + +/** + * Object mapping for the {@code DIDDelete} transaction. + * + *

This constant will be marked {@link Beta} until the featureDID amendment is enabled on mainnet. Its API + * is subject to change.

+ */ +@Beta +@Immutable +@JsonSerialize(as = ImmutableDidDelete.class) +@JsonDeserialize(as = ImmutableDidDelete.class) +public interface DidDelete extends Transaction { + + /** + * Construct a {@code DidDelete} builder. + * + * @return An {@link ImmutableDidDelete.Builder}. + */ + static ImmutableDidDelete.Builder builder() { + return ImmutableDidDelete.builder(); + } + + /** + * Set of {@link TransactionFlags}s for this {@link DidDelete}, which only allows the {@code tfFullyCanonicalSig} + * flag, which is deprecated. + * + *

The value of the flags can be set manually, but exists mostly 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; + } + + +} diff --git a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/DidSet.java b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/DidSet.java new file mode 100644 index 000000000..95559bfe8 --- /dev/null +++ b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/DidSet.java @@ -0,0 +1,75 @@ +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 java.util.Optional; + +/** + * Object mapping for the {@code DIDSet} transaction. + * + *

This constant will be marked {@link Beta} until the featureDID amendment is enabled on mainnet. Its API + * is subject to change.

+ */ +@Beta +@Immutable +@JsonSerialize(as = ImmutableDidSet.class) +@JsonDeserialize(as = ImmutableDidSet.class) +public interface DidSet extends Transaction { + + /** + * Construct a {@code DidSet} builder. + * + * @return An {@link ImmutableDidSet.Builder}. + */ + static ImmutableDidSet.Builder builder() { + return ImmutableDidSet.builder(); + } + + /** + * Set of {@link TransactionFlags}s for this {@link DidSet}, which only allows the {@code tfFullyCanonicalSig} flag, + * which is deprecated. + * + *

The value of the flags can be set manually, but exists mostly 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 DID document for this DID. This field should contain a DID Document per W3C standards, however its contents + * are not checked for validity by the XRPL. + * + * @return An optionally-present {@link DidDocument}. + */ + @JsonProperty("DIDDocument") + Optional didDocument(); + + /** + * The Universal Resource Identifier associated with the DID. + * + * @return An optionally-present {@link DidUri}. + */ + @JsonProperty("URI") + Optional uri(); + + /** + * The public attestations of identity credentials associated with the DID. + * + * @return An optionalyl-present {@link DidData}. + */ + @JsonProperty("Data") + Optional data(); + + +} 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 4c74c491b..40c3e7695 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 @@ -86,6 +86,8 @@ public interface Transaction { .put(ImmutableXChainCreateBridge.class, TransactionType.XCHAIN_CREATE_BRIDGE) .put(ImmutableXChainCreateClaimId.class, TransactionType.XCHAIN_CREATE_CLAIM_ID) .put(ImmutableXChainModifyBridge.class, TransactionType.XCHAIN_MODIFY_BRIDGE) + .put(ImmutableDidSet.class, TransactionType.DID_SET) + .put(ImmutableDidDelete.class, TransactionType.DID_DELETE) .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 91f7d2d59..244e00544 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 @@ -300,7 +300,25 @@ public enum TransactionType { * is subject to change.

*/ @Beta - XCHAIN_MODIFY_BRIDGE("XChainModifyBridge"); + XCHAIN_MODIFY_BRIDGE("XChainModifyBridge"), + + /** + * The {@link TransactionType} for the {@link DidSet} transaction. + * + *

This constant will be marked {@link Beta} until the featureDID amendment is enabled on mainnet. Its API + * is subject to change.

+ */ + @Beta + DID_SET("DIDSet"), + + /** + * The {@link TransactionType} for the {@link DidDelete} transaction. + * + *

This constant will be marked {@link Beta} until the featureDID amendment is enabled on mainnet. Its API + * is subject to change.

+ */ + @Beta + DID_DELETE("DIDDelete"); 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 3558d0c9e..883843409 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 @@ -34,6 +34,12 @@ import org.xrpl.xrpl4j.model.immutables.Wrapper; import org.xrpl.xrpl4j.model.jackson.modules.AddressDeserializer; import org.xrpl.xrpl4j.model.jackson.modules.AddressSerializer; +import org.xrpl.xrpl4j.model.jackson.modules.DidDataDeserializer; +import org.xrpl.xrpl4j.model.jackson.modules.DidDataSerializer; +import org.xrpl.xrpl4j.model.jackson.modules.DidDocumentDeserializer; +import org.xrpl.xrpl4j.model.jackson.modules.DidDocumentSerializer; +import org.xrpl.xrpl4j.model.jackson.modules.DidUriDeserializer; +import org.xrpl.xrpl4j.model.jackson.modules.DidUriSerializer; import org.xrpl.xrpl4j.model.jackson.modules.Hash256Deserializer; import org.xrpl.xrpl4j.model.jackson.modules.Hash256Serializer; import org.xrpl.xrpl4j.model.jackson.modules.MarkerDeserializer; @@ -536,4 +542,64 @@ public String toString() { } } + + /** + * A wrapped {@link String} containing a DID Document. + * + *

This class will be marked {@link com.google.common.annotations.Beta} until the featureDID amendment is + * enabled on mainnet. Its API is subject to change.

+ */ + @Value.Immutable + @Wrapped + @JsonSerialize(as = DidDocument.class, using = DidDocumentSerializer.class) + @JsonDeserialize(as = DidDocument.class, using = DidDocumentDeserializer.class) + @Beta + abstract static class _DidDocument extends Wrapper implements Serializable { + + @Override + public String toString() { + return this.value(); + } + + } + + /** + * A wrapped {@link String} containing a DID URI. + * + *

This class will be marked {@link com.google.common.annotations.Beta} until the featureDID amendment is + * enabled on mainnet. Its API is subject to change.

+ */ + @Value.Immutable + @Wrapped + @JsonSerialize(as = DidUri.class, using = DidUriSerializer.class) + @JsonDeserialize(as = DidUri.class, using = DidUriDeserializer.class) + @Beta + abstract static class _DidUri extends Wrapper implements Serializable { + + @Override + public String toString() { + return this.value(); + } + + } + + /** + * A wrapped {@link String} containing DID Data. + * + *

This class will be marked {@link com.google.common.annotations.Beta} until the featureDID amendment is + * enabled on mainnet. Its API is subject to change.

+ */ + @Value.Immutable + @Wrapped + @JsonSerialize(as = DidData.class, using = DidDataSerializer.class) + @JsonDeserialize(as = DidData.class, using = DidDataDeserializer.class) + @Beta + abstract static class _DidData extends Wrapper implements Serializable { + + @Override + public String toString() { + return this.value(); + } + + } } diff --git a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/metadata/MetaDidObject.java b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/metadata/MetaDidObject.java new file mode 100644 index 000000000..cbfddc267 --- /dev/null +++ b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/metadata/MetaDidObject.java @@ -0,0 +1,108 @@ +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 com.google.common.primitives.UnsignedInteger; +import org.immutables.value.Value; +import org.immutables.value.Value.Immutable; +import org.xrpl.xrpl4j.model.flags.Flags; +import org.xrpl.xrpl4j.model.ledger.ImmutableDidObject; +import org.xrpl.xrpl4j.model.ledger.LedgerObject; +import org.xrpl.xrpl4j.model.transactions.Address; +import org.xrpl.xrpl4j.model.transactions.DidData; +import org.xrpl.xrpl4j.model.transactions.DidDocument; +import org.xrpl.xrpl4j.model.transactions.DidUri; +import org.xrpl.xrpl4j.model.transactions.Hash256; + +import java.util.Optional; + +/** + * A {@code DID} ledger entry holds references to, or data associated with a single DID. + * + *

This interface will be marked {@link Beta} until the featureDID amendment is enabled on mainnet. Its API + * is subject to change.

+ */ +@Beta +@Immutable +@JsonSerialize(as = ImmutableMetaDidObject.class) +@JsonDeserialize(as = ImmutableMetaDidObject.class) +public interface MetaDidObject extends MetaLedgerObject { + + /** + * A bit-map of boolean flags. No flags are defined for {@link MetaDidObject}, so this value is always 0. + * + * @return Always {@link Flags#UNSET}. + */ + @JsonProperty("Flags") + @Value.Derived + default Flags flags() { + return Flags.UNSET; + } + + /** + * The account that controls the DID. + * + * @return An {@link Address}. + */ + @JsonProperty("Account") + Optional
account(); + + /** + * The W3C standard DID document associated with the DID. This field isn't checked for validity and is limited to a + * maximum length of 256 bytes. + * + * @return An optionally-present {@link DidDocument}. + */ + @JsonProperty("DIDDocument") + Optional didDocument(); + + /** + * The public attestations of identity credentials associated with the DID. This field isn't checked for validity and + * is limited to a maximum length of 256 bytes. + * + * @return An optionally-present {@link DidData}. + */ + @JsonProperty("Data") + Optional data(); + + /** + * The Universal Resource Identifier that points to the corresponding DID document or the data associated with the + * DID. This field can be an HTTP(S) URL or IPFS URI. This field isn't checked for validity and is limited to a + * maximum length of 256 bytes. + * + * @return An optionally-present {@link DidUri}. + */ + @JsonProperty("URI") + Optional uri(); + + /** + * 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") + Optional ownerNode(); + + /** + * The identifying hash of the transaction that most recently modified this object. + * + * @return A {@link Hash256} containing the previous transaction hash. + */ + @JsonProperty("PreviousTxnID") + Optional previousTransactionId(); + + /** + * The index of the ledger that contains the transaction that most recently modified this object. + * + * @return An {@link UnsignedInteger} representing the previous transaction ledger sequence. + */ + @JsonProperty("PreviousTxnLgrSeq") + Optional previousTransactionLedgerSequence(); + +} 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 8c1a24936..b913bc70f 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 @@ -41,6 +41,9 @@ public interface MetaLedgerEntryType { @Beta MetaLedgerEntryType XCHAIN_OWNED_CLAIM_ID = MetaLedgerEntryType.of("XChainOwnedClaimID"); + @Beta + MetaLedgerEntryType DID = MetaLedgerEntryType.of("DID"); + /** * Construct a new {@link MetaLedgerEntryType} from a {@link String}. * @@ -93,6 +96,8 @@ default Class ledgerObjectType() { return MetaXChainOwnedClaimIdObject.class; case "XChainOwnedCreateAccountClaimID": return MetaXChainOwnedCreateAccountClaimIdObject.class; + case "DID": + return MetaDidObject.class; default: return MetaUnknownObject.class; } diff --git a/xrpl4j-core/src/main/resources/definitions.json b/xrpl4j-core/src/main/resources/definitions.json index b6b48f440..b8fd9a8a1 100644 --- a/xrpl4j-core/src/main/resources/definitions.json +++ b/xrpl4j-core/src/main/resources/definitions.json @@ -50,6 +50,7 @@ "NFTokenPage": 80, "NFTokenOffer": 55, "AMM": 121, + "DID": 73, "Any": -3, "Child": -2, "Nickname": 110, @@ -140,40 +141,40 @@ [ "LedgerEntry", { - "nth": 1, + "nth": 257, "isVLEncoded": false, "isSerialized": false, - "isSigningField": true, + "isSigningField": false, "type": "LedgerEntry" } ], [ "Transaction", { - "nth": 1, + "nth": 257, "isVLEncoded": false, "isSerialized": false, - "isSigningField": true, + "isSigningField": false, "type": "Transaction" } ], [ "Validation", { - "nth": 1, + "nth": 257, "isVLEncoded": false, "isSerialized": false, - "isSigningField": true, + "isSigningField": false, "type": "Validation" } ], [ "Metadata", { - "nth": 1, + "nth": 257, "isVLEncoded": false, - "isSerialized": true, - "isSigningField": true, + "isSerialized": false, + "isSigningField": false, "type": "Metadata" } ], @@ -1897,6 +1898,26 @@ "type": "Blob" } ], + [ + "DIDDocument", + { + "nth": 26, + "isVLEncoded": true, + "isSerialized": true, + "isSigningField": true, + "type": "Blob" + } + ], + [ + "Data", + { + "nth": 27, + "isVLEncoded": true, + "isSerialized": true, + "isSigningField": true, + "type": "Blob" + } + ], [ "Account", { @@ -2681,6 +2702,7 @@ "temXCHAIN_BRIDGE_NONDOOR_OWNER": -257, "temXCHAIN_BRIDGE_BAD_MIN_ACCOUNT_CREATE_AMOUNT": -256, "temXCHAIN_BRIDGE_BAD_REWARD_AMOUNT": -255, + "temEMPTY_DID": -254, "tefFAILURE": -199, "tefALREADY": -198, @@ -2759,7 +2781,7 @@ "tecKILLED": 150, "tecHAS_OBLIGATIONS": 151, "tecTOO_SOON": 152, - "tecHOOK_ERROR": 153, + "tecHOOK_REJECTED": 153, "tecMAX_SEQUENCE_REACHED": 154, "tecNO_SUITABLE_NFTOKEN_PAGE": 155, "tecNFTOKEN_BUY_SELL_MISMATCH": 156, @@ -2792,7 +2814,8 @@ "tecXCHAIN_PAYMENT_FAILED": 183, "tecXCHAIN_SELF_COMMIT": 184, "tecXCHAIN_BAD_PUBLIC_KEY_ACCOUNT_PAIR": 185, - "tecXCHAIN_CREATE_ACCOUNT_DISABLED": 186 + "tecXCHAIN_CREATE_ACCOUNT_DISABLED": 186, + "tecEMPTY_DID": 187 }, "TRANSACTION_TYPES": { "Invalid": -1, @@ -2839,6 +2862,8 @@ "XChainAddAccountCreateAttestation": 46, "XChainModifyBridge": 47, "XChainCreateBridge": 48, + "DIDSet": 49, + "DIDDelete": 50, "EnableAmendment": 100, "SetFee": 101, "UNLModify": 102 diff --git a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/codec/binary/BinarySerializationTests.java b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/codec/binary/BinarySerializationTests.java index 292c14e30..d22654441 100644 --- a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/codec/binary/BinarySerializationTests.java +++ b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/codec/binary/BinarySerializationTests.java @@ -57,10 +57,17 @@ import org.xrpl.xrpl4j.model.transactions.CheckCreate; import org.xrpl.xrpl4j.model.transactions.CurrencyAmount; import org.xrpl.xrpl4j.model.transactions.DepositPreAuth; +import org.xrpl.xrpl4j.model.transactions.DidData; +import org.xrpl.xrpl4j.model.transactions.DidDelete; +import org.xrpl.xrpl4j.model.transactions.DidDocument; +import org.xrpl.xrpl4j.model.transactions.DidSet; +import org.xrpl.xrpl4j.model.transactions.DidUri; import org.xrpl.xrpl4j.model.transactions.EscrowCancel; import org.xrpl.xrpl4j.model.transactions.EscrowCreate; import org.xrpl.xrpl4j.model.transactions.EscrowFinish; import org.xrpl.xrpl4j.model.transactions.Hash256; +import org.xrpl.xrpl4j.model.transactions.ImmutableDidDelete; +import org.xrpl.xrpl4j.model.transactions.ImmutableDidSet; import org.xrpl.xrpl4j.model.transactions.ImmutableXChainAccountCreateCommit; import org.xrpl.xrpl4j.model.transactions.ImmutableXChainAddClaimAttestation; import org.xrpl.xrpl4j.model.transactions.ImmutableXChainClaim; @@ -2036,6 +2043,83 @@ void serializeXChainModifyBridge() throws JsonProcessingException { assertSerializesAndDeserializes(transaction, binary); } + @Test + void serializeDidSet() throws JsonProcessingException { + DidSet transaction = DidSet.builder() + .account(Address.of("rfmDuhDyLGgx94qiwf3YF8BUV5j6KSvE8")) + .fee(XrpCurrencyAmount.ofDrops(10)) + .flags(TransactionFlags.FULLY_CANONICAL_SIG) + .sequence(UnsignedInteger.valueOf(3)) + .signingPublicKey( + PublicKey.fromBase16EncodedPublicKey("ED9861C4CB029C0DA737B823D7D3459A70F227958D5C0C111CC7CF947FC5A93347") + ) + .transactionSignature( + Signature.fromBase16("AACD31A04CAE14670FC483A1382F393AA96B49C84479B58067F049FBD7729993" + + "25667A6AA2520A63756EE84F3657298815019DD56A1AECE796B08535C4009C08") + ) + .didDocument(DidDocument.of("646F63")) + .data(DidData.of("617474657374")) + .uri(DidUri.of("6469645F6578616D706C65")) + .build(); + + String binary = "1200312280000000240000000368400000000000000A7321ED9861C4CB029C0DA737B823D7D3459A70F227" + + "958D5C0C111CC7CF947FC5A933477440AACD31A04CAE14670FC483A1382F393AA96B49C84479B58067F049FBD77299932566" + + "7A6AA2520A63756EE84F3657298815019DD56A1AECE796B08535C4009C08750B6469645F6578616D706C65701A03646F6370" + + "1B06617474657374811401476926B590BA3245F63C829116A0A3AF7F382D"; + + assertSerializesAndDeserializes(transaction, binary); + } + + @Test + void serializeDidSetWithEmptyValues() throws JsonProcessingException { + DidSet transaction = DidSet.builder() + .account(Address.of("rfmDuhDyLGgx94qiwf3YF8BUV5j6KSvE8")) + .fee(XrpCurrencyAmount.ofDrops(10)) + .flags(TransactionFlags.FULLY_CANONICAL_SIG) + .sequence(UnsignedInteger.valueOf(3)) + .signingPublicKey( + PublicKey.fromBase16EncodedPublicKey("ED9861C4CB029C0DA737B823D7D3459A70F227958D5C0C111CC7CF947FC5A93347") + ) + .transactionSignature( + Signature.fromBase16("AACD31A04CAE14670FC483A1382F393AA96B49C84479B58067F049FBD7729993" + + "25667A6AA2520A63756EE84F3657298815019DD56A1AECE796B08535C4009C08") + ) + .didDocument(DidDocument.of("")) + .data(DidData.of("")) + .uri(DidUri.of("")) + .build(); + + String binary = "1200312280000000240000000368400000000000000A7321ED9861C4CB029C0DA737B823D7D3459A70F2" + + "27958D5C0C111CC7CF947FC5A933477440AACD31A04CAE14670FC483A1382F393AA96B49C84479B58067F049FBD7729993" + + "25667A6AA2520A63756EE84F3657298815019DD56A1AECE796B08535C4009C087500701A00701B00811401476926B590BA" + + "3245F63C829116A0A3AF7F382D"; + + assertSerializesAndDeserializes(transaction, binary); + } + + @Test + void serializeDidDelete() throws JsonProcessingException { + DidDelete transaction = DidDelete.builder() + .account(Address.of("rfmDuhDyLGgx94qiwf3YF8BUV5j6KSvE8")) + .fee(XrpCurrencyAmount.ofDrops(10)) + .sequence(UnsignedInteger.valueOf(4)) + .signingPublicKey( + PublicKey.fromBase16EncodedPublicKey("ED9861C4CB029C0DA737B823D7D3459A70F227958D5C0C111CC7CF947FC5A93347") + ) + .transactionSignature( + Signature.fromBase16("71E28B12465A1B47162C22E121DF61089DCD9AAF5773704B76179E771666" + + "886C8AAD5A33A87E34CC381A7D924E3FE3645F0BF98D565DE42C81E1A7A7E7981802") + ) + .flags(TransactionFlags.FULLY_CANONICAL_SIG) + .build(); + + String binary = "1200322280000000240000000468400000000000000A7321ED9861C4CB029C0DA737B823D7D3459A70F22" + + "7958D5C0C111CC7CF947FC5A93347744071E28B12465A1B47162C22E121DF61089DCD9AAF5773704B76179E771666886C8AA" + + "D5A33A87E34CC381A7D924E3FE3645F0BF98D565DE42C81E1A7A7E7981802811401476926B590BA3245F63C829116A0A3AF7F382D"; + + assertSerializesAndDeserializes(transaction, binary); + } + private void assertSerializesAndDeserializes( T transaction, String expectedBinary 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 75e286c6e..e23dd9dcd 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 @@ -71,6 +71,11 @@ 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.DidData; +import org.xrpl.xrpl4j.model.transactions.DidDelete; +import org.xrpl.xrpl4j.model.transactions.DidDocument; +import org.xrpl.xrpl4j.model.transactions.DidSet; +import org.xrpl.xrpl4j.model.transactions.DidUri; import org.xrpl.xrpl4j.model.transactions.EscrowCancel; import org.xrpl.xrpl4j.model.transactions.EscrowCreate; import org.xrpl.xrpl4j.model.transactions.EscrowFinish; @@ -1080,6 +1085,30 @@ void addSignatureToXChainModifyBridge() { addSignatureToTransactionHelper(transaction); } + @Test + void addSignatureToDidSet() { + DidSet transaction = DidSet.builder() + .account(sourcePublicKey.deriveAddress()) + .fee(XrpCurrencyAmount.ofDrops(10)) + .sequence(UnsignedInteger.valueOf(391)) + .signingPublicKey(sourcePublicKey) + .build(); + + addSignatureToTransactionHelper(transaction); + } + + @Test + void addSignatureToDidDelete() { + DidDelete transaction = DidDelete.builder() + .account(sourcePublicKey.deriveAddress()) + .fee(XrpCurrencyAmount.ofDrops(10)) + .sequence(UnsignedInteger.valueOf(391)) + .signingPublicKey(sourcePublicKey) + .build(); + + addSignatureToTransactionHelper(transaction); + } + @Test public void addSignatureToTransactionUnsupported() { assertThrows(IllegalArgumentException.class, () -> addSignatureToTransactionHelper(transactionMock)); @@ -1673,6 +1702,28 @@ void addMultiSignatureToXChainModifyBridge() { addMultiSignatureToTransactionHelper(transaction); } + @Test + void addMultiSignatureToDidSet() { + DidSet transaction = DidSet.builder() + .account(sourcePublicKey.deriveAddress()) + .fee(XrpCurrencyAmount.ofDrops(10)) + .sequence(UnsignedInteger.valueOf(391)) + .build(); + + addMultiSignatureToTransactionHelper(transaction); + } + + @Test + void addMultiSignatureToDidDelete() { + DidDelete transaction = DidDelete.builder() + .account(sourcePublicKey.deriveAddress()) + .fee(XrpCurrencyAmount.ofDrops(10)) + .sequence(UnsignedInteger.valueOf(391)) + .build(); + + addMultiSignatureToTransactionHelper(transaction); + } + @Test public void addMultiSignaturesToTransactionUnsupported() { when(transactionMock.transactionSignature()).thenReturn(Optional.empty()); 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 index 9f3c3d1d8..62c272658 100644 --- 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 @@ -19,6 +19,7 @@ import org.xrpl.xrpl4j.model.ledger.BridgeObject; import org.xrpl.xrpl4j.model.ledger.CheckObject; import org.xrpl.xrpl4j.model.ledger.DepositPreAuthObject; +import org.xrpl.xrpl4j.model.ledger.DidObject; import org.xrpl.xrpl4j.model.ledger.EscrowObject; import org.xrpl.xrpl4j.model.ledger.Issue; import org.xrpl.xrpl4j.model.ledger.LedgerObject; @@ -52,6 +53,7 @@ void testTypedIndexParams() throws JSONException, JsonProcessingException { assertThat(params.nftPage()).isEmpty(); assertThat(params.bridgeAccount()).isEmpty(); assertThat(params.bridge()).isEmpty(); + assertThat(params.did()).isEmpty(); String json = String.format("{\n" + " \"index\": \"%s\",\n" + @@ -85,6 +87,7 @@ void testUntypedIndexParams() throws JSONException, JsonProcessingException { assertThat(params.depositPreAuth()).isEmpty(); assertThat(params.ticket()).isEmpty(); assertThat(params.nftPage()).isEmpty(); + assertThat(params.did()).isEmpty(); assertThat(params.bridgeAccount()).isEmpty(); assertThat(params.bridge()).isEmpty(); @@ -115,6 +118,7 @@ void testAccountRootParams() throws JSONException, JsonProcessingException { assertThat(params.depositPreAuth()).isEmpty(); assertThat(params.ticket()).isEmpty(); assertThat(params.nftPage()).isEmpty(); + assertThat(params.did()).isEmpty(); assertThat(params.bridgeAccount()).isEmpty(); assertThat(params.bridge()).isEmpty(); @@ -153,6 +157,7 @@ void testAmmParams() throws JSONException, JsonProcessingException { assertThat(params.depositPreAuth()).isEmpty(); assertThat(params.ticket()).isEmpty(); assertThat(params.nftPage()).isEmpty(); + assertThat(params.did()).isEmpty(); assertThat(params.bridgeAccount()).isEmpty(); assertThat(params.bridge()).isEmpty(); @@ -196,6 +201,7 @@ void testOfferParams() throws JSONException, JsonProcessingException { assertThat(params.depositPreAuth()).isEmpty(); assertThat(params.ticket()).isEmpty(); assertThat(params.nftPage()).isEmpty(); + assertThat(params.did()).isEmpty(); assertThat(params.bridgeAccount()).isEmpty(); assertThat(params.bridge()).isEmpty(); @@ -237,6 +243,7 @@ void testRippleStateParams() throws JSONException, JsonProcessingException { assertThat(params.depositPreAuth()).isEmpty(); assertThat(params.ticket()).isEmpty(); assertThat(params.nftPage()).isEmpty(); + assertThat(params.did()).isEmpty(); assertThat(params.bridgeAccount()).isEmpty(); assertThat(params.bridge()).isEmpty(); @@ -271,6 +278,7 @@ void testCheckParams() throws JSONException, JsonProcessingException { assertThat(params.depositPreAuth()).isEmpty(); assertThat(params.ticket()).isEmpty(); assertThat(params.nftPage()).isEmpty(); + assertThat(params.did()).isEmpty(); assertThat(params.bridgeAccount()).isEmpty(); assertThat(params.bridge()).isEmpty(); @@ -305,6 +313,7 @@ void testEscrowParams() throws JSONException, JsonProcessingException { assertThat(params.depositPreAuth()).isEmpty(); assertThat(params.ticket()).isEmpty(); assertThat(params.nftPage()).isEmpty(); + assertThat(params.did()).isEmpty(); assertThat(params.bridgeAccount()).isEmpty(); assertThat(params.bridge()).isEmpty(); @@ -338,6 +347,7 @@ void testPaymentChannelParams() throws JSONException, JsonProcessingException { assertThat(params.depositPreAuth()).isEmpty(); assertThat(params.ticket()).isEmpty(); assertThat(params.nftPage()).isEmpty(); + assertThat(params.did()).isEmpty(); assertThat(params.bridgeAccount()).isEmpty(); assertThat(params.bridge()).isEmpty(); @@ -373,6 +383,7 @@ void testDepositPreAuthParams() throws JSONException, JsonProcessingException { assertThat(params.paymentChannel()).isEmpty(); assertThat(params.ticket()).isEmpty(); assertThat(params.nftPage()).isEmpty(); + assertThat(params.did()).isEmpty(); assertThat(params.bridgeAccount()).isEmpty(); assertThat(params.bridge()).isEmpty(); @@ -411,6 +422,7 @@ void testTicketParams() throws JSONException, JsonProcessingException { assertThat(params.paymentChannel()).isEmpty(); assertThat(params.depositPreAuth()).isEmpty(); assertThat(params.nftPage()).isEmpty(); + assertThat(params.did()).isEmpty(); assertThat(params.bridgeAccount()).isEmpty(); assertThat(params.bridge()).isEmpty(); @@ -445,6 +457,7 @@ void testNftPageParams() throws JSONException, JsonProcessingException { assertThat(params.paymentChannel()).isEmpty(); assertThat(params.depositPreAuth()).isEmpty(); assertThat(params.ticket()).isEmpty(); + assertThat(params.did()).isEmpty(); assertThat(params.bridgeAccount()).isEmpty(); assertThat(params.bridge()).isEmpty(); @@ -457,6 +470,38 @@ void testNftPageParams() throws JSONException, JsonProcessingException { assertCanSerializeAndDeserialize(params, json); } + @Test + void testDidParams() throws JSONException, JsonProcessingException { + LedgerEntryRequestParams params = LedgerEntryRequestParams.did( + ED_ADDRESS, + LedgerSpecifier.VALIDATED + ); + assertThat(params.did()).isNotEmpty().get().isEqualTo(ED_ADDRESS); + assertThat(params.ledgerObjectClass()).isEqualTo(DidObject.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(); + assertThat(params.nftPage()).isEmpty(); + assertThat(params.bridgeAccount()).isEmpty(); + assertThat(params.bridge()).isEmpty(); + + String json = String.format("{\n" + + " \"did\": \"%s\",\n" + + " \"binary\": false,\n" + + " \"ledger_index\": \"validated\"\n" + + " }", ED_ADDRESS); + + assertCanSerializeAndDeserialize(params, json); + } + @Test void testBridgeParams() throws JSONException, JsonProcessingException { XChainBridge bridge = XChainBridge.builder() @@ -485,6 +530,7 @@ void testBridgeParams() throws JSONException, JsonProcessingException { assertThat(params.depositPreAuth()).isEmpty(); assertThat(params.ticket()).isEmpty(); assertThat(params.nftPage()).isEmpty(); + assertThat(params.did()).isEmpty(); String json = String.format("{\n" + " \"bridge_account\": \"%s\",\n" + diff --git a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/ledger/DidObjectTest.java b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/ledger/DidObjectTest.java new file mode 100644 index 000000000..4942ee43a --- /dev/null +++ b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/ledger/DidObjectTest.java @@ -0,0 +1,67 @@ +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.DidData; +import org.xrpl.xrpl4j.model.transactions.DidDocument; +import org.xrpl.xrpl4j.model.transactions.DidUri; +import org.xrpl.xrpl4j.model.transactions.Hash256; + +class DidObjectTest extends AbstractJsonTest { + + @Test + void testJsonWithNonEmptyValues() throws JSONException, JsonProcessingException { + DidObject object = DidObject.builder() + .account(Address.of("rpfqJrXg5uidNo2ZsRhRY6TiF1cvYmV9Fg")) + .didDocument(DidDocument.of("646F63")) + .data(DidData.of("617474657374")) + .ownerNode("0") + .previousTransactionId(Hash256.of("A4C15DA185E6092DF5954FF62A1446220C61A5F60F0D93B4B09F708778E41120")) + .previousTransactionLedgerSequence(UnsignedInteger.valueOf(4)) + .uri(DidUri.of("6469645F6578616D706C65")) + .index(Hash256.of("46813BE38B798B3752CA590D44E7FEADB17485649074403AD1761A2835CE91FF")) + .build(); + + String json = "{\n" + + " \"Account\": \"rpfqJrXg5uidNo2ZsRhRY6TiF1cvYmV9Fg\",\n" + + " \"DIDDocument\": \"646F63\",\n" + + " \"Data\": \"617474657374\",\n" + + " \"Flags\": 0,\n" + + " \"LedgerEntryType\": \"DID\",\n" + + " \"OwnerNode\": \"0\",\n" + + " \"PreviousTxnID\": \"A4C15DA185E6092DF5954FF62A1446220C61A5F60F0D93B4B09F708778E41120\",\n" + + " \"PreviousTxnLgrSeq\": 4,\n" + + " \"URI\": \"6469645F6578616D706C65\",\n" + + " \"index\": \"46813BE38B798B3752CA590D44E7FEADB17485649074403AD1761A2835CE91FF\"\n" + + "}"; + + assertCanSerializeAndDeserialize(object, json); + } + + @Test + void testJsonWithEmptyValues() throws JSONException, JsonProcessingException { + DidObject object = DidObject.builder() + .account(Address.of("rpfqJrXg5uidNo2ZsRhRY6TiF1cvYmV9Fg")) + .ownerNode("0") + .previousTransactionId(Hash256.of("A4C15DA185E6092DF5954FF62A1446220C61A5F60F0D93B4B09F708778E41120")) + .previousTransactionLedgerSequence(UnsignedInteger.valueOf(4)) + .index(Hash256.of("46813BE38B798B3752CA590D44E7FEADB17485649074403AD1761A2835CE91FF")) + .build(); + + String json = "{\n" + + " \"Account\": \"rpfqJrXg5uidNo2ZsRhRY6TiF1cvYmV9Fg\",\n" + + " \"Flags\": 0,\n" + + " \"LedgerEntryType\": \"DID\",\n" + + " \"OwnerNode\": \"0\",\n" + + " \"PreviousTxnID\": \"A4C15DA185E6092DF5954FF62A1446220C61A5F60F0D93B4B09F708778E41120\",\n" + + " \"PreviousTxnLgrSeq\": 4,\n" + + " \"index\": \"46813BE38B798B3752CA590D44E7FEADB17485649074403AD1761A2835CE91FF\"\n" + + "}"; + + assertCanSerializeAndDeserialize(object, json); + } +} \ No newline at end of file diff --git a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/DidDataTest.java b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/DidDataTest.java new file mode 100644 index 000000000..74650d3aa --- /dev/null +++ b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/DidDataTest.java @@ -0,0 +1,72 @@ +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 org.assertj.core.api.Assertions; +import org.immutables.value.Value; +import org.json.JSONException; +import org.junit.jupiter.api.Test; +import org.skyscreamer.jsonassert.JSONAssert; +import org.skyscreamer.jsonassert.JSONCompareMode; +import org.xrpl.xrpl4j.model.jackson.ObjectMapperFactory; + +public class DidDataTest { + + ObjectMapper objectMapper = ObjectMapperFactory.create(); + + @Test + void testToString() { + DidData count = DidData.of(""); + assertThat(count.toString()).isEqualTo(""); + + DidData countMax = DidData.of("ABCDEFG"); + assertThat(countMax.toString()).isEqualTo("ABCDEFG"); + } + + @Test + void testJson() throws JsonProcessingException, JSONException { + DidData count = DidData.of("ABCDEF"); + DidDataWrapper wrapper = DidDataWrapper.of(count); + + String json = "{\"value\": \"ABCDEF\"}"; + assertSerializesAndDeserializes(wrapper, json); + } + + @Test + void testEmptyValueJson() throws JSONException, JsonProcessingException { + DidData count = DidData.of(""); + DidDataWrapper wrapper = DidDataWrapper.of(count); + + String json = "{\"value\": \"\"}"; + assertSerializesAndDeserializes(wrapper, json); + } + + private void assertSerializesAndDeserializes( + DidDataWrapper wrapper, + String json + ) throws JsonProcessingException, JSONException { + String serialized = objectMapper.writeValueAsString(wrapper); + JSONAssert.assertEquals(json, serialized, JSONCompareMode.STRICT); + DidDataWrapper deserialized = objectMapper.readValue( + serialized, DidDataWrapper.class + ); + Assertions.assertThat(deserialized).isEqualTo(wrapper); + } + + @Value.Immutable + @JsonSerialize(as = ImmutableDidDataWrapper.class) + @JsonDeserialize(as = ImmutableDidDataWrapper.class) + interface DidDataWrapper { + + static DidDataWrapper of(DidData value) { + return ImmutableDidDataWrapper.builder().value(value).build(); + } + + DidData value(); + + } +} diff --git a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/DidDeleteTest.java b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/DidDeleteTest.java new file mode 100644 index 000000000..739963267 --- /dev/null +++ b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/DidDeleteTest.java @@ -0,0 +1,72 @@ +package org.xrpl.xrpl4j.model.transactions; + +import static org.xrpl.xrpl4j.crypto.TestConstants.ED_PUBLIC_KEY; + +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.flags.TransactionFlags; + +class DidDeleteTest extends AbstractJsonTest { + + @Test + void testJsonWithEmptyFlags() throws JSONException, JsonProcessingException { + DidDelete transaction = baseBuilder().build(); + + String json = String.format("{\n" + + " \"TransactionType\": \"DIDDelete\", \n" + + " \"Account\": \"rp4pqYgrTAtdPHuZd1ZQWxrzx45jxYcZex\",\n" + + " \"Fee\": \"12\",\n" + + " \"Sequence\": 391,\n" + + " \"SigningPubKey\":\"%s\"\n" + + "}", ED_PUBLIC_KEY.base16Value()); + + assertCanSerializeAndDeserialize(transaction, json); + } + + @Test + void testJsonWithUnsetFlags() throws JSONException, JsonProcessingException { + DidDelete transaction = baseBuilder() + .flags(TransactionFlags.UNSET) + .build(); + + String json = String.format("{\n" + + " \"TransactionType\": \"DIDDelete\", \n" + + " \"Account\": \"rp4pqYgrTAtdPHuZd1ZQWxrzx45jxYcZex\",\n" + + " \"Fee\": \"12\",\n" + + " \"Sequence\": 391,\n" + + " \"Flags\": 0,\n" + + " \"SigningPubKey\":\"%s\"\n" + + "}", ED_PUBLIC_KEY.base16Value()); + + assertCanSerializeAndDeserialize(transaction, json); + } + + @Test + void testJsonWithSetFlags() throws JSONException, JsonProcessingException { + DidDelete transaction = baseBuilder() + .flags(TransactionFlags.FULLY_CANONICAL_SIG) + .build(); + + String json = String.format("{\n" + + " \"TransactionType\": \"DIDDelete\", \n" + + " \"Account\": \"rp4pqYgrTAtdPHuZd1ZQWxrzx45jxYcZex\",\n" + + " \"Fee\": \"12\",\n" + + " \"Sequence\": 391,\n" + + " \"Flags\": %s,\n" + + " \"SigningPubKey\":\"%s\"\n" + + "}", TransactionFlags.FULLY_CANONICAL_SIG, ED_PUBLIC_KEY.base16Value()); + + assertCanSerializeAndDeserialize(transaction, json); + } + + private ImmutableDidDelete.Builder baseBuilder() { + return DidDelete.builder() + .account(Address.of("rp4pqYgrTAtdPHuZd1ZQWxrzx45jxYcZex")) + .fee(XrpCurrencyAmount.ofDrops(12)) + .sequence(UnsignedInteger.valueOf(391)) + .signingPublicKey(ED_PUBLIC_KEY); + } +} \ No newline at end of file diff --git a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/DidDocumentTest.java b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/DidDocumentTest.java new file mode 100644 index 000000000..0ea670ee7 --- /dev/null +++ b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/DidDocumentTest.java @@ -0,0 +1,73 @@ +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.UnsignedLong; +import org.assertj.core.api.Assertions; +import org.immutables.value.Value; +import org.json.JSONException; +import org.junit.jupiter.api.Test; +import org.skyscreamer.jsonassert.JSONAssert; +import org.skyscreamer.jsonassert.JSONCompareMode; +import org.xrpl.xrpl4j.model.jackson.ObjectMapperFactory; + +public class DidDocumentTest { + + ObjectMapper objectMapper = ObjectMapperFactory.create(); + + @Test + void testToString() { + DidDocument count = DidDocument.of(""); + assertThat(count.toString()).isEqualTo(""); + + DidDocument countMax = DidDocument.of("ABCDEFG"); + assertThat(countMax.toString()).isEqualTo("ABCDEFG"); + } + + @Test + void testJson() throws JsonProcessingException, JSONException { + DidDocument count = DidDocument.of("ABCDEF"); + DidDocumentWrapper wrapper = DidDocumentWrapper.of(count); + + String json = "{\"value\": \"ABCDEF\"}"; + assertSerializesAndDeserializes(wrapper, json); + } + + @Test + void testEmptyValueJson() throws JSONException, JsonProcessingException { + DidDocument count = DidDocument.of(""); + DidDocumentWrapper wrapper = DidDocumentWrapper.of(count); + + String json = "{\"value\": \"\"}"; + assertSerializesAndDeserializes(wrapper, json); + } + + private void assertSerializesAndDeserializes( + DidDocumentWrapper wrapper, + String json + ) throws JsonProcessingException, JSONException { + String serialized = objectMapper.writeValueAsString(wrapper); + JSONAssert.assertEquals(json, serialized, JSONCompareMode.STRICT); + DidDocumentWrapper deserialized = objectMapper.readValue( + serialized, DidDocumentWrapper.class + ); + Assertions.assertThat(deserialized).isEqualTo(wrapper); + } + + @Value.Immutable + @JsonSerialize(as = ImmutableDidDocumentWrapper.class) + @JsonDeserialize(as = ImmutableDidDocumentWrapper.class) + interface DidDocumentWrapper { + + static DidDocumentWrapper of(DidDocument value) { + return ImmutableDidDocumentWrapper.builder().value(value).build(); + } + + DidDocument value(); + + } +} diff --git a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/DidSetTest.java b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/DidSetTest.java new file mode 100644 index 000000000..09051f8c9 --- /dev/null +++ b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/DidSetTest.java @@ -0,0 +1,116 @@ +package org.xrpl.xrpl4j.model.transactions; + +import static org.xrpl.xrpl4j.crypto.TestConstants.ED_PUBLIC_KEY; + +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.flags.TransactionFlags; + +class DidSetTest extends AbstractJsonTest { + + @Test + void testJsonWithNonEmptyFields() throws JSONException, JsonProcessingException { + DidSet transaction = DidSet.builder() + .account(Address.of("rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh")) + .fee(XrpCurrencyAmount.ofDrops(10)) + .sequence(UnsignedInteger.valueOf(391)) + .didDocument(DidDocument.of("697066733A2F2F62616679626569676479727A74357366703775646D3768753736756" + + "8377932366E6634646675796C71616266336F636C67747179353566627A6469")) + .uri(DidUri.of("697066733A2F2F62616679626569676479727A74357366703775646D3768753736756" + + "8377932366E6634646675796C71616266336F636C67747179353566627A6469")) + .data(DidData.of("697066733A2F2F62616679626569676479727A74357366703775646D3768753736756" + + "8377932366E6634646675796C71616266336F636C67747179353566627A6469")) + .signingPublicKey(ED_PUBLIC_KEY) + .build(); + + String json = String.format("{\n" + + " \"TransactionType\": \"DIDSet\",\n" + + " \"Account\": \"rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh\",\n" + + " \"Fee\": \"10\",\n" + + " \"Sequence\": 391,\n" + + " \"DIDDocument\": \"697066733A2F2F62616679626569676479727A74357366703775646D37687537367568377932" + + "366E6634646675796C71616266336F636C67747179353566627A6469\",\n" + + " \"URI\": \"697066733A2F2F62616679626569676479727A74357366703775646D37687537367568377932366E663464" + + "6675796C71616266336F636C67747179353566627A6469\",\n" + + " \"Data\": \"697066733A2F2F62616679626569676479727A74357366703775646D37687537367568377932366E66346" + + "46675796C71616266336F636C67747179353566627A6469\",\n" + + " \"SigningPubKey\":\"%s\"\n" + + "}", ED_PUBLIC_KEY.base16Value()); + + assertCanSerializeAndDeserialize(transaction, json); + } + + @Test + void testJsonWithEmptyFields() throws JSONException, JsonProcessingException { + DidSet transaction = DidSet.builder() + .account(Address.of("rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh")) + .fee(XrpCurrencyAmount.ofDrops(10)) + .sequence(UnsignedInteger.valueOf(391)) + .didDocument(DidDocument.of("")) + .uri(DidUri.of("")) + .data(DidData.of("")) + .signingPublicKey(ED_PUBLIC_KEY) + .build(); + String json = String.format("{\n" + + " \"TransactionType\": \"DIDSet\",\n" + + " \"Account\": \"rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh\",\n" + + " \"Fee\": \"10\",\n" + + " \"Sequence\": 391,\n" + + " \"DIDDocument\": \"\",\n" + + " \"URI\": \"\",\n" + + " \"Data\": \"\",\n" + + " \"SigningPubKey\":\"%s\"\n" + + "}", ED_PUBLIC_KEY.base16Value()); + + assertCanSerializeAndDeserialize(transaction, json); + } + + @Test + void testJsonWithUnsetFlags() throws JSONException, JsonProcessingException { + DidSet transaction = DidSet.builder() + .account(Address.of("rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh")) + .fee(XrpCurrencyAmount.ofDrops(10)) + .sequence(UnsignedInteger.valueOf(391)) + .didDocument(DidDocument.of("")) + .flags(TransactionFlags.UNSET) + .signingPublicKey(ED_PUBLIC_KEY) + .build(); + String json = String.format("{\n" + + " \"TransactionType\": \"DIDSet\",\n" + + " \"Account\": \"rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh\",\n" + + " \"Fee\": \"10\",\n" + + " \"Sequence\": 391,\n" + + " \"DIDDocument\": \"\",\n" + + " \"Flags\": 0,\n" + + " \"SigningPubKey\":\"%s\"\n" + + "}", ED_PUBLIC_KEY.base16Value()); + + assertCanSerializeAndDeserialize(transaction, json); + } + + @Test + void testJsonWithSetFlags() throws JSONException, JsonProcessingException { + DidSet transaction = DidSet.builder() + .account(Address.of("rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh")) + .fee(XrpCurrencyAmount.ofDrops(10)) + .sequence(UnsignedInteger.valueOf(391)) + .didDocument(DidDocument.of("")) + .flags(TransactionFlags.FULLY_CANONICAL_SIG) + .signingPublicKey(ED_PUBLIC_KEY) + .build(); + String json = String.format("{\n" + + " \"TransactionType\": \"DIDSet\",\n" + + " \"Account\": \"rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh\",\n" + + " \"Fee\": \"10\",\n" + + " \"Sequence\": 391,\n" + + " \"DIDDocument\": \"\",\n" + + " \"Flags\": %s,\n" + + " \"SigningPubKey\":\"%s\"\n" + + "}", TransactionFlags.FULLY_CANONICAL_SIG, ED_PUBLIC_KEY.base16Value()); + + assertCanSerializeAndDeserialize(transaction, json); + } +} \ No newline at end of file diff --git a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/DidUriTest.java b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/DidUriTest.java new file mode 100644 index 000000000..b2edfbaec --- /dev/null +++ b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/DidUriTest.java @@ -0,0 +1,72 @@ +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 org.assertj.core.api.Assertions; +import org.immutables.value.Value; +import org.json.JSONException; +import org.junit.jupiter.api.Test; +import org.skyscreamer.jsonassert.JSONAssert; +import org.skyscreamer.jsonassert.JSONCompareMode; +import org.xrpl.xrpl4j.model.jackson.ObjectMapperFactory; + +public class DidUriTest { + + ObjectMapper objectMapper = ObjectMapperFactory.create(); + + @Test + void testToString() { + DidUri count = DidUri.of(""); + assertThat(count.toString()).isEqualTo(""); + + DidUri countMax = DidUri.of("ABCDEFG"); + assertThat(countMax.toString()).isEqualTo("ABCDEFG"); + } + + @Test + void testJson() throws JsonProcessingException, JSONException { + DidUri count = DidUri.of("ABCDEF"); + DidUriWrapper wrapper = DidUriWrapper.of(count); + + String json = "{\"value\": \"ABCDEF\"}"; + assertSerializesAndDeserializes(wrapper, json); + } + + @Test + void testEmptyValueJson() throws JSONException, JsonProcessingException { + DidUri count = DidUri.of(""); + DidUriWrapper wrapper = DidUriWrapper.of(count); + + String json = "{\"value\": \"\"}"; + assertSerializesAndDeserializes(wrapper, json); + } + + private void assertSerializesAndDeserializes( + DidUriWrapper wrapper, + String json + ) throws JsonProcessingException, JSONException { + String serialized = objectMapper.writeValueAsString(wrapper); + JSONAssert.assertEquals(json, serialized, JSONCompareMode.STRICT); + DidUriWrapper deserialized = objectMapper.readValue( + serialized, DidUriWrapper.class + ); + Assertions.assertThat(deserialized).isEqualTo(wrapper); + } + + @Value.Immutable + @JsonSerialize(as = ImmutableDidUriWrapper.class) + @JsonDeserialize(as = ImmutableDidUriWrapper.class) + interface DidUriWrapper { + + static DidUriWrapper of(DidUri value) { + return ImmutableDidUriWrapper.builder().value(value).build(); + } + + DidUri value(); + + } +} 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 b9c18e6dd..5333b30dd 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 @@ -35,6 +35,7 @@ void testConstants() { assertThat(MetaLedgerEntryType.XCHAIN_OWNED_CREATE_ACCOUNT_CLAIM_ID.value()) .isEqualTo("XChainOwnedCreateAccountClaimID"); assertThat(MetaLedgerEntryType.XCHAIN_OWNED_CLAIM_ID.value()).isEqualTo("XChainOwnedClaimID"); + assertThat(MetaLedgerEntryType.DID.value()).isEqualTo("DID"); } @Test @@ -62,6 +63,7 @@ void testLedgerObjectType() { assertThat(MetaLedgerEntryType.XCHAIN_OWNED_CLAIM_ID.ledgerObjectType()).isEqualTo( MetaXChainOwnedClaimIdObject.class ); + assertThat(MetaLedgerEntryType.DID.ledgerObjectType()).isEqualTo(MetaDidObject.class); } @Test diff --git a/xrpl4j-core/src/test/resources/codec-fixtures.json b/xrpl4j-core/src/test/resources/codec-fixtures.json index b9a43d13c..28f7946f9 100644 --- a/xrpl4j-core/src/test/resources/codec-fixtures.json +++ b/xrpl4j-core/src/test/resources/codec-fixtures.json @@ -5019,6 +5019,33 @@ "SigningPubKey": "ED7453D2572A2104E7B266A45888C53F503CEB1F11DC4BB3710EB2995238EC65B8", "TxnSignature": "BC2F6E76969E3747E9BDE183C97573B086212F09D5387460E6EE2F32953E85EAEB9618FBBEF077276E30E59D619FCF7C7BDCDDDD9EB94D7CE1DD5CE9246B2107" } + }, + { + "binary": "1200322280000000240000000468400000000000000A7321ED9861C4CB029C0DA737B823D7D3459A70F227958D5C0C111CC7CF947FC5A93347744071E28B12465A1B47162C22E121DF61089DCD9AAF5773704B76179E771666886C8AAD5A33A87E34CC381A7D924E3FE3645F0BF98D565DE42C81E1A7A7E7981802811401476926B590BA3245F63C829116A0A3AF7F382D", + "json": { + "Account": "rfmDuhDyLGgx94qiwf3YF8BUV5j6KSvE8", + "Fee": "10", + "Flags": 2147483648, + "Sequence": 4, + "SigningPubKey": "ED9861C4CB029C0DA737B823D7D3459A70F227958D5C0C111CC7CF947FC5A93347", + "TransactionType": "DIDDelete", + "TxnSignature": "71E28B12465A1B47162C22E121DF61089DCD9AAF5773704B76179E771666886C8AAD5A33A87E34CC381A7D924E3FE3645F0BF98D565DE42C81E1A7A7E7981802" + } + }, + { + "binary": "1200312280000000240000000368400000000000000A7321ED9861C4CB029C0DA737B823D7D3459A70F227958D5C0C111CC7CF947FC5A933477440AACD31A04CAE14670FC483A1382F393AA96B49C84479B58067F049FBD772999325667A6AA2520A63756EE84F3657298815019DD56A1AECE796B08535C4009C08750B6469645F6578616D706C65701A03646F63701B06617474657374811401476926B590BA3245F63C829116A0A3AF7F382D", + "json": { + "Account": "rfmDuhDyLGgx94qiwf3YF8BUV5j6KSvE8", + "Data": "617474657374", + "DIDDocument": "646F63", + "Fee": "10", + "Flags": 2147483648, + "Sequence": 3, + "SigningPubKey": "ED9861C4CB029C0DA737B823D7D3459A70F227958D5C0C111CC7CF947FC5A93347", + "TransactionType": "DIDSet", + "TxnSignature": "AACD31A04CAE14670FC483A1382F393AA96B49C84479B58067F049FBD772999325667A6AA2520A63756EE84F3657298815019DD56A1AECE796B08535C4009C08", + "URI": "6469645F6578616D706C65" + } } ], "ledgerData": [ diff --git a/xrpl4j-integration-tests/src/test/java/org/xrpl/xrpl4j/tests/DidIT.java b/xrpl4j-integration-tests/src/test/java/org/xrpl/xrpl4j/tests/DidIT.java new file mode 100644 index 000000000..fcd886e17 --- /dev/null +++ b/xrpl4j-integration-tests/src/test/java/org/xrpl/xrpl4j/tests/DidIT.java @@ -0,0 +1,270 @@ +package org.xrpl.xrpl4j.tests; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +import com.fasterxml.jackson.core.JsonProcessingException; +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.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.model.client.accounts.AccountInfoResult; +import org.xrpl.xrpl4j.model.client.accounts.AccountObjectsResult; +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.transactions.TransactionResult; +import org.xrpl.xrpl4j.model.flags.Flags; +import org.xrpl.xrpl4j.model.jackson.ObjectMapperFactory; +import org.xrpl.xrpl4j.model.ledger.DidObject; +import org.xrpl.xrpl4j.model.ledger.LedgerObject; +import org.xrpl.xrpl4j.model.transactions.DidData; +import org.xrpl.xrpl4j.model.transactions.DidDelete; +import org.xrpl.xrpl4j.model.transactions.DidDocument; +import org.xrpl.xrpl4j.model.transactions.DidSet; +import org.xrpl.xrpl4j.model.transactions.DidUri; +import org.xrpl.xrpl4j.model.transactions.XrpCurrencyAmount; +import org.xrpl.xrpl4j.model.transactions.metadata.CreatedNode; +import org.xrpl.xrpl4j.model.transactions.metadata.DeletedNode; +import org.xrpl.xrpl4j.model.transactions.metadata.MetaDidObject; +import org.xrpl.xrpl4j.model.transactions.metadata.MetaLedgerEntryType; +import org.xrpl.xrpl4j.model.transactions.metadata.ModifiedNode; + +import java.util.List; +import java.util.stream.Collectors; + +@DisabledIf(value = "shouldRun", disabledReason = "DidIT only runs with local rippled nodes.") +public class DidIT extends AbstractIT { + + static boolean shouldRun() { + return System.getProperty("useTestnet") != null || + System.getProperty("useDevnet") != null || + System.getProperty("useClioTestnet") != null; + } + + @Test + void testCreateAndUpdateDid() throws JsonRpcClientErrorException, JsonProcessingException { + TestDid did = createNewDid(); + + AccountInfoResult sourceAccountInfo = this.getValidatedAccountInfo( + did.ownerKeyPair().publicKey().deriveAddress()); + + XrpCurrencyAmount fee = FeeUtils.computeNetworkFees(xrplClient.fee()).recommendedFee(); + DidSet updateDid = DidSet.builder() + .account(did.ownerKeyPair().publicKey().deriveAddress()) + .sequence(sourceAccountInfo.accountData().sequence()) + .fee(fee) + .lastLedgerSequence(sourceAccountInfo.ledgerIndexSafe().plus(UnsignedInteger.valueOf(4)).unsignedIntegerValue()) + .signingPublicKey(did.ownerKeyPair().publicKey()) + .didDocument(DidDocument.of("")) + .uri(DidUri.of("ABCD")) + .build(); + + TransactionResult didSetResult = this.signSubmitAndWait( + updateDid, + did.ownerKeyPair(), + DidSet.class + ); + + assertThat(didSetResult.metadata()).isNotEmpty(); + List> modifiedNodes = didSetResult.metadata().get().affectedNodes().stream() + .filter(node -> node.ledgerEntryType().equals(MetaLedgerEntryType.DID)) + .filter(node -> ModifiedNode.class.isAssignableFrom(node.getClass())) + .filter(node -> MetaDidObject.class.isAssignableFrom(((ModifiedNode) node).finalFields().get().getClass())) + .filter(node -> MetaDidObject.class.isAssignableFrom(((ModifiedNode) node).previousFields().get().getClass())) + .map(node -> (ModifiedNode) node) + .collect(Collectors.toList()); + + assertThat(modifiedNodes.size()).isEqualTo(1); + ModifiedNode modifiedNode = modifiedNodes.get(0); + assertThat(modifiedNode.previousFields()).isNotEmpty(); + MetaDidObject previousFields = modifiedNode.previousFields().get(); + assertThat(previousFields.didDocument()).isNotEmpty().isEqualTo(did.object().didDocument()); + assertThat(previousFields.uri()).isNotEmpty().isEqualTo(did.object().uri()); + assertThat(previousFields.data()).isEmpty(); + assertThat(previousFields.account()).isEmpty(); + assertThat(previousFields.flags()).isEqualTo(Flags.UNSET); + + assertThat(modifiedNode.previousFields()).isNotEmpty(); + MetaDidObject finalFields = modifiedNode.finalFields().get(); + assertThat(finalFields.didDocument()).isEmpty(); + assertThat(finalFields.uri()).isNotEmpty().isEqualTo(updateDid.uri()); + assertThat(finalFields.data()).isNotEmpty().isEqualTo(did.object().data()); + assertThat(finalFields.account()).isNotEmpty().get().isEqualTo(sourceAccountInfo.accountData().account()); + assertThat(finalFields.flags()).isEqualTo(Flags.UNSET); + + List accountObjects = this.getValidatedAccountObjects( + did.ownerKeyPair().publicKey().deriveAddress(), + DidObject.class + ); + assertThat(accountObjects.size()).isEqualTo(1); + DidObject didFromAccountObjects = accountObjects.get(0); + assertThat(didFromAccountObjects.account()).isEqualTo(sourceAccountInfo.accountData().account()); + assertThat(didFromAccountObjects.didDocument()).isEmpty(); + assertThat(didFromAccountObjects.uri()).isNotEmpty().isEqualTo(updateDid.uri()); + assertThat(didFromAccountObjects.data()).isNotEmpty().isEqualTo(did.object().data()); + assertThat(didFromAccountObjects.flags()).isEqualTo(did.object().flags()); + + LedgerEntryResult didFromLedgerEntryIndex = xrplClient.ledgerEntry( + LedgerEntryRequestParams.index( + modifiedNode.ledgerIndex(), + DidObject.class, + LedgerSpecifier.VALIDATED + ) + ); + + assertThat(didFromLedgerEntryIndex.node()).isEqualTo(didFromAccountObjects); + + LedgerEntryResult didFromLedgerEntry = xrplClient.ledgerEntry( + LedgerEntryRequestParams.did( + did.ownerKeyPair().publicKey().deriveAddress(), + LedgerSpecifier.VALIDATED + ) + ); + + assertThat(didFromLedgerEntry.node()).isEqualTo(didFromAccountObjects); + } + + @Test + void testCreateAndDeleteDid() throws JsonRpcClientErrorException, JsonProcessingException { + TestDid did = createNewDid(); + + AccountInfoResult sourceAccountInfo = this.getValidatedAccountInfo( + did.ownerKeyPair().publicKey().deriveAddress()); + + XrpCurrencyAmount fee = FeeUtils.computeNetworkFees(xrplClient.fee()).recommendedFee(); + DidDelete didDelete = DidDelete.builder() + .account(did.ownerKeyPair().publicKey().deriveAddress()) + .sequence(sourceAccountInfo.accountData().sequence()) + .fee(fee) + .lastLedgerSequence(sourceAccountInfo.ledgerIndexSafe().plus(UnsignedInteger.valueOf(4)).unsignedIntegerValue()) + .signingPublicKey(did.ownerKeyPair().publicKey()) + .build(); + + TransactionResult result = this.signSubmitAndWait(didDelete, did.ownerKeyPair(), DidDelete.class); + + logger.info(ObjectMapperFactory.create().writerWithDefaultPrettyPrinter().writeValueAsString(result)); + assertThat(result.metadata()).isNotEmpty(); + List> deletedNodes = result.metadata().get().affectedNodes().stream() + .filter(node -> node.ledgerEntryType().equals(MetaLedgerEntryType.DID)) + .filter(node -> DeletedNode.class.isAssignableFrom(node.getClass())) + .filter(node -> MetaDidObject.class.isAssignableFrom(((DeletedNode) node).finalFields().getClass())) + .map(node -> (DeletedNode) node) + .collect(Collectors.toList()); + + assertThat(deletedNodes.size()).isEqualTo(1); + DeletedNode deletedNode = deletedNodes.get(0); + MetaDidObject finalFields = deletedNode.finalFields(); + assertThat(finalFields.didDocument()).isNotEmpty().isEqualTo(did.object().didDocument()); + assertThat(finalFields.uri()).isNotEmpty().isEqualTo(did.object().uri()); + assertThat(finalFields.data()).isNotEmpty().isEqualTo(did.object().data()); + assertThat(finalFields.account()).isNotEmpty().get().isEqualTo(sourceAccountInfo.accountData().account()); + assertThat(finalFields.flags()).isEqualTo(Flags.UNSET); + + List accountObjects = this.getValidatedAccountObjects( + sourceAccountInfo.accountData().account(), + DidObject.class + ); + + assertThat(accountObjects).asList().isEmpty(); + } + + private TestDid createNewDid() throws JsonRpcClientErrorException, JsonProcessingException { + KeyPair sourceKeyPair = this.createRandomAccountEd25519(); + + AccountInfoResult sourceAccountInfo = this.scanForResult( + () -> this.getValidatedAccountInfo(sourceKeyPair.publicKey().deriveAddress()) + ); + + XrpCurrencyAmount fee = FeeUtils.computeNetworkFees(xrplClient.fee()).recommendedFee(); + + DidSet didSet = DidSet.builder() + .account(sourceAccountInfo.accountData().account()) + .sequence(sourceAccountInfo.accountData().sequence()) + .fee(fee) + .lastLedgerSequence(sourceAccountInfo.ledgerIndexSafe().unsignedIntegerValue().plus(UnsignedInteger.valueOf(4))) + .signingPublicKey(sourceKeyPair.publicKey()) + .data(DidData.of("617474657374")) + .uri(DidUri.of("6469645F6578616D706C65")) + .didDocument(DidDocument.of("646F63")) + .build(); + + TransactionResult didSetResult = this.signSubmitAndWait(didSet, sourceKeyPair, DidSet.class); + assertThat(didSetResult.metadata()).isNotEmpty(); + List> createdNodes = didSetResult.metadata().get().affectedNodes().stream() + .filter(node -> node.ledgerEntryType().equals(MetaLedgerEntryType.DID)) + .filter(node -> CreatedNode.class.isAssignableFrom(node.getClass())) + .filter(node -> MetaDidObject.class.isAssignableFrom(((CreatedNode) node).newFields().getClass())) + .map(node -> (CreatedNode) node) + .collect(Collectors.toList()); + + assertThat(createdNodes.size()).isEqualTo(1); + CreatedNode createdNode = createdNodes.get(0); + MetaDidObject createdDid = createdNode.newFields(); + assertThat(createdDid.didDocument()).isNotEmpty().isEqualTo(didSet.didDocument()); + assertThat(createdDid.uri()).isNotEmpty().isEqualTo(didSet.uri()); + assertThat(createdDid.data()).isNotEmpty().isEqualTo(didSet.data()); + assertThat(createdDid.account()).isNotEmpty().get().isEqualTo(sourceAccountInfo.accountData().account()); + assertThat(createdDid.flags()).isEqualTo(Flags.UNSET); + + List accountObjects = this.getValidatedAccountObjects( + sourceKeyPair.publicKey().deriveAddress(), + DidObject.class + ); + assertThat(accountObjects.size()).isEqualTo(1); + DidObject didFromAccountObjects = accountObjects.get(0); + assertThat(didFromAccountObjects.account()).isEqualTo(sourceAccountInfo.accountData().account()); + assertThat(didFromAccountObjects.didDocument()).isNotEmpty().isEqualTo(createdDid.didDocument()); + assertThat(didFromAccountObjects.uri()).isNotEmpty().isEqualTo(createdDid.uri()); + assertThat(didFromAccountObjects.data()).isNotEmpty().isEqualTo(createdDid.data()); + assertThat(didFromAccountObjects.flags()).isEqualTo(createdDid.flags()); + + LedgerEntryResult didFromLedgerEntryIndex = xrplClient.ledgerEntry( + LedgerEntryRequestParams.index( + createdNode.ledgerIndex(), + DidObject.class, + LedgerSpecifier.VALIDATED + ) + ); + + assertThat(didFromLedgerEntryIndex.node()).isEqualTo(didFromAccountObjects); + + LedgerEntryResult didFromLedgerEntry = xrplClient.ledgerEntry( + LedgerEntryRequestParams.did( + sourceKeyPair.publicKey().deriveAddress(), + LedgerSpecifier.VALIDATED + ) + ); + + assertThat(didFromLedgerEntry.node()).isEqualTo(didFromAccountObjects); + + return TestDid.builder() + .object(didFromLedgerEntry.node()) + .ownerKeyPair(sourceKeyPair) + .build(); + } + + @Immutable + @JsonSerialize(as = ImmutableTestDid.class) + @JsonDeserialize(as = ImmutableTestDid.class) + interface TestDid { + + /** + * Construct a {@code TestDid} builder. + * + * @return An {@link ImmutableTestDid.Builder}. + */ + static ImmutableTestDid.Builder builder() { + return ImmutableTestDid.builder(); + } + + KeyPair ownerKeyPair(); + + DidObject object(); + + } +} From 24bf9962d2a13c4a197837478a1bf35324a41fd5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Dec 2023 16:25:35 -0500 Subject: [PATCH 3/4] Bump ch.qos.logback:logback-classic from 1.4.11 to 1.4.12 (#504) Bumps [ch.qos.logback:logback-classic](https://github.com/qos-ch/logback) from 1.4.11 to 1.4.12. - [Commits](https://github.com/qos-ch/logback/compare/v_1.4.11...v_1.4.12) --- updated-dependencies: - dependency-name: ch.qos.logback:logback-classic dependency-type: direct:development ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: nkramer44 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index d75c33d72..82577648f 100644 --- a/pom.xml +++ b/pom.xml @@ -69,7 +69,7 @@ ch.qos.logback logback-classic - 1.4.11 + 1.4.12 org.assertj From 3c4a07692e8debf1aacb7ef924a47d8c8b2e07fa Mon Sep 17 00:00:00 2001 From: nkramer44 Date: Wed, 6 Dec 2023 13:23:27 -0500 Subject: [PATCH 4/4] Revert logback-classic to 1.3.X to work with JDK 8 (#509) revert logback-classic to 1.3.X to work with jdk 8 --- pom.xml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 82577648f..938e8b369 100644 --- a/pom.xml +++ b/pom.xml @@ -66,10 +66,11 @@ jackson-datatype-cryptoconditions ${cryptoconditions.version} + ch.qos.logback logback-classic - 1.4.12 + 1.3.8 org.assertj @@ -291,7 +292,7 @@ 1.0.4 2.14.2 12.3 - 1.7.36 + 2.0.7 5.10.0 32.1.1-jre