diff --git a/CHANGELOG.md b/CHANGELOG.md
index 40de1b867..1ed158fc0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,9 @@
## Pending
+### Update
+- fix: `Asset.compareTo` orders same-code assets by the issuers' raw Ed25519 key bytes instead of their strkey text, matching the order the network requires.
+
## 6.0.0
**This release adds support for Protocol 30.**
diff --git a/src/main/java/org/stellar/sdk/Asset.java b/src/main/java/org/stellar/sdk/Asset.java
index 724bc769b..35432c14e 100644
--- a/src/main/java/org/stellar/sdk/Asset.java
+++ b/src/main/java/org/stellar/sdk/Asset.java
@@ -100,7 +100,8 @@ public static Asset fromXdr(org.stellar.sdk.xdr.Asset xdr) {
*
* - First compare the type (eg. native before alphanum4 before alphanum12).
*
- If the types are equal, compare the assets codes.
- *
- If the asset codes are equal, compare the issuers.
+ *
- If the asset codes are equal, compare the issuers by their raw Ed25519 public key bytes,
+ * as the network requires, not by their strkey text.
*
*
* @param other the object to be compared.
diff --git a/src/main/java/org/stellar/sdk/AssetTypeCreditAlphaNum12.java b/src/main/java/org/stellar/sdk/AssetTypeCreditAlphaNum12.java
index 9db076f3a..f4c0e0b7c 100644
--- a/src/main/java/org/stellar/sdk/AssetTypeCreditAlphaNum12.java
+++ b/src/main/java/org/stellar/sdk/AssetTypeCreditAlphaNum12.java
@@ -63,6 +63,8 @@ public int compareTo(@NonNull Asset other) {
if (!this.getCode().equals(o.getCode())) {
return this.getCode().compareTo(o.getCode());
}
- return this.getIssuer().compareTo(o.getIssuer());
+ return Util.compareBytesUnsigned(
+ StrKey.decodeEd25519PublicKey(this.getIssuer()),
+ StrKey.decodeEd25519PublicKey(o.getIssuer()));
}
}
diff --git a/src/main/java/org/stellar/sdk/AssetTypeCreditAlphaNum4.java b/src/main/java/org/stellar/sdk/AssetTypeCreditAlphaNum4.java
index 018876f61..a79464302 100644
--- a/src/main/java/org/stellar/sdk/AssetTypeCreditAlphaNum4.java
+++ b/src/main/java/org/stellar/sdk/AssetTypeCreditAlphaNum4.java
@@ -66,6 +66,8 @@ public int compareTo(@NonNull Asset other) {
return this.getCode().compareTo(o.getCode());
}
- return this.getIssuer().compareTo(o.getIssuer());
+ return Util.compareBytesUnsigned(
+ StrKey.decodeEd25519PublicKey(this.getIssuer()),
+ StrKey.decodeEd25519PublicKey(o.getIssuer()));
}
}
diff --git a/src/test/java/org/stellar/sdk/LiquidityPoolTest.java b/src/test/java/org/stellar/sdk/LiquidityPoolTest.java
index 7f2a57529..b966c8b5f 100644
--- a/src/test/java/org/stellar/sdk/LiquidityPoolTest.java
+++ b/src/test/java/org/stellar/sdk/LiquidityPoolTest.java
@@ -1,6 +1,8 @@
package org.stellar.sdk;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.io.IOException;
@@ -130,6 +132,43 @@ public void testLexicographicOrderAssetIssuer() {
}
}
+ @Test
+ public void testIssuersAreOrderedByRawKeyBytes() {
+ // Issuers of testnet pool c2a730127d006897b021d6fcb279da68b4f65f9b6a408c74eb2cdd85ea862d5e.
+ // GDRQ sorts after GD32 as strkey text, but before it as raw key bytes (e305... < f7a4...).
+ for (String code : new String[] {"USD", "BANANA"}) {
+ Asset assetA =
+ Asset.create(code + ":GDRQL6EF772CANMYX55HXMU463OLAX46ZRCPCWS4KVL66ZS4BYA7OYDL");
+ Asset assetB =
+ Asset.create(code + ":GD32JQHFWLIROCNQ564GLCB2M7T4GMQAP5UKHMVT64OQISWL2LHYK2HO");
+ assertTrue(assetA.compareTo(assetB) < 0);
+ assertTrue(assetB.compareTo(assetA) > 0);
+ new LiquidityPool(assetA, assetB);
+ assertThrows(IllegalArgumentException.class, () -> new LiquidityPool(assetB, assetA));
+ }
+ }
+
+ @Test
+ public void testLiquidityPoolIdWithIssuersOrderedByRawKeyBytes() {
+ Asset assetA = Asset.create("USD:GDRQL6EF772CANMYX55HXMU463OLAX46ZRCPCWS4KVL66ZS4BYA7OYDL");
+ Asset assetB = Asset.create("USD:GD32JQHFWLIROCNQ564GLCB2M7T4GMQAP5UKHMVT64OQISWL2LHYK2HO");
+ assertEquals(
+ "c2a730127d006897b021d6fcb279da68b4f65f9b6a408c74eb2cdd85ea862d5e",
+ new LiquidityPool(assetA, assetB).getLiquidityPoolId());
+ }
+
+ @Test
+ public void testDecodeTransactionWithIssuersOrderedByRawKeyBytes() throws IOException {
+ // Testnet transaction 0126748091930959a3aefac9296e1773b947f964a9b73f37ce856c4df32d346f
+ String envelopeXdr =
+ "AAAAAgAAAAADeR2SwEvKvthWOdthGC/c1Bxym7fafJmtjj5EyvmvxAAAASwAVEAXAAAAAQAAAAAAAAAAAAAAAwAAAAAAAAAGAAAAAVVTRAAAAAAA4wX4hf/0IDWYv3p7spz23LBfnsxE8VpcVVfvZlwOAfd//////////wAAAAAAAAAGAAAAAVVTRAAAAAAA96TA5bLRFwmw77hliDpn58MyAH9oo7Kz9x0ESsvSz4V//////////wAAAAAAAAAGAAAAAwAAAAAAAAABVVNEAAAAAADjBfiF//QgNZi/enuynPbcsF+ezETxWlxVV+9mXA4B9wAAAAFVU0QAAAAAAPekwOWy0RcJsO+4ZYg6Z+fDMgB/aKOys/cdBErL0s+FAAAAHn//////////AAAAAAAAAAHK+a/EAAAAQMtA5QpAy6I3ILVmPZcRA3SJhGo4CiMT7K7dXQt7Qo1Prip/OedNocMY2QloSRle4hYvhJilIiOyoD85l3+WrQM=";
+ AbstractTransaction transaction =
+ AbstractTransaction.fromEnvelopeXdr(envelopeXdr, Network.TESTNET);
+ assertEquals(envelopeXdr, transaction.toEnvelopeXdrBase64());
+ assertEquals(
+ "0126748091930959a3aefac9296e1773b947f964a9b73f37ce856c4df32d346f", transaction.hashHex());
+ }
+
@Value
static class OrderTestCase {
Asset assetA;