diff --git a/assets/src/main/java/haveno/asset/CardanoAddressValidator.java b/assets/src/main/java/haveno/asset/CardanoAddressValidator.java
new file mode 100644
index 0000000000..0b3546e4fb
--- /dev/null
+++ b/assets/src/main/java/haveno/asset/CardanoAddressValidator.java
@@ -0,0 +1,106 @@
+/*
+ * This file is part of Haveno.
+ *
+ * Haveno is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or (at
+ * your option) any later version.
+ *
+ * Haveno is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with Haveno. If not, see .
+ */
+
+package haveno.asset;
+
+/**
+ * Validates a Shelley-era mainnet Cardano address.
+ */
+public class CardanoAddressValidator extends RegexAddressValidator {
+
+ private static final String CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l";
+ private static final int BECH32_CONST = 1;
+ private static final int BECH32M_CONST = 0x2bc830a3;
+ private static final int MAX_LEN = 104; // bech32 / bech32m max for Cardano
+
+ public CardanoAddressValidator() {
+ super("^addr1[0-9a-z]{20,98}$");
+ }
+
+ public CardanoAddressValidator(String errorMessageI18nKey) {
+ super("^addr1[0-9a-z]{20,98}$", errorMessageI18nKey);
+ }
+
+ @Override
+ public AddressValidationResult validate(String address) {
+ if (!isValidShelleyMainnet(address)) {
+ return AddressValidationResult.invalidStructure();
+ }
+ return super.validate(address);
+ }
+
+ /**
+ * Checks if the given address is a valid Shelley-era mainnet Cardano address.
+ *
+ * This code is AI-generated and has been tested with a variety of addresses.
+ *
+ * @param addr the address to validate
+ * @return true if the address is valid, false otherwise
+ */
+ private static boolean isValidShelleyMainnet(String addr) {
+ if (addr == null) return false;
+ String lower = addr.toLowerCase();
+
+ // must start addr1 and not be absurdly long
+ if (!lower.startsWith("addr1") || lower.length() > MAX_LEN) return false;
+
+ int sep = lower.lastIndexOf('1');
+ if (sep < 1) return false; // no separator or empty HRP
+ String hrp = lower.substring(0, sep);
+ if (!"addr".equals(hrp)) return false; // mainnet only
+
+ String dataPart = lower.substring(sep + 1);
+ if (dataPart.length() < 6) return false; // checksum is 6 chars minimum
+
+ int[] data = new int[dataPart.length()];
+ for (int i = 0; i < dataPart.length(); i++) {
+ int v = CHARSET.indexOf(dataPart.charAt(i));
+ if (v == -1) return false;
+ data[i] = v;
+ }
+
+ int[] hrpExp = hrpExpand(hrp);
+ int[] combined = new int[hrpExp.length + data.length];
+ System.arraycopy(hrpExp, 0, combined, 0, hrpExp.length);
+ System.arraycopy(data, 0, combined, hrpExp.length, data.length);
+
+ int chk = polymod(combined);
+ return chk == BECH32_CONST || chk == BECH32M_CONST; // accept either legacy Bech32 (1) or Bech32m (0x2bc830a3)
+ }
+
+ private static int[] hrpExpand(String hrp) {
+ int[] ret = new int[hrp.length() * 2 + 1];
+ int idx = 0;
+ for (char c : hrp.toCharArray()) ret[idx++] = c >> 5;
+ ret[idx++] = 0;
+ for (char c : hrp.toCharArray()) ret[idx++] = c & 31;
+ return ret;
+ }
+
+ private static int polymod(int[] values) {
+ int chk = 1;
+ int[] GEN = {0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3};
+ for (int v : values) {
+ int b = chk >>> 25;
+ chk = ((chk & 0x1ffffff) << 5) ^ v;
+ for (int i = 0; i < 5; i++) {
+ if (((b >>> i) & 1) != 0) chk ^= GEN[i];
+ }
+ }
+ return chk;
+ }
+}
diff --git a/assets/src/main/java/haveno/asset/coins/Cardano.java b/assets/src/main/java/haveno/asset/coins/Cardano.java
new file mode 100644
index 0000000000..16f2563930
--- /dev/null
+++ b/assets/src/main/java/haveno/asset/coins/Cardano.java
@@ -0,0 +1,28 @@
+/*
+ * This file is part of Haveno.
+ *
+ * Haveno is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or (at
+ * your option) any later version.
+ *
+ * Haveno is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with Haveno. If not, see .
+ */
+
+package haveno.asset.coins;
+
+import haveno.asset.CardanoAddressValidator;
+import haveno.asset.Coin;
+
+public class Cardano extends Coin {
+
+ public Cardano() {
+ super("Cardano", "ADA", new CardanoAddressValidator());
+ }
+}
diff --git a/assets/src/main/resources/META-INF/services/haveno.asset.Asset b/assets/src/main/resources/META-INF/services/haveno.asset.Asset
index a10943e769..a68e22a8d5 100644
--- a/assets/src/main/resources/META-INF/services/haveno.asset.Asset
+++ b/assets/src/main/resources/META-INF/services/haveno.asset.Asset
@@ -4,6 +4,7 @@
# See https://haveno.exchange/list-asset for complete instructions.
haveno.asset.coins.Bitcoin$Mainnet
haveno.asset.coins.BitcoinCash
+haveno.asset.coins.Cardano
haveno.asset.coins.Ether
haveno.asset.coins.Litecoin
haveno.asset.coins.Monero
diff --git a/assets/src/test/java/haveno/asset/coins/CardanoTest.java b/assets/src/test/java/haveno/asset/coins/CardanoTest.java
new file mode 100644
index 0000000000..bae8141aba
--- /dev/null
+++ b/assets/src/test/java/haveno/asset/coins/CardanoTest.java
@@ -0,0 +1,42 @@
+/*
+ * This file is part of Haveno.
+ *
+ * Haveno is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or (at
+ * your option) any later version.
+ *
+ * Haveno is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with Haveno. If not, see .
+ */
+
+package haveno.asset.coins;
+
+import haveno.asset.AbstractAssetTest;
+import org.junit.jupiter.api.Test;
+
+public class CardanoTest extends AbstractAssetTest {
+
+ public CardanoTest() {
+ super(new Cardano());
+ }
+
+ @Test
+ public void testValidAddresses() {
+ assertValidAddress("addr1vpu5vlrf4xkxv2qpwngf6cjhtw542ayty80v8dyr49rf5eg0yu80w");
+ assertValidAddress("addr1q8gg2r3vf9zggn48g7m8vx62rwf6warcs4k7ej8mdzmqmesj30jz7psduyk6n4n2qrud2xlv9fgj53n6ds3t8cs4fvzs05yzmz");
+ }
+
+ @Test
+ public void testInvalidAddresses() {
+ assertInvalidAddress("addr1Q9r4y0gx0m4hd5s2u3pnj7ufc4s0ghqzj7u6czxyfks5cty5k5yq5qp6gmw5v7uqvx2g4kw6zjhx4l6fnhcey9lg9nys6v2mpu");
+ assertInvalidAddress("addr2q9r4y0gx0m4hd5s2u3pnj7ufc4s0ghqzj7u6czxyfks5cty5k5yq5qp6gmw5v7uqvx2g4kw6zjhx4l6fnhcey9lg9nys6v2mpu");
+ assertInvalidAddress("addr2vpu5vlrf4xkxv2qpwngf6cjhtw542ayty80v8dyr49rf5eg0yu80w");
+ assertInvalidAddress("Ae2tdPwUPEYxkYw5GrFyqb4Z9TzXo8f1WnWpPZP1sXrEn1pz2VU3CkJ8aTQ");
+ }
+}
diff --git a/core/src/main/java/haveno/core/locale/CurrencyUtil.java b/core/src/main/java/haveno/core/locale/CurrencyUtil.java
index e3b61eefaf..dc65c954ec 100644
--- a/core/src/main/java/haveno/core/locale/CurrencyUtil.java
+++ b/core/src/main/java/haveno/core/locale/CurrencyUtil.java
@@ -201,6 +201,7 @@ public class CurrencyUtil {
result.add(new CryptoCurrency("ETH", "Ether"));
result.add(new CryptoCurrency("LTC", "Litecoin"));
result.add(new CryptoCurrency("XRP", "Ripple"));
+ result.add(new CryptoCurrency("ADA", "Cardano"));
result.add(new CryptoCurrency("DAI-ERC20", "Dai Stablecoin"));
result.add(new CryptoCurrency("USDT-ERC20", "Tether USD"));
result.add(new CryptoCurrency("USDT-TRC20", "Tether USD"));
diff --git a/desktop/src/main/java/haveno/desktop/images.css b/desktop/src/main/java/haveno/desktop/images.css
index 549549605e..013a07e300 100644
--- a/desktop/src/main/java/haveno/desktop/images.css
+++ b/desktop/src/main/java/haveno/desktop/images.css
@@ -357,6 +357,10 @@
-fx-image: url("../../images/xrp_logo.png");
}
+#image-ada-logo {
+ -fx-image: url("../../images/ada_logo.png");
+}
+
#image-dark-mode-toggle {
-fx-image: url("../../images/dark_mode_toggle.png");
}
diff --git a/desktop/src/main/resources/images/ada_logo.png b/desktop/src/main/resources/images/ada_logo.png
new file mode 100644
index 0000000000..70d1166426
Binary files /dev/null and b/desktop/src/main/resources/images/ada_logo.png differ