diff --git a/assets/src/main/java/haveno/asset/TronAddressValidator.java b/assets/src/main/java/haveno/asset/TronAddressValidator.java new file mode 100644 index 0000000000..6125975621 --- /dev/null +++ b/assets/src/main/java/haveno/asset/TronAddressValidator.java @@ -0,0 +1,104 @@ +/* + * 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; + +import java.math.BigInteger; +import java.security.MessageDigest; +import java.util.Arrays; + +/** + * Validates a Tron address. + */ +public class TronAddressValidator implements AddressValidator { + + private static final String BASE58_ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"; + private static final byte MAINNET_PREFIX = 0x41; + + public TronAddressValidator() { + } + + @Override + public AddressValidationResult validate(String address) { + if (!isValidTronAddress(address)) { + return AddressValidationResult.invalidStructure(); + } + return AddressValidationResult.validAddress(); + } + + /** + * Checks if the given address is a valid Solana 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 isValidTronAddress(String address) { + if (address == null || address.length() != 34) return false; + + byte[] decoded = decodeBase58(address); + if (decoded == null || decoded.length != 25) return false; // 21 bytes data + 4 bytes checksum + + // Check checksum + byte[] data = Arrays.copyOfRange(decoded, 0, 21); + byte[] checksum = Arrays.copyOfRange(decoded, 21, 25); + byte[] calculatedChecksum = Arrays.copyOfRange(doubleSHA256(data), 0, 4); + + if (!Arrays.equals(checksum, calculatedChecksum)) return false; + + // Check mainnet prefix + return data[0] == MAINNET_PREFIX; + } + + private static byte[] decodeBase58(String input) { + BigInteger num = BigInteger.ZERO; + BigInteger base = BigInteger.valueOf(58); + + for (char c : input.toCharArray()) { + int digit = BASE58_ALPHABET.indexOf(c); + if (digit < 0) return null; + num = num.multiply(base).add(BigInteger.valueOf(digit)); + } + + // Convert BigInteger to byte array + byte[] bytes = num.toByteArray(); + if (bytes.length > 1 && bytes[0] == 0) { + bytes = Arrays.copyOfRange(bytes, 1, bytes.length); + } + + // Add leading zero bytes for '1's + int leadingZeros = 0; + for (char c : input.toCharArray()) { + if (c == '1') leadingZeros++; + else break; + } + + byte[] result = new byte[leadingZeros + bytes.length]; + System.arraycopy(bytes, 0, result, leadingZeros, bytes.length); + return result; + } + + private static byte[] doubleSHA256(byte[] data) { + try { + MessageDigest sha256 = MessageDigest.getInstance("SHA-256"); + return sha256.digest(sha256.digest(data)); + } catch (Exception e) { + throw new RuntimeException(e); + } + } +} diff --git a/assets/src/main/java/haveno/asset/coins/Tron.java b/assets/src/main/java/haveno/asset/coins/Tron.java new file mode 100644 index 0000000000..d358834eeb --- /dev/null +++ b/assets/src/main/java/haveno/asset/coins/Tron.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.Coin; +import haveno.asset.TronAddressValidator; + +public class Tron extends Coin { + + public Tron() { + super("Tron", "TRX", new TronAddressValidator()); + } +} 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 3125ac64aa..a42480de1d 100644 --- a/assets/src/main/resources/META-INF/services/haveno.asset.Asset +++ b/assets/src/main/resources/META-INF/services/haveno.asset.Asset @@ -10,6 +10,7 @@ haveno.asset.coins.Litecoin haveno.asset.coins.Monero haveno.asset.coins.Ripple haveno.asset.coins.Solana +haveno.asset.coins.Tron haveno.asset.tokens.TetherUSDERC20 haveno.asset.tokens.TetherUSDTRC20 haveno.asset.tokens.USDCoinERC20 diff --git a/assets/src/test/java/haveno/asset/coins/TronTest.java b/assets/src/test/java/haveno/asset/coins/TronTest.java new file mode 100644 index 0000000000..dd70d2edea --- /dev/null +++ b/assets/src/test/java/haveno/asset/coins/TronTest.java @@ -0,0 +1,45 @@ +/* + * 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 TronTest extends AbstractAssetTest { + + public TronTest() { + super(new Tron()); + } + + @Test + public void testValidAddresses() { + assertValidAddress("TRjE1H8dxypKM1NZRdysbs9wo7huR4bdNz"); + assertValidAddress("THdUXD3mZqT5aMnPQMtBSJX9ANGjaeUwQK"); + assertValidAddress("THUE6WTLaEGytFyuGJQUcKc3r245UKypoi"); + assertValidAddress("TH7vVF9RTMXM9x7ZnPnbNcEph734hpu8cf"); + assertValidAddress("TJNtFduS4oebw3jgGKCYmgSpTdyPieb6Ha"); + } + + @Test + public void testInvalidAddresses() { + assertInvalidAddress("TJRyWwFs9wTFGZg3L8nL62xwP9iK8QdK9R"); + assertInvalidAddress("TJRyWwFs9wTFGZg3L8nL62xwP9iK8QdK9X"); + assertInvalidAddress("1JRyWwFs9wTFGZg3L8nL62xwP9iK8QdK9R"); + assertInvalidAddress("TGzz8gjYiYRqpfmDwnLxfgPuLVNmpCswVo"); + } +} diff --git a/core/src/main/java/haveno/core/locale/CurrencyUtil.java b/core/src/main/java/haveno/core/locale/CurrencyUtil.java index 8d329324fe..58e7ab422b 100644 --- a/core/src/main/java/haveno/core/locale/CurrencyUtil.java +++ b/core/src/main/java/haveno/core/locale/CurrencyUtil.java @@ -203,6 +203,7 @@ public class CurrencyUtil { result.add(new CryptoCurrency("XRP", "Ripple")); result.add(new CryptoCurrency("ADA", "Cardano")); result.add(new CryptoCurrency("SOL", "Solana")); + result.add(new CryptoCurrency("TRX", "Tron")); 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 cc388612b3..70ca246cd6 100644 --- a/desktop/src/main/java/haveno/desktop/images.css +++ b/desktop/src/main/java/haveno/desktop/images.css @@ -365,6 +365,10 @@ -fx-image: url("../../images/sol_logo.png"); } +#image-trx-logo { + -fx-image: url("../../images/trx_logo.png"); +} + #image-dark-mode-toggle { -fx-image: url("../../images/dark_mode_toggle.png"); } diff --git a/desktop/src/main/resources/images/trx_logo.png b/desktop/src/main/resources/images/trx_logo.png new file mode 100644 index 0000000000..bb2ab57421 Binary files /dev/null and b/desktop/src/main/resources/images/trx_logo.png differ