support Solana (SOL)

This commit is contained in:
woodser 2025-08-10 16:46:00 -04:00 committed by woodser
parent 0dc67f06c4
commit b0446c637f
7 changed files with 174 additions and 0 deletions

View file

@ -0,0 +1,94 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
package haveno.asset;
import java.math.BigInteger;
/**
* Validates a Solana address.
*/
public class SolanaAddressValidator implements AddressValidator {
private static final String BASE58_ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
public SolanaAddressValidator() {
}
@Override
public AddressValidationResult validate(String address) {
if (!isValidSolanaAddress(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 isValidSolanaAddress(String address) {
if (address == null) return false;
if (address.length() < 32 || address.length() > 44) return false; // typical Solana length range
// Check all chars are base58 valid
for (char c : address.toCharArray()) {
if (BASE58_ALPHABET.indexOf(c) == -1) return false;
}
// Decode from base58 and ensure exactly 32 bytes
byte[] decoded = decodeBase58(address);
return decoded != null && decoded.length == 32;
}
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; // invalid char
num = num.multiply(base).add(BigInteger.valueOf(digit));
}
// Convert BigInteger to byte array
byte[] bytes = num.toByteArray();
// Remove sign byte if present
if (bytes.length > 1 && bytes[0] == 0) {
byte[] tmp = new byte[bytes.length - 1];
System.arraycopy(bytes, 1, tmp, 0, tmp.length);
bytes = tmp;
}
// Count leading '1's and add leading zero bytes
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;
}
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
package haveno.asset.coins;
import haveno.asset.Coin;
import haveno.asset.SolanaAddressValidator;
public class Solana extends Coin {
public Solana() {
super("Solana", "SOL", new SolanaAddressValidator());
}
}

View file

@ -9,6 +9,7 @@ haveno.asset.coins.Ether
haveno.asset.coins.Litecoin
haveno.asset.coins.Monero
haveno.asset.coins.Ripple
haveno.asset.coins.Solana
haveno.asset.tokens.TetherUSDERC20
haveno.asset.tokens.TetherUSDTRC20
haveno.asset.tokens.USDCoinERC20

View file

@ -0,0 +1,46 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
package haveno.asset.coins;
import haveno.asset.AbstractAssetTest;
import org.junit.jupiter.api.Test;
public class SolanaTest extends AbstractAssetTest {
public SolanaTest() {
super(new Solana());
}
@Test
public void testValidAddresses() {
assertValidAddress("4Nd1mYZbtJbHkj9QwxAXWah8X9M8vZ9H1fsn6uhPW33k");
assertValidAddress("8HoQnePLqPj4M7PUDzfw8e3Ymdwgc7NqAcoH7okh4wz7");
assertValidAddress("H3C5pGrMmD8FrGd9VRtNVbY3tWusJX3A1u33f9bdBpsk");
assertValidAddress("7zVhJcA5s8zfg3UoDUuG4zmnqaVmLqj6L6F6L8WPLnYw");
assertValidAddress("AVHUu155WoNexeNCGce8mrb8hvg8pBgvCJh4vtd3Q1RV");
assertValidAddress("8HoQnePLqPj4M7PUDzfw8e3Ymdwgc7NqAcoH7okh4wz");
}
@Test
public void testInvalidAddresses() {
assertInvalidAddress("4Nd1mYZbtJbHkj9QwxAXWah8X9M8vZ9H1fsn6uhPW33O");
assertInvalidAddress("H3C5pGrMmD8FrGd9VRtNVbY3tWusJX3A1u33f9bdBpskAAA");
assertInvalidAddress("1");
assertInvalidAddress("abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ123456789");
}
}

View file

@ -202,6 +202,7 @@ public class CurrencyUtil {
result.add(new CryptoCurrency("LTC", "Litecoin"));
result.add(new CryptoCurrency("XRP", "Ripple"));
result.add(new CryptoCurrency("ADA", "Cardano"));
result.add(new CryptoCurrency("SOL", "Solana"));
result.add(new CryptoCurrency("DAI-ERC20", "Dai Stablecoin"));
result.add(new CryptoCurrency("USDT-ERC20", "Tether USD"));
result.add(new CryptoCurrency("USDT-TRC20", "Tether USD"));

View file

@ -361,6 +361,10 @@
-fx-image: url("../../images/ada_logo.png");
}
#image-sol-logo {
-fx-image: url("../../images/sol_logo.png");
}
#image-dark-mode-toggle {
-fx-image: url("../../images/dark_mode_toggle.png");
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB