Skip to content

Commit 1968480

Browse files
committed
added support for pathcurrency
1 parent 121b7fc commit 1968480

File tree

4 files changed

+250
-13
lines changed

4 files changed

+250
-13
lines changed

xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/client/path/PathCurrency.java

Lines changed: 56 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@
99
* Licensed under the Apache License, Version 2.0 (the "License");
1010
* you may not use this file except in compliance with the License.
1111
* You may obtain a copy of the License at
12-
*
12+
*
1313
* http://www.apache.org/licenses/LICENSE-2.0
14-
*
14+
*
1515
* Unless required by applicable law or agreed to in writing, software
1616
* distributed under the License is distributed on an "AS IS" BASIS,
1717
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -23,17 +23,22 @@
2323
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
2424
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
2525
import org.immutables.value.Value;
26+
import org.xrpl.xrpl4j.model.jackson.modules.PathCurrencyDeserializer;
27+
import org.xrpl.xrpl4j.model.jackson.modules.PathCurrencySerializer;
28+
import org.xrpl.xrpl4j.model.ledger.CurrencyIssue;
29+
import org.xrpl.xrpl4j.model.ledger.Issue;
2630
import org.xrpl.xrpl4j.model.transactions.Address;
2731

28-
import java.util.Optional;
29-
3032
/**
3133
* Represents a currency that an account holds on the XRPL, which can be used to specify the source currencies in
3234
* {@link RipplePathFindRequestParams}.
35+
*
36+
* <p>This class wraps an {@link Issue} to support both traditional currencies (XRP and IOUs) and MPTokens.
37+
* For traditional currencies, use {@link CurrencyIssue}. For MPTokens, use {@link org.xrpl.xrpl4j.model.ledger.MptIssue}.</p>
3338
*/
3439
@Value.Immutable
35-
@JsonSerialize(as = ImmutablePathCurrency.class)
36-
@JsonDeserialize(as = ImmutablePathCurrency.class)
40+
@JsonSerialize(as = ImmutablePathCurrency.class, using = PathCurrencySerializer.class)
41+
@JsonDeserialize(as = ImmutablePathCurrency.class, using = PathCurrencyDeserializer.class)
3742
public interface PathCurrency {
3843

3944
/**
@@ -48,29 +53,67 @@ static ImmutablePathCurrency.Builder builder() {
4853
/**
4954
* Construct a {@link PathCurrency} with the specified currency code and no issuer.
5055
*
56+
* <p>This is a convenience method for creating a {@link PathCurrency} for XRP or a currency without
57+
* specifying an issuer. For IOUs with an issuer or MPTokens, use {@link #of(Issue)} instead.</p>
58+
*
5159
* @param currency A {@link String} of either a 3 character currency code, or a 40 character hexadecimal encoded
5260
* currency code value.
5361
*
5462
* @return A new {@link PathCurrency}.
5563
*/
5664
static PathCurrency of(String currency) {
5765
return builder()
58-
.currency(currency)
66+
.issue(CurrencyIssue.builder().currency(currency).build())
5967
.build();
6068
}
6169

6270
/**
63-
* Either a 3 character currency code, or a 40 character hexadecimal encoded currency code value.
71+
* Construct a {@link PathCurrency} from an {@link Issue}.
72+
*
73+
* <p>This method supports both {@link CurrencyIssue} (for XRP and IOUs) and
74+
* {@link org.xrpl.xrpl4j.model.ledger.MptIssue} (for MPTokens).</p>
6475
*
65-
* @return A {@link String} containing the currency code.
76+
* @param issue An {@link Issue} representing the currency.
77+
*
78+
* @return A new {@link PathCurrency}.
6679
*/
67-
String currency();
80+
static PathCurrency of(Issue issue) {
81+
return builder()
82+
.issue(issue)
83+
.build();
84+
}
6885

6986
/**
70-
* The {@link Address} of the issuer of the currency.
87+
* Construct a {@link PathCurrency} with the specified currency code and issuer.
88+
*
89+
* <p>This is a convenience method for creating a {@link PathCurrency} for an IOU with an issuer.
90+
* For MPTokens, use {@link #of(Issue)} with an {@link org.xrpl.xrpl4j.model.ledger.MptIssue} instead.</p>
91+
*
92+
* @param currency A {@link String} of either a 3 character currency code, or a 40 character hexadecimal encoded
93+
* currency code value.
94+
* @param issuer The {@link Address} of the issuer of the currency.
95+
*
96+
* @return A new {@link PathCurrency}.
97+
*/
98+
static PathCurrency of(String currency, Address issuer) {
99+
return builder()
100+
.issue(CurrencyIssue.builder().currency(currency).issuer(issuer).build())
101+
.build();
102+
}
103+
104+
/**
105+
* The asset that this path currency represents. This can be either a {@link CurrencyIssue}
106+
* (for XRP or IOUs) or an {@link org.xrpl.xrpl4j.model.ledger.MptIssue} (for MPTokens).
107+
*
108+
* <p>The {@link Issue} fields will be unwrapped and serialized directly into the PathCurrency JSON object
109+
* by the custom {@link PathCurrencySerializer}:
110+
* <ul>
111+
* <li>For {@link CurrencyIssue}: {@code {"currency": "...", "issuer": "..."}}</li>
112+
* <li>For {@link org.xrpl.xrpl4j.model.ledger.MptIssue}: {@code {"mpt_issuance_id": "..."}}</li>
113+
* </ul>
71114
*
72-
* @return The optionally-present {@link Address} of the issuer account.
115+
* @return An {@link Issue}.
73116
*/
74-
Optional<Address> issuer();
117+
Issue issue();
75118

76119
}
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
package org.xrpl.xrpl4j.model.jackson.modules;
2+
3+
/*-
4+
* ========================LICENSE_START=================================
5+
* xrpl4j :: model
6+
* %%
7+
* Copyright (C) 2020 - 2022 XRPL Foundation and its contributors
8+
* %%
9+
* Licensed under the Apache License, Version 2.0 (the "License");
10+
* you may not use this file except in compliance with the License.
11+
* You may obtain a copy of the License at
12+
*
13+
* http://www.apache.org/licenses/LICENSE-2.0
14+
*
15+
* Unless required by applicable law or agreed to in writing, software
16+
* distributed under the License is distributed on an "AS IS" BASIS,
17+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18+
* See the License for the specific language governing permissions and
19+
* limitations under the License.
20+
* =========================LICENSE_END==================================
21+
*/
22+
23+
import com.fasterxml.jackson.core.JsonParser;
24+
import com.fasterxml.jackson.databind.DeserializationContext;
25+
import com.fasterxml.jackson.databind.JsonNode;
26+
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
27+
import org.xrpl.xrpl4j.model.client.path.PathCurrency;
28+
import org.xrpl.xrpl4j.model.ledger.CurrencyIssue;
29+
import org.xrpl.xrpl4j.model.ledger.ImmutableCurrencyIssue;
30+
import org.xrpl.xrpl4j.model.ledger.MptIssue;
31+
import org.xrpl.xrpl4j.model.transactions.Address;
32+
import org.xrpl.xrpl4j.model.transactions.MpTokenIssuanceId;
33+
34+
import java.io.IOException;
35+
36+
/**
37+
* Custom Jackson deserializer for {@link PathCurrency}.
38+
*
39+
* <p>This deserializer handles the polymorphic nature of PathCurrency, which can wrap either
40+
* a {@link CurrencyIssue} (for XRP or IOUs) or an {@link MptIssue} (for MPTokens).</p>
41+
*/
42+
public class PathCurrencyDeserializer extends StdDeserializer<PathCurrency> {
43+
44+
/**
45+
* No-args constructor.
46+
*/
47+
public PathCurrencyDeserializer() {
48+
super(PathCurrency.class);
49+
}
50+
51+
@Override
52+
public PathCurrency deserialize(JsonParser jsonParser, DeserializationContext ctxt) throws IOException {
53+
JsonNode node = jsonParser.getCodec().readTree(jsonParser);
54+
55+
// Check if it's an MPT (has mpt_issuance_id field)
56+
if (node.has("mpt_issuance_id")) {
57+
return PathCurrency.of(
58+
MptIssue.of(MpTokenIssuanceId.of(node.get("mpt_issuance_id").asText()))
59+
);
60+
}
61+
62+
// Otherwise, it's a currency issue (has currency field, optionally issuer)
63+
if (node.has("currency")) {
64+
ImmutableCurrencyIssue.Builder builder = CurrencyIssue.builder()
65+
.currency(node.get("currency").asText());
66+
67+
if (node.has("issuer")) {
68+
builder.issuer(Address.of(node.get("issuer").asText()));
69+
}
70+
71+
return PathCurrency.of(builder.build());
72+
}
73+
74+
throw new IllegalArgumentException(
75+
"PathCurrency JSON must have either 'mpt_issuance_id' or 'currency' field"
76+
);
77+
}
78+
}
79+
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
package org.xrpl.xrpl4j.model.jackson.modules;
2+
3+
/*-
4+
* ========================LICENSE_START=================================
5+
* xrpl4j :: model
6+
* %%
7+
* Copyright (C) 2020 - 2022 XRPL Foundation and its contributors
8+
* %%
9+
* Licensed under the Apache License, Version 2.0 (the "License");
10+
* you may not use this file except in compliance with the License.
11+
* You may obtain a copy of the License at
12+
*
13+
* http://www.apache.org/licenses/LICENSE-2.0
14+
*
15+
* Unless required by applicable law or agreed to in writing, software
16+
* distributed under the License is distributed on an "AS IS" BASIS,
17+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18+
* See the License for the specific language governing permissions and
19+
* limitations under the License.
20+
* =========================LICENSE_END==================================
21+
*/
22+
23+
import com.fasterxml.jackson.core.JsonGenerator;
24+
import com.fasterxml.jackson.databind.SerializerProvider;
25+
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
26+
import org.xrpl.xrpl4j.model.client.path.PathCurrency;
27+
import org.xrpl.xrpl4j.model.ledger.CurrencyIssue;
28+
import org.xrpl.xrpl4j.model.ledger.MptIssue;
29+
30+
import java.io.IOException;
31+
32+
/**
33+
* Custom Jackson serializer for {@link PathCurrency}.
34+
*/
35+
public class PathCurrencySerializer extends StdSerializer<PathCurrency> {
36+
37+
public PathCurrencySerializer() {
38+
super(PathCurrency.class);
39+
}
40+
41+
@Override
42+
public void serialize(
43+
PathCurrency pathCurrency,
44+
JsonGenerator gen,
45+
SerializerProvider provider
46+
) throws IOException {
47+
gen.writeStartObject();
48+
49+
pathCurrency.issue().handle(
50+
currencyIssue -> {
51+
try {
52+
gen.writeStringField("currency", currencyIssue.currency());
53+
if (currencyIssue.issuer().isPresent()) {
54+
gen.writeStringField("issuer", currencyIssue.issuer().get().value());
55+
}
56+
} catch (IOException e) {
57+
throw new RuntimeException("Error serializing CurrencyIssue", e);
58+
}
59+
},
60+
mptIssue -> {
61+
try {
62+
gen.writeStringField("mpt_issuance_id", mptIssue.mptIssuanceId().value());
63+
} catch (IOException e) {
64+
throw new RuntimeException("Error serializing MptIssue", e);
65+
}
66+
}
67+
);
68+
69+
gen.writeEndObject();
70+
}
71+
}
72+

xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/client/path/RipplePathFindRequestParamsJsonTests.java

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,12 @@
2525
import org.junit.jupiter.api.Test;
2626
import org.xrpl.xrpl4j.model.AbstractJsonTest;
2727
import org.xrpl.xrpl4j.model.client.common.LedgerSpecifier;
28+
import org.xrpl.xrpl4j.model.ledger.MptIssue;
2829
import org.xrpl.xrpl4j.model.transactions.Address;
2930
import org.xrpl.xrpl4j.model.transactions.Hash256;
3031
import org.xrpl.xrpl4j.model.transactions.IssuedCurrencyAmount;
32+
import org.xrpl.xrpl4j.model.transactions.MpTokenIssuanceId;
33+
import org.xrpl.xrpl4j.model.transactions.MptCurrencyAmount;
3134

3235
public class RipplePathFindRequestParamsJsonTests extends AbstractJsonTest {
3336

@@ -71,4 +74,44 @@ public void testJson() throws JsonProcessingException, JSONException {
7174

7275
assertCanSerializeAndDeserialize(params, json);
7376
}
77+
78+
@Test
79+
public void testJsonWithMpt() throws JsonProcessingException, JSONException {
80+
MpTokenIssuanceId mptId1 = MpTokenIssuanceId.of("00000002430427B80BD2D09D36B70B969E12801065F22308");
81+
MpTokenIssuanceId mptId2 = MpTokenIssuanceId.of("00000003430427B80BD2D09D36B70B969E12801065F22308");
82+
83+
RipplePathFindRequestParams params = RipplePathFindRequestParams.builder()
84+
.destinationAccount(Address.of("r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59"))
85+
.destinationAmount(MptCurrencyAmount.builder()
86+
.mptIssuanceId(mptId1)
87+
.value("100")
88+
.build())
89+
.sourceAccount(Address.of("r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59"))
90+
.addSourceCurrencies(
91+
PathCurrency.of(MptIssue.of(mptId1)),
92+
PathCurrency.of(MptIssue.of(mptId2))
93+
)
94+
.ledgerSpecifier(LedgerSpecifier.CURRENT)
95+
.build();
96+
97+
String json = "{" +
98+
" \"ledger_index\": \"current\"," +
99+
" \"destination_account\": \"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59\"," +
100+
" \"destination_amount\": {" +
101+
" \"mpt_issuance_id\": \"00000002430427B80BD2D09D36B70B969E12801065F22308\"," +
102+
" \"value\": \"100\"" +
103+
" }," +
104+
" \"source_account\": \"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59\"," +
105+
" \"source_currencies\": [" +
106+
" {" +
107+
" \"mpt_issuance_id\": \"00000002430427B80BD2D09D36B70B969E12801065F22308\"" +
108+
" }," +
109+
" {" +
110+
" \"mpt_issuance_id\": \"00000003430427B80BD2D09D36B70B969E12801065F22308\"" +
111+
" }" +
112+
" ]" +
113+
"}";
114+
115+
assertCanSerializeAndDeserialize(params, json);
116+
}
74117
}

0 commit comments

Comments
 (0)