diff --git a/assets/src/main/java/bisq/asset/GrinAddressValidator.java b/assets/src/main/java/bisq/asset/GrinAddressValidator.java
new file mode 100644
index 0000000000..71c8ec36f9
--- /dev/null
+++ b/assets/src/main/java/bisq/asset/GrinAddressValidator.java
@@ -0,0 +1,106 @@
+/*
+ * This file is part of Bisq.
+ *
+ * Bisq 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.
+ *
+ * Bisq 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 Bisq. If not, see .
+ */
+
+package bisq.asset;
+
+/**
+ * The supported "address" (better wallet URL) format is IP:port or the grinbox format.
+ *
+ * Here is the information from a conversation with the Grinbox developer regarding the Grinbox address format.
+ *
+ A Grinbox address is of the format: grinbox://@domain.com:port where everything besides is optional.
+ If no domain is specified, the default relay grinbox.io will be used.
+
+ The is a base58check encoded value (like in Bitcoin). For Grin mainnet, the first 2 bytes will be [1, 11] and
+ the following 33 bytes should be a valid secp256k1 compressed public key.
+
+ Some examples of valid addresses are:
+
+ gVvRNiuopubvxPrs1BzJdQjVdFAxmkLzMqiVJzUZ7ubznhdtNTGB
+ gVvUcSafSTD3YTSqgNf9ojEYWkz3zMZNfsjdpdb9en5mxc6gmja6
+ gVvk7rLBg3r3qoWYL3VsREnBbooT7nynxx5HtDvUWCJUaNCnddvY
+ grinbox://gVtWzX5NTLCBkyNV19QVdnLXue13heAVRD36sfkGD6xpqy7k7e4a
+ gVw9TWimGFXRjoDXWhWxeNQbu84ZpLkvnenkKvA5aJeDo31eM5tC@somerelay.com
+ grinbox://gVwjSsYW5vvHpK4AunJ5piKhhQTV6V3Jb818Uqs6PdC3SsB36AsA@somerelay.com:1220
+
+ Some examples of invalid addresses are:
+
+ gVuBJDKcWkhueMfBLAbFwV4ax55YXPeinWXdRME1Zi3eiC6sFNye (invalid checksum)
+ geWGCMQjxZMHG3EtTaRbR7rH9rE4DsmLfpm1iiZEa7HFKjjkgpf2 (wrong version bytes)
+ gVvddC2jYAfxTxnikcbTEQKLjhJZpqpBg39tXkwAKnD2Pys2mWiK (invalid public key)
+
+ We only add the basic validation without checksum, version byte and pubkey validation as that would require much more
+ effort. Any Grin developer is welcome to add that though!
+
+ */
+public class GrinAddressValidator implements AddressValidator {
+ // A Grin Wallet URL (address is not the correct term) can be in the form IP:port or a grinbox format.
+ // The grinbox has the format grinbox://@domain.com:port where everything beside the key is optional.
+
+
+ // Regex for IP validation borrowed from https://stackoverflow.com/questions/53497/regular-expression-that-matches-valid-ipv6-addresses
+ private static final String IPV4 = "((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])";
+ private static final String PORT = "((6553[0-5])|(655[0-2][0-9])|(65[0-4][0-9]{2})|(6[0-4][0-9]{3})|([1-5][0-9]{4})|([0-5]{0,5})|([0-9]{1,4}))$";
+ private static final String DOMAIN = "[a-zA-Z0-9][a-zA-Z0-9-]{1,61}[a-zA-Z0-9]\\.[a-zA-Z]{2,}$";
+ private static final String KEY = "[a-km-zA-HJ-NP-Z1-9]{52}$";
+
+ public GrinAddressValidator() {
+ }
+
+ @Override
+ public AddressValidationResult validate(String address) {
+ if (address == null || address.length() == 0)
+ return AddressValidationResult.invalidAddress("Address may not be empty");
+
+ if (address.matches("^" + IPV4 + ":" + PORT))
+ return AddressValidationResult.validAddress();
+
+
+ // We might have a grinbox address
+ String key;
+ String domain = null;
+ String port = null;
+ address = address.replace("grinbox://", "");
+ if (address.contains("@")) {
+ String[] keyAndDomain = address.split("@");
+ key = keyAndDomain[0];
+ if (keyAndDomain.length > 1) {
+ domain = keyAndDomain[1];
+ if (domain.contains(":")) {
+ String[] domainAndPort = domain.split(":");
+ domain = domainAndPort[0];
+ if (domainAndPort.length > 1)
+ port = domainAndPort[1];
+ }
+ }
+ } else {
+ key = address;
+ }
+
+ if (!key.matches("^" + KEY))
+ return AddressValidationResult.invalidAddress("Invalid key");
+
+ if (domain != null && !domain.matches("^" + DOMAIN))
+ return AddressValidationResult.invalidAddress("Invalid domain");
+
+ if (port != null && !port.matches("^" + PORT))
+ return AddressValidationResult.invalidAddress("Invalid port");
+
+ return AddressValidationResult.validAddress();
+
+ }
+}
diff --git a/assets/src/main/java/bisq/asset/coins/Grin.java b/assets/src/main/java/bisq/asset/coins/Grin.java
new file mode 100644
index 0000000000..b934077801
--- /dev/null
+++ b/assets/src/main/java/bisq/asset/coins/Grin.java
@@ -0,0 +1,28 @@
+/*
+ * This file is part of Bisq.
+ *
+ * Bisq 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.
+ *
+ * Bisq 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 Bisq. If not, see .
+ */
+
+package bisq.asset.coins;
+
+import bisq.asset.Coin;
+import bisq.asset.GrinAddressValidator;
+
+public class Grin extends Coin {
+
+ public Grin() {
+ super("Grin", "GRIN", new GrinAddressValidator());
+ }
+}
diff --git a/assets/src/main/resources/META-INF/services/bisq.asset.Asset b/assets/src/main/resources/META-INF/services/bisq.asset.Asset
index 760f5ada65..ab26b4df1a 100644
--- a/assets/src/main/resources/META-INF/services/bisq.asset.Asset
+++ b/assets/src/main/resources/META-INF/services/bisq.asset.Asset
@@ -25,6 +25,7 @@ bisq.asset.coins.Dragonglass
bisq.asset.coins.Ether
bisq.asset.coins.EtherClassic
bisq.asset.coins.GambleCoin
+bisq.asset.coins.Grin
bisq.asset.coins.FourtyTwo
bisq.asset.coins.Gridcoin
bisq.asset.coins.Horizen
diff --git a/assets/src/test/java/bisq/asset/coins/GrinTest.java b/assets/src/test/java/bisq/asset/coins/GrinTest.java
new file mode 100644
index 0000000000..22851f78bb
--- /dev/null
+++ b/assets/src/test/java/bisq/asset/coins/GrinTest.java
@@ -0,0 +1,76 @@
+/*
+ * This file is part of Bisq.
+ *
+ * Bisq 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.
+ *
+ * Bisq 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 Bisq. If not, see .
+ */
+
+package bisq.asset.coins;
+
+import bisq.asset.AbstractAssetTest;
+
+import org.junit.Test;
+
+public class GrinTest extends AbstractAssetTest {
+
+ public GrinTest() {
+ super(new Grin());
+ }
+
+ @Test
+ public void testValidAddresses() {
+ assertValidAddress("0.0.0.0:8080");
+ assertValidAddress("173.194.34.134:8080");
+ assertValidAddress("127.0.0.1:8080");
+ assertValidAddress("192.168.0.1:8080");
+ assertValidAddress("18.101.25.153:8080");
+ assertValidAddress("173.194.34.134:1");
+ assertValidAddress("173.194.34.134:11");
+ assertValidAddress("173.194.34.134:1111");
+ assertValidAddress("173.194.34.134:65535");
+
+ // grinbox
+ assertValidAddress("gVvk7rLBg3r3qoWYL3VsREnBbooT7nynxx5HtDvUWCJUaNCnddvY");
+ assertValidAddress("grinbox://gVtWzX5NTLCBkyNV19QVdnLXue13heAVRD36sfkGD6xpqy7k7e4a");
+ assertValidAddress("gVw9TWimGFXRjoDXWhWxeNQbu84ZpLkvnenkKvA5aJeDo31eM5tC@somerelay.com");
+ assertValidAddress("gVw9TWimGFXRjoDXWhWxeNQbu84ZpLkvnenkKvA5aJeDo31eM5tC@somerelay.com:1220");
+ assertValidAddress("grinbox://gVwjSsYW5vvHpK4AunJ5piKhhQTV6V3Jb818Uqs6PdC3SsB36AsA@somerelay.com");
+ assertValidAddress("grinbox://gVwjSsYW5vvHpK4AunJ5piKhhQTV6V3Jb818Uqs6PdC3SsB36AsA@somerelay.com:1220");
+ }
+
+ @Test
+ public void testInvalidAddresses() {
+ assertInvalidAddress("google.com");
+ assertInvalidAddress("100.100.100.100");
+ assertInvalidAddress(".100.100.100.100:1222");
+ assertInvalidAddress("100..100.100.100:1222.");
+ assertInvalidAddress("100.100.100.100.:1222");
+ assertInvalidAddress("999.999.999.999:1222");
+ assertInvalidAddress("256.256.256.256:1222");
+ assertInvalidAddress("256.100.100.100.100:1222");
+ assertInvalidAddress("123.123.123:1222");
+ assertInvalidAddress("http://123.123.123:1222");
+ assertInvalidAddress("1000.2.3.4:1222");
+ assertInvalidAddress("999.2.3.4:1222");
+ // too large port
+ assertInvalidAddress("173.194.34.134:65536");
+
+ assertInvalidAddress("gVvk7rLBg3r3qoWYL3VsREnBbooT7nynxx5HtDvUWCJUaNCnddvY1111");
+ assertInvalidAddress("grinbox:/gVtWzX5NTLCBkyNV19QVdnLXue13heAVRD36sfkGD6xpqy7k7e4a");
+ assertInvalidAddress("gVw9TWimGFXRjoDXWhWxeNQbu84ZpLkvnenkKvA5aJeDo31eM5tC@somerelay.com.");
+ assertInvalidAddress("gVw9TWimGFXRjoDXWhWxeNQbu84ZpLkvnenkKvA5aJeDo31eM5tC@somerelay.com:1220a");
+ assertInvalidAddress("grinbox://gVwjSsYW5vvHpK4AunJ5piKhhQTV6V3Jb818Uqs6PdC3SsB36AsAsomerelay.com");
+ assertInvalidAddress("grinbox://gVwjSsYW5vvHpK4AunJ5piKhhQTV6V3Jb818Uqs6PdC3SsB36AsA@somerelay.com1220");
+
+ }
+}